关于两个字符串的kmp比对算法

关于两个字符串的kmp比对算法

假设有字符串X和Y,知足len(X)>len(Y),要比对这两个字符串。算法

咱们知道,最朴实的方法,就是现将两者对齐,而后依次比对对应位置的字符。若是能匹配到Y最后位置,则匹配成功;若是匹配失败,则将Y右移一位,再从头进行匹配。数组

设字符串X为dababeabafdababcg;字符串Y为ababc。指针

这种比对方法以下所示:code

起始时,两者对其,第一个字符不匹配 :| :dababeabafdababcg :ababc字符串

右移一位,比对位置移动到Y起始位置数学

: |
:dababeabafdababcg
: ababc

连续成功4次,再次遇到不匹配string

:     |
:dababeabafdababcg
: ababc

右移一位,比对位置移动到Y起始位置原理

:  |
:dababeabafdababcg
:  ababc
:

不断重复该过程,直到…………搜索

:               |
:dababeabafdababcg
:           ababc

Y完整匹配到X,结束。循环

毫无疑问,这种方法太笨了。时间复杂度高达O(mn)。最大的问题是,进行了大量的重复比对工做。

kmp算法正是为了解决这一点而提出的。kmp算法的中心思想是,已经匹配的部分不须要再次去匹配,相反,应根据已匹配的部分进行多字节的挪动,加快匹配速度。

怎么作呢?kmp给出的答案是:

已经匹配的部分,能够说是已知的,并且是只取决于字符串Y的,对字符串Y的挪动能够直接挪动到下一个知足匹配的位置。

什么叫下一个知足匹配的位置?

假设字符串X和字符串Y已经匹配的部分为abcab,显然:

: abcab...  : abcab...   :
:  abcab... :   abcab... :

都是不匹配的,只有以下的

:abcab...
:   abcab...

才匹配。显然,新的匹配序列,是旧的匹配序列的一个真后缀,并且仍是字符串Y的一个前缀;而旧的匹配序列也是字符串Y的前缀。

也就是说,根据已经匹配的部分,咱们已经能够排除大量的位置了。kmp算法正是基于这一原理实现的。

规则1
若是当前比对位置两个字符相同,则比对位置右移1位
规则2
若是当前比对位置两个字符不一样,则:

若是没有匹配到序列,则比对位置右移1位,字符串Y右移1位
若是已有匹配到序列,则移动字符串Y到下一个匹配位置

以下例,起始

:|
:dababeabafdababcg
:ababc

字符不匹配,比对位置和字符串Y都右移一位

: |
:dababeabafdababcg
: ababc

字符匹配,比对位置右移1位

:  |
:dababeabafdababcg
: ababc

连续成功匹配4次,再次遇到不匹配

:     |
:dababeabafdababcg
: ababc

字符不匹配,挪动字符串Y到下一个匹配位置

:     |
:dababeabafdababcg
:   ababc

仍旧不匹配,继续挪动字符串Y

:     |
:dababeabafdababcg
:     ababc

仍旧不匹配,已经没有匹配序列了,比对位置和字符串Y都右移一位

:      |
:dababeabafdababcg
:      ababc

字符匹配,比对位置右移1位

:       |
:dababeabafdababcg
:      ababc

连续成功匹配3次,再次遇到不匹配

:         |
:dababeabafdababcg
:      ababc

字符不匹配,挪动字符串Y到下一个匹配位置

:         |
:dababeabafdababcg
:        ababc

仍旧不匹配,继续挪动字符串Y

:         |
:dababeabafdababcg
:         ababc

字符不匹配,无匹配序列,比对位置和字符串Y都右移一位

:          |
:dababeabafdababcg
:          ababc

字符不匹配,无匹配序列,比对位置和字符串Y都右移一位

:           |
:dababeabafdababcg
:           ababc

字符匹配,比对位置右移1位

:            |
:dababeabafdababcg
:           ababc

连续成功匹配5次,到达字符串Y终点,匹配成功

:               |
:dababeabafdababcg
:           ababc

因而可知,kmp算法的比对位置没有倒车的状况,并且同一位置比对的次数也明显少于朴实算法,比对速率是至关快的。

kmp算法的关键,是根据已有匹配序列计算下一个匹配序列,而这一部分,是只须要字符串Y就能够实现的,由于下一匹配序列的计算和已有匹配序列以外的部分,无关。

事实上,根据已有匹配序列,彻底能够获得多个知足要求的下一级匹配序列(即既是已有匹配序列的真后缀,又是字符串Y的前缀),可是毫无疑问,咱们应该选择最长的那个。

kmp算法的关键,也就是对根据已有匹配序列计算下一个匹配序列这一个步骤的计算了,由于咱们知道下一匹配序列必然是字符串Y的前缀,实际上只须要记录下一匹配序列的长度便可。这也就是所谓的next数组的真面目。(其余人介绍的next数组的值可能和我说的不一样,但实际上都是对下一匹配长度进行数学变换的结果)

那么关于next数组的计算,天然也有多种方法,好比很朴实的方法,我就很少说了。事实上,有个很好的方法能够轻松的计算出next数组。这个方法和kmp自己比对的过程有些相似,同时也和后缀树构树的ukkonen算法有殊途同归之妙。

go代码以下:

// 计算既是s[:i]真后缀又是其真前缀的最长(连续)序列的长度。
// 既是真后缀,也是真前缀,称之为(同向)回文序列。
// 长度为零的序列是任意序列的(同向)回文序列。
// n[i]用来表示s[:i]最长(同向)回文序列长度。
func next(s string) []int {    // 已知当前比较位置前的字符都匹配
	n := make([]int, l)        // n[0]、n[1]都是0,从n[2]开始算起
	for i, j := 2, 0; i < l; { // i-1表示s[:i]的后缀的当前比较位置
		if s[i-1] == s[j] {    // j  表示s[:i]的前缀的当前比较位置
			n[i] = j+1         // j+1已是最大的序列长度了
			i, j = i+1, j+1    // 同时移动两个字符串的比较位置
			continue		   // 进入下一比较位置的循环
		}                      // 字符不匹配,最长回文不能延长
		if j == 0 {            // 没有非空的回文序列了
			i++                // n[i]的默认值即为0
			continue           // 从头开始匹配循环
		}                      // 存在非空的回文序列
		j = n[j]               // 找到下一个匹配位置
	}
	return n
}

咱们每一次循环的时候,只进行了一个字符的比对!这是由于以前的比对已经保证前面的部分是匹配的。那么若是这一次比对成功,只须要延长下一次匹配的长度就好了;若是比对失败,咱们也不须要从头开始去查找下一个匹配的起始位置,由于以前的匹配结果已经告诉了咱们下一个匹配的位置。

结果是,kmp算法构造next数组和比对的过程,都很迅速!

kmp算法完整代码以下:

// kmp字符串搜索算法
func KMP(s, r string) int {
   l := len(r)
   // 成员n[i]表示既是s[:i]真后缀又是s前缀的最长序列的长度。
   // 真后缀指非自身的非空后缀。如不存在,则置该成员值为0。
   n := func(s string) []int {
       n := make([]int, l)
       // 从n[2]开始算起
       for i, j := 2, 0; i < l; {
           // 已知前面的字符所有匹配
           if s[i-1] == s[j] {
               j++
               n[i] = j
               i++
               continue
           }
           if j == 0 {
               // 申请的n会所有初始化为零
               i++
               continue
           }
           // 相似后缀指针的功用
           j = n[j]
       }
       return n
   }(r)
   // 进行搜索
   i, j := 0, 0
   for i+l < j+len(s) && j < l {
       if s[i] == r[j] {
           i, j = i+1, j+1
           continue
       }
       if j == 0 {
           i++
       } else {
           j = n[j]
       }
   }
   if j == l {
       return i - l
   }
   return -1
}

kmp算法是一个至关精巧,可是代码却很是简单的算法。相比之下,虽然速度可能逊色于BM算法,可是确实优美得多。

相关文章
相关标签/搜索