1. 程式人生 > 實用技巧 >旅行 O(n log n) —— 基環樹

旅行 O(n log n) —— 基環樹

作用:演算法的核心是利用匹配失敗後的資訊,儘量減少模式串與主串的匹配次數以達到快速匹配的目的。具體實現就是通過一個next()函式/ 失配陣列 實現,函式本身包含了模式串的區域性匹配資訊。

時間複雜度:O(m+n)

核心失配陣列

1)最長公共前後綴作用:

eg: 文字串s abeabeabf

模式串s1 abeabf

如上兩串匹配到畫線部分時會失配

普通匹配會倒退主串到s[2],即b位置,然後重新匹配串s1,如果不倒退直接往前呢?那是不行的(考慮該情況 s: aaaab s1: aaab)

而觀察到兩串已匹配且未失配的部分,其最長公共前後綴為ab,即文字串能重新與模式串進行匹配、文字串涵蓋有模式串字首的地方即字尾所在處,只有這一處是兩串有可能匹配成功的且長度最長的,所以可以直接將模式串的字首ab移動到字尾ab處,文字串不動,繼續匹配

2)含義:next[i]表示串s 0~i-1部分的公共最長前後綴,進行一點優化(當s[j]==s[k],將 next[j]=k 變為 next[j]=next[k])

3)作用:記錄好匹配過的部分的狀態,使失配時主串不需要回退,只需利用nex記錄移動更新nex陣列的位置,避免重複匹配,達到快速匹配。

4)優化

優化前:

1 while(j<len){
2         if(k==-1||s[j]==s[k])
3             ++j,++k,nex[j]=k;    //nex[i]等於i前最長公共前後綴,記錄好匹配過的狀態
4         else k=nex[k];
5 }

優化後:

eg: 文字串s aabcabe

模式串s1 abcabd 字串匹配到s1串的d和s串的e處失配,為繼續下一步匹配,呼叫d的nex,即nex[6]=3,因為d前面部分與模式串相同的字元截止到c,

模式串s2 abcabc 字串匹配到s2串的d和s串的e處失配,為繼續下一步匹配,呼叫c的nex,此時nex[6]=0,即失配此時並未失配,若nex[6]=3,c失配時會 返回到字首對應相等的c,但是該公共綴“abc”在之前兩串匹配時已經顯示匹配失敗,所以現在會出現重複匹配的情況,此時應該為 nex[6]=nex[nex[6]];

規律(理解最重要):

tj = tnex [ j ] , nex [ j ] = nex [ nex [ j ] ];

else nex [ j ] = nex [ j ];

1  while(j<len){
2         if(k==-1||s[j]==s[k]){
3                 ++j,++k;
4                 if(s[j]!=s[k])nex[j]=k;
5                 else nex[j]=nex[k];   
6                 //未失配情況,k、j代表的就是字首與字尾相等部分的末尾,兩者完全相同,所以直接再nex一次,
7         }
8         else k=nex[k];
9       }

模板:

 1 //建失配陣列
 2 void getnext(char *p,int nex[]){
 3       int len=strlen(p),k=-1,j=0;
 4       nex[0]=-1;
 5       while(j<len){
 6         if(k==-1||p[j]==p[k]){
 7             ++j,++k;
 8             if(p[j]!=p[k])nex[j]=k;
 9             else nex[j]=nex[k];
10         }
11         else k=nex[k];
12       }
13 }
14 //模式串匹配文字
15 int kmp(char *s,char *p){
16       int i=0,j=0;
17       int len1=strlen(s),len2=strlen(p);
18       while(i<len1){
19         if(j==-1||s[i]==p[j])
20             i++,j++;
21         else 
22             j=nex[j];
23         if(j==len2)
24             j=-1,i--,mark++;
25       }
26       return mark;
27 }
View Code