連結串列、棧、佇列、KMP相關知識點

xbhog發表於2021-02-21

連結串列、棧與佇列、kmp;

陣列模擬單連結串列:

用的最多的是鄰接表--就是多個單連結串列:

作用:儲存樹與圖

需要明確相關定義:

為什麼需要使用陣列模擬連結串列

  1. 比使用結構體 或者類來說 速度更快
  2. 程式碼簡潔
  3. 演算法題:空間換時間

題目詳情

圖解:

head儲存連結串列頭,e[]儲存節點的值,ne[]儲存節點的next指標,idx表示當前用到了哪個節點

import java.util.*;

public class Main{
    static int N = 100010;
    static int idx,head;
    static int[] e = new int[N];
    static int[] next = new int[N];
    
    // 初始化
    static void init(){
        head = -1;
        idx = 0;
    }
    // 向連結串列頭插入一個數
    static void insertHead(int x){
        e[idx] = x;
        next[idx] = head;
        head = idx++;
    }
    
    // 向k位置插入x
    static void insert(int k,int x){
        e[idx] = x;
        next[idx] = next[k];
        next[k] = idx++;
    }
    
    // 刪除k位置的數
    static void delete(int k){
        next[k] = next[next[k]];
    }
    // 主函式
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        init();
        int n = sc.nextInt();
        while(n-- != 0){
            String s = sc.next();
            if(s.equals("H")){
                int x = sc.nextInt();
                insertHead(x);
            }else if(s.equals("D")){
                int k = sc.nextInt();
                if(k == 0) head=next[head];
                else delete(k-1);
            }else if(s.equals("I")){
                int k = sc.nextInt();
                int x = sc.nextInt();
                insert(k-1,x);
            }
            
        }
        for(int i =head;i != -1;i=next[i]){
            System.out.print(e[i]+" ");
        }
        
    }
    
}

陣列模擬雙連結串列:

作用:優化某些問題

題目詳情

棧:

先進後出

// tt表示棧頂
int stk[N], tt = 0;

// 向棧頂插入一個數
stk[ ++ tt] = x;

// 從棧頂彈出一個數
tt -- ;

// 棧頂的值
stk[tt];

// 判斷棧是否為空
if (tt > 0)
{

}

佇列:

先進先出

1. 普通佇列:
// hh 表示隊頭,tt表示隊尾
int q[N], hh = 0, tt = -1;

// 向隊尾插入一個數
q[ ++ tt] = x;

// 從隊頭彈出一個數
hh ++ ;

// 隊頭的值
q[hh];

// 判斷佇列是否為空
if (hh <= tt)
{

}
2. 迴圈佇列
// hh 表示隊頭,tt表示隊尾的後一個位置
int q[N], hh = 0, tt = 0;

// 向隊尾插入一個數
q[tt ++ ] = x;
if (tt == N) tt = 0;

// 從隊頭彈出一個數
hh ++ ;
if (hh == N) hh = 0;

// 隊頭的值
q[hh];

// 判斷佇列是否為空
if (hh != tt)
{

}

單調棧:

題目詳情

每個數找到左邊離自己最近且比自己小的數:

暴力演算法 :

import java.util.*;
import java.io.*;

public class Main{
    static int N = 100010;
    static int[] q = new int[N];
    
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        for(int i = 0;i < n; i++){
            q[i] = sc.nextInt();
        }    
        
        for(int i = 0;i < n;i++){
            int index =0;
            for(int j = i-1;j >=0;j--){
                if(q[i]>q[j]){
                    index = j;
                    System.out.print(q[j]+" ");
                    break;
                }
            }
            if(q[i]<=q[index]){
                System.out.print("-1 ");
            }
        }
        
    }
    
}

AC程式碼:

import java.util.*;

public class Main{
    static int N = 100010;
    static int[] stk = new int[N];
    static int tt=0;
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        
        int n = sc.nextInt();
        
        for(int i = 0; i < n; i++){
                
            int x = sc.nextInt();
            // 如果棧中還有元素並且棧頂元素大於x的話,從棧頂彈出;
            while(tt != 0 && stk[tt] >= x) tt--;
            // 上面結束以後是棧頂元素小於輸入的元素;  stk[tt] < x;  這樣就保證了x元素在左邊並且小於本身的元素是棧頂元素;
            //如果棧中有元素,輸出棧頂元素
            if(tt != 0) System.out.print(stk[tt]+" ");
            // 否則棧中沒有元素
            else System.out.print("-1"+" ");
            // 將元素加入棧中
            stk[++tt] = x;
        }
        
    }
    
}

單調佇列:

滑動視窗:

解題思路:

  1. 判斷隊頭出沒出視窗 if true-->hh++;
  2. 求最小值,保持佇列單調上升,判斷隊尾元素tt與當前元素a[i]的大小,若tt >=a[i],剔除隊尾元素;
  3. 求最大值,保持佇列單調下降,判斷隊尾元素tt與當前元素a[i]的大小,若tt <=a[i],剔除隊尾元素;
  4. 將當前元素下標加入隊尾;
  5. 如果滿足條件輸出

注:佇列是先進先出模式,只有在佇列中保持單調性 才能保證,隊頭為最小值或者最大值;

import java.io.*;
public class Main{
    static int N = 1000010;
    // 原陣列
    static int[] a = new int[N];
    // 佇列
    static int[] q = new int[N];
    
    static int hh=0,tt=-1;
    public static void main(String[] args) throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        
        String[] num = br.readLine().split(" ");
        
        // a陣列中由n個元素
        int n = Integer.parseInt(num[0]);
        
        // 滑動視窗的大小
        int k = Integer.parseInt(num[1]);
        String[] nums = br.readLine().split(" ");
        
        
        // 初始化a陣列
        for(int i = 0; i < n; i++) a[i] = Integer.parseInt(nums[i]);
        
        
        // 查詢最小值
        for(int i = 0; i < n; i++){
            
            // 判斷當前視窗是否大於滑動視窗的大小
            if(hh <= tt && i-q[hh]+1 > k) hh++;
            
            // 判斷如果有元素並且隊尾元素大於入隊元素,則捨棄隊尾元素來保證單調上升的佇列;
            while(hh <= tt && a[q[tt]] >= a[i]) tt--;
            
            //向佇列加入元素下標 
            q[++tt] = i;
            
            // 如果下標大於等於視窗大小,那麼輸出隊頭元素
            if(i+1 >=k) bw.write(a[q[hh]]+" ");
        }
        bw.write("\n");
        hh = 0;
        tt = -1;
        // 查詢最大值
        for(int i = 0; i < n; i++){
            
            // 判斷當前視窗是否大於滑動視窗的大小
            if(hh <= tt && i-q[hh]+1 > k) hh++;
            
            // 判斷如果有元素並且隊尾元素小於入隊元素,則捨棄隊尾元素來保證單調上升的佇列;
            while(hh <= tt && a[q[tt]] <= a[i]) tt--;
            
            //向佇列加入元素下標 
            q[++tt] = i;
            
            // 如果下標大於等於視窗大小,那麼輸出隊頭元素
            if(i+1 >=k) bw.write(a[q[hh]]+" ");
        }
        bw.flush();
        br.close();
        bw.close();
    }
    

    
}

KMP演算法:

KMP定義:是取自三個發明人的首字母組成的;

作用:是一個字串匹配演算法,對暴力的一種優化

kmp實現的方式:求next[]、以及匹配字串

明確其中的概念:

對字串的匹配,需要兩個字串:長字串為模板字串,短的字串為匹配字串;

字首與字尾的概念:(很重要)

舉個例子:列舉

ababa

其字首為:

a,ab,aba,abab

其字尾:

baba,aba,ba,a

其中字首與字尾的最長且相同的元素字串是:aba ;長度length:3。

模板串的匹配:

image-20210220180508330

匹配失敗的話,假設p1串往後移動到p2串位置,表明1串=2.2串;

\[\because 匹配失敗使得p1移動到p2位置\\ \therefore s-1 = p2-2.2\\ \because p2是p1的平移\\ \therefore p1 = p2\\ \therefore p1-2.1 = p2-2.2\\ 又\because s = p1\\ \therefore s-1 = p1-3\\ 由上可知:\\ p2-2.2 = p1-3;\\ p1-2.1 = p1-3; \]

如果匹配失敗,p串最少往右移動多少(看next[]陣列),可以使得p串與s串相等,由圖可知:往後移動多少是看p串,如果我們能預處理來這個東西:使得p1-2.1串能與p1.3串相等;這個相等的最大值是多少,值越大,則表示往後移動p串的距離越少;

// kmp匹配過程,遍歷s模板每個元素
        for(int i =1, j =0; i<=m; i++ ){
            // 如果j回退到0或者i位置元素與j+1位置的元素不相同,那麼執行回退操作,
            //j退回next[i]處,即字首與字尾相同的區間最後元素位置 
            while(j != 0 && s[i] != p[j+1]) j = next[j];
            if(s[i] == p[j+1]) j++;
            // 如果匹配成功
            if(j ==n){
                // 輸出匹配元素在s模板中的起始位置
                bw.write(i-n+" ");
                // 繼續匹配;
               j = next[j];
            }

也就是p串的字首與字尾相同的最大字串是多少;----也就是next[]陣列;

next[]陣列(難點)

其中next[j]陣列表示的是:子串p[1~j]的最長相等前字尾的字首最後一位的下標。

對 p = “abcab”

p a b c a b
下標 1 2 3 4 5
next[ ] 0 0 0 1 2
對next[ 1 ] :字首 = 空集—————字尾 = 空集—————next[ 1 ] = 0; (特殊點)

對next[ 2 ] :字首 = { a }—————字尾 = { b }—————next[ 2 ] = 0;

對next[ 3 ] :字首 = { a , ab }—————字尾 = { c , bc}—————next[ 3 ] = 0;

對next[ 4 ] :字首 = { a , ab , abc }—————字尾 = { a . ca , bca }—————next[ 4 ] = 1;

對next[ 5 ] :字首 = { a , ab , abc , abca }————字尾 = { a , ab , cab , bcab}————next[ 5 ] = 2;

image-20210221103501736

// 實現next陣列(找到字首與字尾相同的最大元素長度)
int[] next = new int[n+1];
//next[1] = 0; 
for(int i = 2,j =0; i<= n;i++){
    // 如果j沒有回退到0並且i位置元素與j+1位置的元素不相同,那麼執行回退操作,
    //j退回next[i]處,即字首與字尾相同的區間最後元素位置
    while(j !=0 && p[i] != p[j+1]) j = next[j];
    // 如果i與j+1相同,那麼移動j向後匹配
    if(p[i] == p[j+1]) j++;
    // p[1,j] = p[i-j+1,i];字首與字尾相同;i表示終點
    next[i] = j;
}

完整程式碼:

import java.util.*;
import java.io.*;

//下標為什麼從1開始,簡化程式碼的複雜度;
public class Main{

    public static void main(String[] args) throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        
        // 對模式串p進行操作
        int n = Integer.parseInt(br.readLine());
        char[] p = new char[n+1];
        // 將輸入的字元儲存到緩衝區
        String pstr = br.readLine();
        for(int i = 1; i <=n;i++){
            // 取出的字串下標-1;
            p[i] = pstr.charAt(i-1);
        }
        
        // 對模板串進行操作
        int m = Integer.parseInt(br.readLine());
        char[] s = new char[m+1];
        String sstr = br.readLine();
        for(int i = 1;i <= m;i++){
            s[i] = sstr.charAt(i-1);
        }
        
        // 實現next陣列
        int[] next = new int[n+1];
        //next[1] = 0; 
        for(int i = 2,j =0; i<= n;i++){
            // 如果j回退到0或者i位置元素與j+1位置的元素不相同,那麼執行回退操作,
            //j退回next[i]處,即字首與字尾相同的區間最後元素位置
            while(j !=0 && p[i] != p[j+1]) j = next[j];
            // 如果i與j+1相同,那麼移動j向後匹配
            if(p[i] == p[j+1]) j++;
            // p[1,j] = p[i-j+1,i];字首與字尾相同;i表示終點
            next[i] = j;
        }
        
        // kmp匹配過程,遍歷s模板每個元素
        for(int i =1, j =0; i<=m; i++ ){
            // 如果j回退到0或者i位置元素與j+1位置的元素不相同,那麼執行回退操作,
            //j退回next[i]處,即字首與字尾相同的區間最後元素位置 
            while(j != 0 && s[i] != p[j+1]) j = next[j];
            if(s[i] == p[j+1]) j++;
            // 如果匹配成功
            if(j ==n){
                // 輸出匹配元素在s模板中的起始位置
                bw.write(i-n+" ");
                // 繼續匹配;
               j = next[j];
            }
            
        }
        bw.flush();
        br.close();
        bw.close();
    }
    
}

結束:

感謝大家看到最後,如果有錯誤,歡迎指正!
參考文獻:https://www.acwing.com/solution/content/14666/

相關文章