leetCode28 strStr kmp solution

Reprint address:

Blogger: c_cloud http://www.cnblogs.com/c-cloud/p/3224788.html

reprint

       leetCode28 strStr, the easiest way is to loop comparison and then backtrack, but the efficiency is relatively low, the more suitable and well-known method is to use the kmp method, no need to backtrack, time complexity log(n), Macgic!!

       The following is a blogger wrote, very nice, reprint it:

foreword  

  Although I knew the principle of the kmp algorithm before, that is, to find the maximum same prefix and suffix length k of P 0 ... P i ; but the problem is how to find this maximum prefix and suffix length? I think that many posts on the Internet are not very clear , and I always feel that the layer of paper has not been pierced. Later, I read the introduction to algorithms, although Chapter 32 of string matching talks about the correctness of the calculation of prefixes and suffixes, but a lot of reasoning proves that It is not easy to understand, and it is not combined with the program. Today I will talk about some of my understanding here, I hope you can give me more advice, if there is anything unclear or wrong, please leave a message to me. 

1. The principle of the kmp algorithm:

  This section is reproduced from: http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html

  

String matching is one of the fundamental tasks of computers.

For example, there is a string "BBC ABCDAB ABCDABCDABDE", I want to know, does it contain another string "ABCDABD"?

Many algorithms can accomplish this task, and the Knuth-Morris-Pratt algorithm (KMP for short) is one of the most commonly used. It is named after three inventors, the first K is the famous scientist Donald Knuth.

This algorithm is not easy to understand, there are many explanations online , but it is very laborious to read. I didn't really understand this algorithm until I read Jake Boxer 's article. Below, I use my own language to try to write an easy-to-understand explanation of the KMP algorithm.

1.

First, the first character of the string "BBC ABCDAB ABCDABCDABDE" is compared with the first character of the search term "ABCDABD". Because B does not match A, the search term is shifted back one place.

2.

Because B does not match A, the search term is moved further back.

3.

And so on until the string has one character that is the same as the first character of the search term.

4.

Then compare the string with the next character of the search term, again the same.

5.

Until the string has one character that is not identical to the character corresponding to the search term.

6.

At this time, the most natural reaction is to move the entire search term back one place, and then compare them one by one from the beginning. This works, but it's very inefficient, because you have to move the "search position" to the position that has already been compared, and repeat the comparison.

7.

A basic fact is that when the space doesn't match the D, you actually know that the first six characters are "ABCDAB". The idea of ​​the KMP algorithm is to try to take advantage of this known information and not move the "search position" back to where it has already been compared, but keep moving it backwards, which improves efficiency.

8.

How to do this? A Partial Match Table can be calculated for the search term. How this table is generated will be introduced later, as long as you can use it here.

9.

The first six characters "ABCDAB" are matched when a space is known not to match D. Looking up the table, it can be seen that the "partial matching value" corresponding to the last matching character B is 2, so the number of digits shifted backward is calculated according to the following formula:

  移动位数 = 已匹配的字符数 - 对应的部分匹配值

因为 6 - 2 等于4,所以将搜索词向后移动4位。

10.

因为空格与C不匹配,搜索词还要继续往后移。这时,已匹配的字符数为2("AB"),对应的"部分匹配值"为0。所以,移动位数 = 2 - 0,结果为 2,于是将搜索词向后移2位。

11.

因为空格与A不匹配,继续后移一位。

12.

逐位比较,直到发现C与D不匹配。于是,移动位数 = 6 - 2,继续将搜索词向后移动4位。

13.

逐位比较,直到搜索词的最后一位,发现完全匹配,于是搜索完成。如果还要继续搜索(即找出全部匹配),移动位数 = 7 - 0,再将搜索词向后移动7位,这里就不再重复了。

14.

下面介绍《部分匹配表》是如何产生的。

首先,要了解两个概念:"前缀"和"后缀"。 "前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。

15.

"部分匹配值"就是"前缀"和"后缀"的最长的共有元素的长度。以"ABCDABD"为例,

  - "A"的前缀和后缀都为空集,共有元素的长度为0;

  - "AB"的前缀为[A],后缀为[B],共有元素的长度为0;

  - "ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;

  - "ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;

  - "ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A",长度为1;

  - "ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度为2;

  - "ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。

16.

"部分匹配"的实质是,有时候,字符串头部和尾部会有重复。比如,"ABCDAB"之中有两个"AB",那么它的"部分匹配值"就是2("AB"的长度)。搜索词移动的时候,第一个"AB"向后移动4位(字符串长度-部分匹配值),就可以来到第二个"AB"的位置。

2.next数组的求解思路

  通过上文完全可以对kmp算法的原理有个清晰的了解,那么下一步就是编程实现了,其中最重要的就是如何根据待匹配的模版字符串求出对应每一位的最大相同前后缀的长度。我先给出我的代码:

复制代码
 1 void makeNext(const char P[],int next[])
 2 {
 3     int q,k;//q:模版字符串下标;k:最大前后缀长度
 4     int m = strlen(P);//模版字符串长度
 5     next[0] = 0;//模版字符串的第一个字符的最大前后缀长度为0
 6     for (q = 1,k = 0; q < m; ++q)//for循环,从第二个字符开始,依次计算每一个字符对应的next值
 7     {
 8         while(k > 0 && P[q] != P[k])//递归的求出P[0]···P[q]的最大的相同的前后缀长度k
 9             k = next[k-1];          //不理解没关系看下面的分析,这个while循环是整段代码的精髓所在,确实不好理解  
10         if (P[q] == P[k])//如果相等,那么最大相同前后缀长度加1
11         {
12             k++;
13         }
14         next[q] = k;
15     }
16 } 
复制代码

   现在我着重讲解一下while循环所做的工作:

  1.   已知前一步计算时最大相同的前后缀长度为k(k>0),即P[0]···P[k-1];
  2.   此时比较第k项P[k]与P[q],如图1所示
  3.   如果P[K]等于P[q],那么很简单跳出while循环;
  4.   关键!关键有木有!关键如果不等呢???那么我们应该利用已经得到的next[0]···next[k-1]来求P[0]···P[k-1]这个子串中最大相同前后缀可能有同学要问了——为什么要求P[0]···P[k-1]的最大相同前后缀呢???是啊!为什么呢? 原因在于P[k]已经和P[q]失配了,而且P[q-k] ··· P[q-1]又与P[0] ···P[k-1]相同,看来P[0]···P[k-1]这么长的子串是用不了了,那么我要找个同样也是P[0]打头、P[k-1]结尾的子串即P[0]···P[j-1](j==next[k-1]),看看它的下一项P[j]是否能和P[q]匹配。如图2所示

 

 

附代码:

复制代码
 1 #include<stdio.h>
 2 #include<string.h>
 3 void makeNext(const char P[],int next[])
 4 {
 5     int q,k;
 6     int m = strlen(P);
 7     next[0] = 0;
 8     for (q = 1,k = 0; q < m; ++q)
 9     {
10         while(k > 0 && P[q] != P[k])
11             k = next[k-1];
12         if (P[q] == P[k])
13         {
14             k++;
15         }
16         next[q] = k;
17     }
18 }
19 
20 int kmp(const char T[],const char P[],int next[])
21 {
22     int n,m;
23     int i,q;
24     n = strlen(T);
25     m = strlen(P);
26     makeNext(P,next);
27     for (i = 0,q = 0; i < n; ++i)
28     {
29         while(q > 0 && P[q] != T[i])
30             q = next[q-1];
31         if (P[q] == T[i])
32         {
33             q++;
34         }
35         if (q == m)
36         {
37             printf("Pattern occurs with shift:%d\n",(i-m+1));
38         }
39     }    
40 }
41 
42 int main()
43 {
44     int i;
45     int next[20]={0};
46     char T[] = "ababxbababcadfdsss";
47     char P[] = "abcdabd";
48     printf("%s\n",T);
49     printf("%s\n",P );
50     // makeNext(P,next);
51     kmp(T,P,next);
52     for (i = 0; i < strlen(P); ++i)
53     {
54         printf("%d ",next[i]);
55     }
56     printf("\n");
57 
58     return 0;
59 }
复制代码


Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325584243&siteId=291194637