P45-字串搜尋-KMP演算法
阿新 • • 發佈:2022-04-11
(1)BF 暴力演算法
/* * 一個一個字元比較,比較到最後都還是不相等的,就在A串下標+1,再次一個一個字元比較 * */
(2)RK 暴力的優化,偽hash演算法
/* * 擷取A串進行hashcode,B串進行hashcode,判斷是否相等,不等就A串下標加1再次擷取進行hashcode * 這樣其實還是和暴力沒啥區別 * 自定義hash演算法: * 把B串的每個字元的編碼進行相加,得到結果作為hash值,然後A串也是把相同數量的字元的編碼進行相加 * 判斷最後的hash值是否相同,相同的話再一個一個字元比較(因為這種方法很容易hash衝突) * 如果值不同,A傳的hash值只需要減去第一個字元的編碼,再加上新字元的編碼,即可得到新的hash值 **/
(3)BM演算法
(3.1)壞字元規則
(3.2)好字尾規則
(4)KMP演算法
//字串搜尋-----試題常見 /* * 給定兩個字串A、B,判斷B在A中是否存在,存在返回A中的下標,不存在返回-1 * 如:A:ABCABCAABCABCD * B:ABCABCD * 返回值7 * 就是indexOf * */ public class P43 { //KMP演算法 /* * 字首:字串A和B,A=B+S,S非空,則B為A的字首 * 字尾:A=S+B,S非空,則B為A的字尾 * PMT值:字首集合和字尾集合的交集,最長元素的長度 * 假如串是ABCA,字首有A、AB、ABC,字尾有A、CA、BCA,PMT值就是1 * 假如串是ABC,字首有A、AB,字尾有B、BC,PMT值就是0,沒有交集 * 找交集是為了移動,如 * BCADBCABCABCD * BCADBCD * 這時是A和D不同,然後BCADBCD的字首和BCADBCA的字尾的交集是BC,就可以移動為 * BCADBCABCABCD * BCADBCD * 字首移動到字尾位置 * 交集長度是2,相當於把B串的2下標移動到那個A串不匹配的下標,就對齊了(最好找找網上的圖示或視訊,這裡文字不好表示) * 部分匹配表:PMT值的集合,字串的所有字首的PMT值 * prefix:每一個下標位置對於一個PMT值,組成的數值 * next:prefix向右移一個下標位置,組成next陣列 **/ public static void main(String[] args) { String str = "ABCABCAABCABCD"; String strPattern = "ABCABCD"; int[] next = new int[strPattern.length()]; getNext(strPattern.toCharArray(), next); int i = search(str.toCharArray(), strPattern.toCharArray(), next); System.out.println(Arrays.toString(next)); System.out.println(i); System.out.println(str.indexOf(strPattern)); }//第一個引數是A串,第二個引數是B串,第三個引數是next陣列,含有PMT值 public static int search(char[] str, char[] pattern, int[] next){ int i = 0; int j = 0; while(i < str.length && j < pattern.length){ //j是有可能-1的,next[0]就是-1 if(j == -1 || str[i] == pattern[j]){ //判斷字元是否相等,相等就兩個串的下標都加1再比較 i++; j++; }else{ j = next[j]; } } if(j == pattern.length){ return i - j; }else{ return -1; } } /* * 過程分析 * A串:ABCABCAABCABCD * B串:ABCABCD * next陣列是[-1, 0, 0, 0, 1, 2, 3] * 先 i=0,j=0,進入while * str[0] == pattern[0],進入分支,i=1,j=1 * str[1] == pattern[1],進入分支,i=2,j=2 * str[2] == pattern[2],進入分支,i=3,j=3 * str[3] == pattern[3],進入分支,i=4,j=4 * str[4] == pattern[4],進入分支,i=5,j=5 * str[5] == pattern[5],進入分支,i=6,j=6 * str[6] != pattern[6],進入else,i=6,j=next[6]=3,即如下所示 * A串:ABCABCAABCABCD * B串: ABCABCD * str[6] == pattern[3],進入分支,i=7,j=4 * str[7] != pattern[4],進入else,i=7,j=next[4]=1,即如下所示 * A串:ABCABCAABCABCD * B串: ABCABCD * str[7] != pattern[1],進入else,i=7,j=next[1]=0,即現在的對齊狀態如下所示 * A串:ABCABCAABCABCD * B串: ABCABCD * str[7] == pattern[0],進入分支,i=8,j=1 * str[8] == pattern[1],進入分支,i=9,j=2 * str[9] == pattern[2],進入分支,i=10,j=3 * str[10] == pattern[3],進入分支,i=11,j=4 * str[11] == pattern[4],進入分支,i=12,j=5 * str[12] == pattern[5],進入分支,i=13,j=6 * str[13] == pattern[6],進入分支,i=14,j=7 * 退出while * j==B串長度,返回 i-j即14-7=7 * */ //構造next陣列,其實就是PMT右移,因為prefix就是PMT陣列,而next就是prefix全部右移的結果 //第一個引數是B串,第二個引數是空陣列 public static void getNext(char[] pattern, int[] next){ //因為是要求字首和字尾的交集,而字首就是不含最後一個字元,字尾就是不含第一個字元 //即 abcdefg // tuvwxyz 這樣的對齊就是最初狀態 // -1 //可以看看下方的註釋說明 next[0] = -1; int i = 0; int j = -1; while(i < pattern.length){ if(j == -1){ i++; j++; }else if(pattern[i] == pattern[j]){ //就是判斷上面註釋例子的 b 是否等於 t i++; j++; next[i] = j; //相等就是有交集,看看交集長度就是j }else{ j = next[j]; } } } /* * 假如pattern是ABCABCD * x串:ABCABCD * y串: ABCABCD * next[0]=-1 * i是x串的0下標 * j是y串的-1下標,對應x串的A * 進入while,j==-1,這是i=1,j=0,即x串的B、y串的A * x串的B != y串的A,所以走else,j=next[0]=-1,i還是1 * * j==-1,這是j=0,i=2,即如下所示 * x串:ABCABCD * y串: ABCABCD * 又不相等了,j=next[0]=-1 * 再次進入 j==-1分支,這是j=0,i=3,即 * x串:ABCABCD * y串: ABCABCD * * 然後pattern[i] == pattern[j],大家都右移下標,i=4,j=1,並記錄next[4]=1 * 這裡為什麼是先加加,再記錄交集長度呢?說過了,這是next陣列,PMT陣列右移 * 也就是prefix[3]=1,即x串的3下標A字元的交集長度(PMT值)是1,所以next就是next[4]=1 * * pattern[4] == pattern[1],再次進入分支,這時 i=5,j=2,next[5]=2 * pattern[5] == pattern[2],再次進入分支,這是 i=6,j=3,next[6]=3 * * 然後pattern[6] != pattern[3],j=next[3]=0,沒對next[3]賦值過,是初始化就是0,i=7 * 退出while * next陣列 [-1, 0, 0, 0, 1, 2, 3],也可以推斷出prefix陣列 [0, 0, 0, 1, 2, 3, 0] * */ }