“看毛片”算法可以说是许多人学习数据结构路上的一道坎,虽说代码并不算难,但总是写了就忘,其算法思想也是难以理解。其实多写几遍就会发现,也就那么回事。
主要思想:
kmp算法主要是通过一个next数组来加速匹配的过程,这个next数组也称为失配数组。现在我们来考虑下面这种情况,母串与匹配串匹配到了如图箭头位置,但此时A和B并不匹配,怎么办呢?注意到图中蓝色部分是相等的子串,因此我们直接移动匹配串让A和C进行比较。这就实现了加速。
上面的直接移动就是利用了next数组,next数组保存的是当匹配串中这个位置失配以后,可以移动到哪个位置继续进行匹配。现在我们来思考一下如何求得next数组。
对于匹配串,首先无需考虑的是0和1位置,当0号位置失配后无法再移动了,只能由母串移动,所以置为-1,1位置失配后自然是重新开始,所以置为0。
我们从位置2开始,设置一个cur变量从0开始,然后依次向后走,并进行匹配,这部分看代码更容易理解,需要注意的是一种可以进行加速的情况。
如下图,对于匹配串的next数组,当我们求到A位置时,由蓝色部分的相等,如果此时B和C相等,那么对于A来说,它的失配移动位置就是B后面的位置。
但当B和C不相等时,情况就有点复杂了,假设B的失配位置为D,由蓝色部分的相等可知此时红色箭头所指的部分也相等,所以我们把B更新为D,然后继续比较D和C是否相等。
代码:
vector<int> GetNext(string& s)
{
int n = s.size();
vector<int> next(n);
next[0] = -1, next[1] = 0;
if (n == 1)
return next;
int pos = 2, cur = 0;
while (pos < n)
{
if (s[cur] == s[pos - 1])
next[pos++] = ++cur;
else if (cur > 0) // 加速情况
cur = next[cur];
else
next[pos++] = 0;
}
return next;
}
int KMP(string& s, string& m)
{
int s_len = s.size();
int m_len = m.size();
// 匹配串大于母串,返回不存在
if (s_len < m_len)
return -1;
// 匹配串为空直接返回0
if ( m_len == 0)
return 0;
int s_index = 0, m_index = 0;
vector<int> next = GetNext(m);
while (s_index < s_len && m_index < m_len)
{
if (s[s_index] == m[m_index])
{
++s_index;
++m_index;
}
else if (next[m_index] == -1)
{
++s_index;
}
else
{
m_index = next[m_index];
}
}
return m_index == m_len ? s_index - m_index : -1;
}
最后解释一下为什么匹配失配后匹配串可以直接向右滑动一段距离,而不用检查中间的位置。因为如果从中间开始能够匹配的话,那么在求next数组时得到的值一定是这个从中间匹配的长度,而不是原来的值,具体动手画下图就知道了。