당신이 모르는 것들에 대한 문자열 ---- KMP 알고리즘을 찾기

사설 :

(이 글을 읽기 전에 당신은 KMP 알고리즘의 기본 개념을 이해할 필요가있다. 또한,이 문서의 제인에 도로를 생각의 정신, 모든 예는 처음부터 끝까지 할 일을 설명 할 것이다)

 

기존의 온라인 KMP 알고리즘 블로그를 많이 읽고, 우리는 광범위한 심지어 불완전한 KMP 알고리즘을 발견했다. 즉, 일치하지 않는 백 오프를 달성하기 위하여 유한 상태 오토 마톤 다음과 같은 배열에 의해. 이것은 확실히 좋은 방법입니다.

그러나 우리는 경험을 더 나은 및 더 완벽한 방법입니다거야 ---- KMP 알고리즘의 전체 DFA있다

 

본 명세서에서 일반적인 방법으로 설명에 기재된 방법과 비교하여 제 나열된 여러 장점 :

  1. 최악의 경우, 작업의 수는 일반적으로 실제로 문자열의 3 분의 2이다.
  2. 모든 경우에, 문자열 피연산자 덜 일반적인 관행이다.
  3. 세부의 일반적인 관행에 대한 존중의 더 완전한 아이디어는, 당신이 KMP의 새로운 이해를 할 수 있도록 학습.

 (독자들은 후 결국 텍스트 권리를 읽어 돌아가서이 말을 볼 수 있습니다)

 

I, 유한 상태 자동 장치에 (DFA 무엇입니까)

KMP 알고리즘은 유한 상태 오토 마톤, DFA 일반적인 알고리즘 다음 어레이의 동작을 시뮬레이션하고 배열 운영 가이드 유한 상태 기계로서 사용된다.

다른 유한 상태 자동 장치, 프로그램은 자연스럽게 다른있을 것입니다 달렸다.

이 문서에서 제시된 KMP 알고리즘에서는, 유한 상태 오토 마톤 지침으로 DFA의 2 차원 배열을 사용

  1. 정의 : DFA = 새로운 INT [R] [M]가, R은 텍스트 문자 유형 (EXTENDED_ASCII R은 256 정상적인 상황에서 충분하다) 나타날 수이고, M은 패턴 문자열의 길이이다.
  2. 공간 : R의 다음 배열에 비해 DFA 시간 더 큰 공간, 그러나 공간의 비용은 성능 향상에 안내 수밖에 없다 !
  3. 저장 내용 :처럼 배열과 옆, DFA는 스타트 모드 문자열의 위치는 각 위치에 맞게 실패하지만 경기가 특정 위치와 일치를 다시 시작하지 못할 때 발생할 수있는 다른 문자, DFA 자세한입니다 저장, 이 혜택은 나중에 성능 분석에 감소 될 것이다.

 

         1 ABABAC 판정 유한 상태 머신에 대응하는 오토 마톤도 패턴 문자열 

 

그림 패턴 문자열 확실 보여준다 ABABAC 유한 상태 머신은 상기 결정된 오토 마톤에 대응

DFA [A] [J]를 나타냄 패턴 문자열 성공적 패턴 문자열이 일치 할 경우에 문자 'A'를 텍스트에 대응하는 경우의 j 번째의 위치의 위치 정합.

1 걸릴 DFA는 [A] [3] 텍스트 A, 다시 패턴 문자열 DFA 이번에 [A] [3] = 1에 대응하는 제 ABABAC (B)에 대한 패턴 일치하는 문자열을 나타낸다 즉 다시 처음 (B)에 ABABAC 패턴 문자열이고, 그 다음으로 계속 (또한에서 ABABAC에 제 자리, 여기 됨) 비교는 다음 문자를 계속.

그것은 꽤 복잡해 보이지만, 건축의 방법을 이해, 당신은 그것을 사용할 수있는 유연성을 가지고있다.

 

1, DFA 생성자 :

우리는 현재 매칭 위치 X의 J와 건설 DFA, J 포인트의 도움이 필요합니다, X는 경기가 실패 다시 시작 위치입니다. J를 시작하고 X는 0으로 설정됩니다.

각 J, 우리는해야 할 :

  1. DAF의 [] [X] 복사 DAF []를 [J] (a 매치 실패 케이스)
  2. DAF의은 [pat.charAt는 (j)] [J]를 1 (성공적으로 일치하는 경우에 대해)의 J +로 설정
  3. X 업데이트

다음과 같은 코드로 표현 :

(아마 아래 완벽한 예와 결합 된 코드의 리더 모습, 그 후에 디버거를 실행하는 코드를 할 것을 권장합니다)

DFA [pat.charAt (0)] [0] = 1이다. ]
  ( INT X- = 0, J의 = 1]. J <M, J ++) { // 계산 DFA [] [J] 
     ( INT C = 0, C <& lt; 화학식 C ++) { // 불일치 
        DFA [C] [J] = DFA [C] X-] 
    } 
    DFA [pat.charAt (J)] [J] = J + 1. , 
    X- = DFA [PAT .charAt (J)] X-] 
}    

 

최대 위의 코드 건설 과정의 전체 프리젠 테이션의 기초 :

①  J 및 X는 0이고, DFA [pat.charAt (0) ] [0] = 1

 

용 루프로 ② X = 0, J = 1 : 컬럼에 X 칼럼 J를 복사하고 DFA는 [pat.charAt는 (j)]을 [j]가 J + 1, X 업데이트 = 설정

업데이트 된 X X 0 번째 단계를 볼 수 있고, X는 DFA는 [pat.charAt는 (j)]을 [X는 = DFA [B]가 [0] = 0 (약 X 접점 논의 변경 = 때부터 제 2 단계 아래로 언급됩니다)

 

③ 제 2 사이클 X = 0, J = 2 : X의 칼럼 칼럼 J 복사 후 설정 DFA는 [pat.charAt는 (j)]을 [j]가 = J + 1, X 업데이트

X = DFA [pat.charAt (j)] [X] = DFA [A] [0] = 1

 

④ 제주기 X = 1, J = 3 : X의 칼럼 칼럼 J 복사 후 설정 DFA는 [pat.charAt (j)] [J] = J + 1, X 업데이트

 

X=dfa[pat.charAt(j)][X]=dfa[B][1]=2

 

⑤ 第四次循环X=2,j=4:将X的列复制到j的列,再设dfa[pat.charAt(j)][j]=j+1,更新X

 

X=dfa[pat.charAt(j)][X]=dfa[A][2]=3

 

⑥ 第四次循环X=3,j=5:将X的列复制到j的列,再设dfa[pat.charAt(j)][j]=j+1,已经结束到最后一位,不用更新X

 

 

到这里就结束了模式字符串ABABAC的dfa构造最终得到的结果:

 

相信大家已经明白了dfa的构造思路

为巩固练习,下面请读者自己构造出模式字符串ABRACAD的daf,然后和下图对照一下是不是一样

 

2、关于X的一些问答:

值得一提的是,X是构造dfa的关键,下面几个问答有助于我们理解整个dfa构造。

为什么每次都能得出X的值?

答:因为X永远小于j,X走的是j走的老路。

为什么要把X列复制到j列?

答:dfa里记录了到每种状态时可能的所有选择,如果状态A发生不匹配时可以回到状态B继续匹配,那我们就可以先把状态B复制到状态A,这样在状态A不匹配时就可以直接使用状态B的方案。

X的位置何时会发生变化?

X的下一个位置与j当前指向的字符、j之前指向过的字符、X当前位置都有关,事实上不管j当前指向的字符在之前是否出现过,X都可能移动。

X的位置会怎么变化?

当每次j指向的字符与X指向的字符能够连续对应上的时候,X就会每次向后移一位(字符与前缀对应时X往后移)。

当j指向的字符在之前没有出现过,X就会指向0。

 3、实例对问题的证明:

 

上图是模式ABCDE的dfa数组,可以观察到ABCDE中是没有出现重复字符的,所以到最后X依然指向0

对应极端情况,前面的字符出现重复达到了四次,X也是要移动四次,但只停留在3是因为模式串已经匹配完成,不需要再移动X。

 

关于X的移动,是需要读者自己在模拟dfa构造中细想的,想明白了就能全懂KMP,不明白就再看看上面的问题,尝试自己作答就会有新的心得。

 

二、改变搜索方法

有了强大的有限状态自动机,怎么用它呢?实际使用中是否比原来更强大呢?咱直接将两者的代码贴出来一顿对比,顺便说明精妙之处。

大体的思路是一样的,就是将txt字符串从头到尾循环一遍,过程中不断判断模式串的位置

 

1、先来看看一般方法中的搜索方法代码:

for(i=0;i<n;i++){
    while (j>-1&&txt.charAt(i)!=pat.charAt(j)){
        j=next[j];
    }
    if(j==-1||txt.charAt(i)==pat.charAt(j)){
        j++;
    }
    if(j==m){return i-j;
    }
}            

一边从头到尾循环,一边判断j是不是等于m,应该注意到的是,for循环中还包含了一个while,用来做回退和继续匹配的。

可以发现,这个过程中的操作次数必定是要大于i的(每次for循环都可能要加入while)

 

2、下面是使用dfa后的搜索方法:

for(j=0,i=0;i<N&&j<M;i++){
    j=dfa[txt.charAt(i)][j];
}
if(j==M){
    System.out.println("匹配成功");
    return i-M;
}else {
    System.out.println("匹配失败");
    return N;
}

可以看到,在for循环之后,直接进行匹配成功或失败的判断,整个过程的操作次数等于i,是小于一般方法的。

 

 

三、性能分析对比

①当字符串不匹配时(这是两种方法差异最大的地方):

使用DFA二维数组作为有限状态自动机,每次不匹配时都能到达精准位置(对每个不匹配的情况dfa都有记录在案)。

而使用next一维数组时,在每次匹配失败后到达的位置是不能确认的,它只是先到达可能的位置。

从可能的最长前缀位置,进行字符的匹配,如果不匹配再移到下一位可能的位置(下标在模式字符串上往前移)。

②当字符串匹配时

在两种方式中是一样的,i和j都加一,然后进入下一个for循环。

②最坏情况什么时候出现

对于一般方法:如果文本为AAAA,模式串为AAAB,这时匹配到最后一位时失败,j会一步步往前走,这时在搜索方法中操作次数达到了2n,加上构造next数组的n次操作,共3n次操作。

对于完整KMP算法:上面的情况并不会使它达到3n,因为在j一步步往前走的时候i也会往后走,当i达到n时for循环结束,这样最多也就操作n次,加上dfa数组的构造需要n次,共2n次操作。

结果:

可以看到,在通常情况下完整KMP算法的操作次数要比一般算法的操作次数少

即便是在最坏情况下完整KMP算法的操作次数也为一般方法的三分之二。

足以证明完整KMP的性能是更优的。

 

 

四、完整实现及测试代码(java)

 1 public class KMP {
 2     private String pat;
 3     private int dfa[][];
 4 
 5     public KMP(String pat){//由模式字符串构建dfa
 6         this.pat=pat;
 7         int M=pat.length();
 8         int R=256;
 9         dfa=new int[R][M];
10         dfa[pat.charAt(0)][0]=1;
11         for(int X=0,j=1;j<M;j++){//计算dfa[][j]
12             for(int c=0;c<R;c++){//不匹配情况
13                 dfa[c][j]=dfa[c][X];
14             }
15             dfa[pat.charAt(j)][j]=j+1;
16             X=dfa[pat.charAt(j)][X];
17         }
18     }
19 
20     public int search(String txt){
21         int N= txt.length();
22         int M=pat.length();
23         int j,i;
24         for(j=0,i=0;i<N&&j<M;i++){
25             j=dfa[txt.charAt(i)][j];
26         }
27         if(j==M){
28             System.out.println("匹配成功");
29             return i-M;
30         }else {
31             System.out.println("匹配失败");
32             return N;
33         }
34     }
35 }

测试例子:

1     @Test
2     public void KMPTest(){
3         KMP kmp=new KMP("abc");
4         System.out.println(kmp.search("abfeabcabc"));
5

추천

출처www.cnblogs.com/Unicron/p/11746306.html