KMP字符串匹配
1. KMP字符串匹配的原理
Knuth-Morris-Pratt算法(简称KMP),是一种非常高效的字符串匹配。设n为文本的长度,m为待查找文本模板的长度,则预处理时间需要O(m),匹配时间需要O(n)。
1.预处理阶段。KMP字符串匹配的原理就是为待查找的字符串模板创建一个前缀函数,该前缀函数得到对于模板每一个子字符串(1~i),若有相同的长度的前缀和相同的长度的后缀的字符串相同的话,返回前缀/后缀的最大长度。可能这个概念说的有点模糊,看一个例子:
例如ababc这个自字符串模板,前缀函数的值是这样计算的:
对于子字符串a,规定在只有一个字符串,该前缀函数返回长度0。
对于子字符串ab,由于任何相同长度的前缀和后缀都不相同,所以前缀函数返回长度0。
对于子字符串aba,由于前缀a和后缀a相同,并且无更长的前缀和后缀相同,故返回长度1。
对于子字符串abab,由于前缀ab和后缀ab相同,并且无更长的前缀和后缀相同,故返回长度2。
对于子字符串ababc,由于任何相同长度的前缀和后缀都不相同,并且无更长的前缀和后缀相同,故返回长度0。
2.查找阶段。遍历文本的每一个字符,
(1).若新扫描进来的字符和模板匹配,则继续扫描(扫描位置后移)。
(2).若新扫描进来的字符和模板不匹配(扫描位置暂时不动)。利用前缀函数在模板中找到一个前缀,从该前缀的后一个位置开始继续和文本匹配。(为什么可以从从该前缀的后一个位置开始?因为该前缀就是之前匹配成功的字符串的后缀,因此可以避免重复扫描已经匹配成功的字符)
不断循环如上两步即可,直到匹配成功的字符个数恰好为待匹配的字符串模板长度则为查找成功,或者遍历文本每一个字符都查找不到则为查找失败。
2. KMP字符串匹配实现(伪代码)
你可以看到计算前缀函数跟匹配函数非常相识,这是因为计算前缀函数其实是待查找的字符串模板跟自身匹配,而匹配函数是待查找的字符串模板跟查找文本匹配。
3. KMP字符串匹配实现(C++代码)
#pragma once
#include <string>
class KMPMatcher
{
public:
KMPMatcher(std::string pattern);
std::string const& GetPattern() const;
void SetPattern(std::string val);
unsigned int Search(std::string str, unsigned int startPos = 1);
private:
std::string m_pattern;
int* m_prefixValue;
};
#include "KMPMatcher.h"
#include <iostream>
std::string const& KMPMatcher::GetPattern() const
{
return m_pattern;
}
void KMPMatcher::SetPattern(std::string val)
{
m_pattern = val;
//计算前缀函数
std::string pattern = val; //这两个变量没有必要,但是可以增加阅读性
std::string text = val; //这两个变量没有必要,但是可以增加阅读性
int currentMatch = 0;
m_prefixValue = new int[val.length()];
m_prefixValue[0] = 0;
for (int i = 2; i != text.length()+1; i++)
{
while (currentMatch > 0 && text[i-1] != pattern[currentMatch])
{
currentMatch = m_prefixValue[currentMatch-1];
}
if (text[i-1] == pattern[currentMatch])
{
++currentMatch;
}
m_prefixValue[i-1] = currentMatch;
}
}
KMPMatcher::KMPMatcher(std::string pattern)
{
SetPattern(pattern);
}
unsigned int KMPMatcher::Search(std::string str, unsigned int startPos /*= 1*/)
{
std::string pattern = m_pattern;
std::string text = str;
int currentMatch = 0;
for (int i = startPos; i != text.length()+1; i++)
{
while (currentMatch > 0 && text[i-1] != pattern[currentMatch])
{
currentMatch = m_prefixValue[currentMatch-1];
}
if (text[i-1] == pattern[currentMatch])
{
++currentMatch;
}
if (currentMatch == pattern.length())
{
return static_cast<unsigned int>(i - pattern.length() + 1);
}
}
return 0; //查找不到
}
//main.cpp
#include "KMPMatcher.h"
#include <iostream>
using namespace std;
int main()
{
KMPMatcher testKMP("ababa");
cout << "查找结果:" << testKMP.Search("abaababa") << endl;
return 0;
}
4. 程序运行结果
查找结果:4