KMP字符串匹配算法及next前缀数组的应用

KMP算法通常是我们学习字符串匹配算法时遇见的第一个算法,另外还有Rabin-Karp, Sunday算法等. 相对于其他字符串匹配算法, kmp在字符串中字符重复率低的情况下并不具备优势,那为什么KMP算法会作为经典的教学算法呢?原因是KMP算法充分利用next前缀数组的信息来优化算法,能解决很多字符串相关问题.

首先上KMP字符串匹配算法,关于KMP算法的详细介绍可以参考从头到尾彻底理解KMP,KMP算法leetcode在线测试地址

```C++
class Solution {
public:
    int strStr(string haystack, string needle) {
	return kmpsearch(haystack, needle);
    }

    vector<int> getnext(const string& needle){
	    vector<int> next(needle.size(), 0);
	    for(int i = 1; i < needle.size(); i++){
		    int j = next[i-1];
		    while(j != 0 && needle[j] != needle[i]) {
			    j = next[j-1];
		    }
    		next[i] = j += (needle[j] == needle[i]);
    	}
    	return next;
    }

    int kmpsearch(string& haystack, string& needle) {
	if(needle.size() == 0) return 0;
    	vector<int> next = getnext(needle);
	int i = 0, j = 0;
	while(i < haystack.size() && j < needle.size()) {
	    while(j != 0 && haystack[i] != needle[j]) {
	        j = next[j-1];
	    }
	    j += haystack[i] == needle[j];
	    i++;
	}
    	return j == needle.size() ? i - needle.size(): -1;
    }
};
```

KMP算法中前缀数组(即next数组)的含义为:对于P = p0 p1 ...pj-1 pj,寻找模式串P中长度最大且相等的以p0开始的前缀和以pj结束的后缀.采用平摊分析可得求next数组的时间复杂度的O(N).

添加字符生成最短回文字符串

我们先来看leetcode上的214题:
题目描述:
给定一个字符串S, 通过在其前面添加字符使其成为回文字符串, 输出最短的回文串
示例: aacecaaa -> aaacecaaa ; abcd -> dcbabcd

这道题很容易想到首先求出以第一个字符开始的最长的回文字符串的长度,然后将剩余部分添加在前面即为最短的所求回文串,于是问题转化为如何求出以首字符开始的最长回文串长度.

考虑将字符串逆转,问题转化成求原串的以首字符开始的前缀和逆串以末字符结束的后缀的长度相等且最大的值,这个描述根next数组基本就一致了.

于是,解决方法为:
将字符串S逆转后添加到原字符串末尾(为了避免字符串内部干扰中间添加#字符, 如S="aaaa")成为字符串T, 如abcd->abcd#dcba, 求T的next数组,next[T.size()-1]即为S以第一个字符开始的最长的回文字符串的长度,从而原问题得解.

代码如下:

```C++
class Solution {
public:
    string shortestPalindrome(string s) {
	string rs = s;
	reverse(rs.begin(), rs.end());
	string l = s + "#" + rs;
	vector<int> next(l.size(), 0);
	for(int i = 1; i < next.size(); i++){
	    int j = next[i-1];
	    while(j > 0 && l[i] != l[j])
	        j = next[j-1];
	    next[i] = (j += l[i] == l[j]);
	}
	return rs.substr(0,rs.size()-next[l.size()-1]) + s;
    }
};
```

最长重复子串

题目描述:
给定字符串S,找出其中最长的重复出现的子串
示例: abcdabef->ab; aaaaaa->aaa

思路:如果重复子串从手字符开始,求出next数组后找出最大值即可, 那么不从首字符开始呢, 我们从其开始计算next数组找出最大值即可.

代码如下:

```C++
class Solution {
public:
	string longestRepeatString(const string& s) {
		int maxl = 0, start = 0;
		vector<int> next(s.size(), 0);
		printvec(next);
		for(int i = 0; i < s.size(); i++) {
			int mi = 0;
			next[i] = 0;
			for(int j = i+1; j < s.size(); j++) {
				int k = next[j-1];
				while(k > 0  && s[j] != s[k+i]){
					k = next[k-1];
				}
				next[j] = (k += s[j] == s[k+i]);
				mi = max(mi, next[j]);
			}
			printvec(next);
			if(mi > maxl) {
				start = i;
				maxl = mi;
			}
		}
		return s.substr(start, maxl);
	}
};
```
posted @ 2015-06-25 20:27  scaiz  阅读(284)  评论(0)    收藏  举报