1. 程式人生 > 其它 >BF(字串暴力匹配演算法)+KMP演算法 (java c++雙語)

BF(字串暴力匹配演算法)+KMP演算法 (java c++雙語)

技術標籤:JAVA演算法資料結構字串動態規劃演算法

模式匹配KMP演算法

一,KMP演算法分析
1,從s第一個字元開始,i=1,j=1,如圖所示,比較兩個字元是否相等,如果相等則i++,j++.第一次匹配不相等,如圖所示。
在這裡插入圖片描述
在這裡插入圖片描述
2,按BF演算法,如果不等,則i回退到i-j+1,j回退到0
在這裡插入圖片描述
3,其實i不用回退,讓j回退到第2個位置,接著比較。
在這裡插入圖片描述
4,為什麼呢?因為串中開頭的兩個字元和I指向的字元前面的兩個字元一模一樣,這樣j就可以回推到第二個位置繼續比較,因為前面兩個字元已經相等了。
在這裡插入圖片描述
在這裡插入圖片描述
5,我們發現s中i指向的字元,前面的兩個字元和T中j指向的字元,前面兩個字元一模一樣,因為他們一直相等,i++,j++才會走到當前的位置。

在這裡插入圖片描述
也就是說,我們不必判斷開頭的2個字母和i指向的字元,前面的兩個字元是否一樣,只需要在T本身比較就可以。
6,假設T中當前j指向的字元前面的所有字元為T’,只需要比較T’字首和字尾即可。
以下分析就是通過判斷T本身,得到其字首和字尾陣列next之後。s串不用再分解得到其子串並和T挨個進行比對了,i不回退,當s[i]!=t[i]時,僅是j回退到next[j],然後重新開始比較即可。
在這裡插入圖片描述
7,字首是從前向後取若干個字元,字尾是從後向前取若干個字元。字首和字尾不可以取字串本身,所以串的長度為n,字首和字尾長度最多可達到n-1。
在這裡插入圖片描述
判斷T’的字首和字尾是否相等,並找相等字首和字尾的最大長度。
在這裡插入圖片描述
8,相等字首字尾最大長度為L=2,則j可以回退到第L=2個位置繼續比較了。
因此,當i,j指向的字元不等時,只需要求出T’的相等字首字尾的最大長度L,i不變,j回退到L的位置繼續比較則可。
在這裡插入圖片描述
在這裡插入圖片描述
9,現在可以寫出通用公式來表示next[j]表示j需要回退到的位置。在這裡插入圖片描述
所以T=“abaabe”的next陣列如圖所示
在這裡插入圖片描述
10,用動態規劃遞推:
①假設,next[j]=k,T’=“t1t2…tj-1”,那麼T’相等字首,字尾最大長度為k-1,在這裡插入圖片描述
那麼next[j+1]等於什麼?
②如果Tk=Tj:那麼next[j+1]=k+1,即:相等字首和字尾的長度比next[j]多1.在這裡插入圖片描述
③如果Tk!=Tj:則有回退到next[k]=k’的位置,比較Tk’與Tj是否相等。
如果Tk’=Tj,則next[j+1]=k’+1
如果Tk’!=Tj,則繼續回退
next[k’]=k’’,比較Tk’‘與Tj是否相等。
如果Tk’’=Tj,則next[j+1]=k’’+1
如果Tk’’!=Tj,則繼續回退.
直到next[1]=0停止。

二**,KMP程式碼分析**

public static int []get_next(char []t){//得到kmp演算法中的next陣列
        int length=t.length;
        int next[]=new int[length];//建立next陣列
        next[0]=-1;//沒有字首和字尾
        int j=0;//
        int k=-1;
        while (j<length-1){//必須減一,因為索引從0開始,++j越界
            if(k==-1 || t[j]==t[k]){//相等 或 原始沒有字首和字尾時
                next[++j]=++k;//字首和字尾相等時,把k的長度付給相應的j所指的next中
            }
            else {
                //回溯 t[j]和 t[k]不等時
                k=next[k];
            }
    }
        return next;
}

1,初始化next[0]=-1,j=0,k=-1.
進入迴圈,判斷是否滿足k=-1,
是則執行next[++j]=++k,
則next[1]=k=0,
此時j=1,k=0;
2,j=1,k=0,進入迴圈,判斷是否滿足T[j]bT[k]==a,
由於T[1]!=T[0],則執行k=next[k],此時j=1,k=1
k=next[0]=-1;
3,j=1,k=-1,進入迴圈,判斷是否滿足k=-1
則執行next[++j]=++k,即next[2]=0,此時j=2,k=0;
4,j=2,k=0,進入迴圈,判斷是否滿足T[j]aT[k]==a,
由於T[2]==T[0],則執行next[++j]=++k,此時j=3,k=1
5,j=3,k=0,進入迴圈,判斷是否滿足T[j]bT[k]==a,
由於T[3]!=T[1],則執行k=next[k],此時k=next[1]=0;j=3,k=0.

JAVA

package String;

import java.util.Scanner;

public class KMP {
    public static int []get_next(char []t){//得到kmp演算法中的next陣列
        int length=t.length;
        int next[]=new int[length];//建立next陣列
        next[0]=-1;//沒有字首和字尾
        int j=0;//初始化
        int k=-1;
        while (j<length-1){//必須減一,因為索引從0開始,++j越界
            if(k==-1 || t[j]==t[k]){//相等 或 原始沒有字首和字尾時
                next[++j]=++k;//字首和字尾相等時,把k的長度付給相應的j所指的next中
            }
            else {
                //回溯 t[j]和 t[k]不等時
                k=next[k];
            }
    }
        return next;
}

public static int []get_next2(char []t){//優化版
    int length=t.length;
    int next[]=new int[length];//建立next陣列
    next[0]=-1;//沒有字首和字尾
    int j=0;//初始化
    int k=-1;
    while (j<length-1){//必須減一,因為索引從0開始,++j越界
        if(k==-1 || t[j]==t[k]){//相等 或 原始沒有字首和字尾時
            next[++j]=++k;//字首和字尾相等時,把k的長度付給相應的j所指的next中
            if(t[j]==t[k]){//改進了,和相鄰的結果一樣,就不需要比較了,直接付給next
                next[j]=next[k];
            }
        }
        else {
            //回溯 t[j]和 t[k]不等時
            k=next[k];
        }
    }
    return next;

}

public static int kmp(char[]s,char []t){
        int []next=get_next(t);
        int i=0;
        int k=-1;
        while (i<=s.length-1 && k<=t.length-1){
            if(k==-1||s[i]==t[k]){// 如果k是沒有前後綴或者s[j] == t[k] 繼續比較後面的字元
                ++i;
                ++k;
            }
            else {
                k=next[k];
            }
        }
        if(k<t.length){//只有相等的字元才能k++ k代表長度 <t模式串的長度 沒有找到t這個串
            return -1;//匹配失敗
        }
        else {//找到t這個串了
            return i-t.length;//i是s串中的計數因子
        }
}

    public static void main(String[] args) {
        Scanner sc=new Scanner(System.in);
        String s="";
        System.out.println("主串:"+s);

        s=sc.nextLine();

        String t="";
        System.out.println("子串:"+t);
        t=sc.nextLine();

        char []s1=s.toCharArray();
        char [] t2=t.toCharArray();


        System.out.println("主串和子串在第"+kmp(s1,t2)+"個字元首次匹配");

    }
}

C++


#include "stdio.h"
#include "iostream"
#include "cstring"
using namespace std;
#define Maxsize 100
typedef char SString[Maxsize];
//strassign
bool StrAssign(SString &T,char * chars){//生成一個其值等於chars的串T
    if(strlen(chars)>Maxsize){
        return false;
    }else{
        //賦值
        //C版的字串陣列  0【長度】1~Maxsize【字元】
        T[0]=strlen(chars);
        for(int i=1;i<=T[0];i++){
            T[i]= *(chars+i-1);//挨個送字元
            cout<<T[i]<<" ";
        }
        cout<<endl;
        return true;
    }

}
void get_next(SString T,int next[]){
    int j=1,k=0;
    next[1]=0;
    while(j<T[0]){//++j 會越界 字首和字尾不能取n,只能取n-1
        if(k==0 ||T[j]==T[k]){//沒有字首和字尾或者字首和字尾相等時
            next[++j]=++k;
        }else{//回溯繼續比較
            k=next[k];
        }
    }
}
void get_next2(SString T,int next[]){
    int j=1,k=0;
    next[1]=0;
    while(j<T[0]){//++j 會越界 字首和字尾不能取n,只能取n-1++j 會越界 字首和字尾不能取n,只能取n-1
        if(k==0 ||T[j]==T[k]){//沒有字首和字尾或者字首和字尾相等時
            next[++j]=++k;
            if(T[j]==T[k]){//表示新的k的回退位置的字元和j所指的位置還是相等,k的回退位置送給現在的j
                next[j]=next[k];
            }
        }else{//回溯繼續比較
            k=next[k];
        }
    }
}

int index_kmp(SString S,SString T,int next[],int pos){
    //利用模式串T的next陣列,求T在S中的首個出現位置
    //
    int i=pos;
    int k=0;
    int sum=0;
    while(i<=S[0] && k<=T[0]){
        sum++;
        if(k==0||S[i]==T[k]){// 如果k是沒有前後綴或者S[j] == T[k] 繼續比較後面的字元
            i++;
            k++;
        }else{
            k=next[k];//回溯
        }
    }
    if(k>T[0]){//找到了
        return i-T[0];
    }else{//只有相等的字元才能k++ k代表長度 <T模式串的長度 沒有找到T這個串
        return -1;
    }
}

int  main(){

    SString S,T;
    char str[100];
    cout<<"串S:"<<endl;//abaabaabeca
    cin>>str;
    StrAssign(S,str);
    cout<<"串T:"<<endl;//abaabe
    cin>>str;
    StrAssign(T,str);
    int *next=new int[T[0]+1];
    get_next(T,next);
    cout<<"主串和子串在第"<<index_kmp(S,T,next,1)<<"個位置處首次匹配【索引從1開始】"<<endl;

}