字符串匹配(KMP 算法 含代码) 有关字符串的基本知识 传统的串匹配法 模式匹配的一种改进算法(KMP算法)

主要是针对字符串的匹配算法进行解说



串(string或字符串)是由零个或多个字符组成的有限序列,一般记为字符串匹配(KMP 算法 含代码)
有关字符串的基本知识
传统的串匹配法
模式匹配的一种改进算法(KMP算法) 当中s是串的名,用单引號括起来的字符序列是串的值;ai(1<=i<=n)能够是字母、数值或其它字符。串中字符的数组 n称为串的长度。零个字符的串称为空串,它的长度为0

串中随意个连续的字符组成的子序列称为该串的子串。

包括子串的串相应的称为主串。

通常称字符在序列中的序号为该字符在串中的位置。子串在主串中的位置则以子串的第一个字符在主串中的位置来表示。

以下主要说一下串的模式匹配算法

传统的串匹配法

算法的基本思想是:从主串S的第pos个字符起和模式的第一个字符比較,若相等,则继续逐个比較兴许字符;否则从主串的下一个字符起再又一次和模式的字符比較。依次类推,直至模式T中的每一个字符依次和主串S中的一个连续的字符序列相等,则匹配成功,函数值为和模式T中第一个字符相等的字符在主串S中的序号,否则称匹配不成功。函数值为零。
此算法在最坏情况下的时间复杂度为O(m*n)

模式匹配的一种改进算法(KMP算法)

网上一比較易懂的解说

字符串匹配的KMP算法

字符串匹配是计算机的基本任务之中的一个。
举例来说,有一个字符串”BBC ABCDAB ABCDABCDABDE”,我想知道,里面是否包括还有一个字符串”ABCDABD”?

很多算法能够完毕这个任务,Knuth-Morris-Pratt算法(简称KMP)是最经常使用的之中的一个。它以三个发明者命名,起头的那个K就是著名科学家Donald Knuth。

以下,我用自己的语言,试图写一篇比較好懂的KMP算法解释。
1.字符串匹配(KMP 算法 含代码)
有关字符串的基本知识
传统的串匹配法
模式匹配的一种改进算法(KMP算法)

首先,字符串”BBC ABCDAB ABCDABCDABDE”的第一个字符与搜索词”ABCDABD”的第一个字符,进行比較。由于B与A不匹配。所以搜索词后移一位。


2.
字符串匹配(KMP 算法 含代码)
有关字符串的基本知识
传统的串匹配法
模式匹配的一种改进算法(KMP算法)
由于B与A不匹配。搜索词再往后移。


3.字符串匹配(KMP 算法 含代码)
有关字符串的基本知识
传统的串匹配法
模式匹配的一种改进算法(KMP算法)

就这样,直到字符串有一个字符,与搜索词的第一个字符同样为止。
4.字符串匹配(KMP 算法 含代码)
有关字符串的基本知识
传统的串匹配法
模式匹配的一种改进算法(KMP算法)

接着比較字符串和搜索词的下一个字符,还是同样。
5.字符串匹配(KMP 算法 含代码)
有关字符串的基本知识
传统的串匹配法
模式匹配的一种改进算法(KMP算法)

直到字符串有一个字符,与搜索词相应的字符不同样为止。
6.
字符串匹配(KMP 算法 含代码)
有关字符串的基本知识
传统的串匹配法
模式匹配的一种改进算法(KMP算法)
这时,最自然的反应是,将搜索词整个后移一位,再从头逐个比較。

这样做尽管可行,可是效率非常差,由于你要把”搜索位置”移到已经比較过的位置,重比一遍。


7.
字符串匹配(KMP 算法 含代码)
有关字符串的基本知识
传统的串匹配法
模式匹配的一种改进算法(KMP算法)
一个基本事实是,当空格与D不匹配时,你事实上知道前面六个字符是”ABCDAB”。KMP算法的想法是,设法利用这个已知信息。不要把”搜索位置”移回已经比較过的位置,继续把它向后移,这样就提高了效率。
8.
字符串匹配(KMP 算法 含代码)
有关字符串的基本知识
传统的串匹配法
模式匹配的一种改进算法(KMP算法)
怎么做到这一点呢?能够针对搜索词,算出一张《部分匹配表》(Partial Match Table)。这张表是怎样产生的,后面再介绍,这里仅仅要会用就能够了。
9.
字符串匹配(KMP 算法 含代码)
有关字符串的基本知识
传统的串匹配法
模式匹配的一种改进算法(KMP算法)
已知空格与D不匹配时,前面六个字符”ABCDAB”是匹配的。查表可知。最后一个匹配字符B相应的”部分匹配值”为2,因此依照以下的公式算出向后移动的位数:
  移动位数 = 已匹配的字符数 - 相应的部分匹配值
由于 6 - 2 等于4。所以将搜索词向后移动4位。


10.
字符串匹配(KMP 算法 含代码)
有关字符串的基本知识
传统的串匹配法
模式匹配的一种改进算法(KMP算法)
由于空格与C不匹配,搜索词还要继续往后移。这时,已匹配的字符数为2(”AB”),相应的”部分匹配值”为0。所以。移动位数 = 2 - 0,结果为 2。于是将搜索词向后移2位。
11.
字符串匹配(KMP 算法 含代码)
有关字符串的基本知识
传统的串匹配法
模式匹配的一种改进算法(KMP算法)
由于空格与A不匹配,继续后移一位。
12.
字符串匹配(KMP 算法 含代码)
有关字符串的基本知识
传统的串匹配法
模式匹配的一种改进算法(KMP算法)
逐位比較,直到发现C与D不匹配。于是。移动位数 = 6 - 2,继续将搜索词向后移动4位。
13.
字符串匹配(KMP 算法 含代码)
有关字符串的基本知识
传统的串匹配法
模式匹配的一种改进算法(KMP算法)
逐位比較。直到搜索词的最后一位,发现全然匹配。于是搜索完毕。假设还要继续搜索(即找出所有匹配)。移动位数 = 7 - 0。再将搜索词向后移动7位。这里就不再反复了。


14.
字符串匹配(KMP 算法 含代码)
有关字符串的基本知识
传统的串匹配法
模式匹配的一种改进算法(KMP算法)
以下介绍《部分匹配表》是怎样产生的。
首先,要了解两个概念:”前缀”和”后缀”。 “前缀”指除了最后一个字符以外。一个字符串的所有头部组合;”后缀”指除了第一个字符以外。一个字符串的所有尾部组合。
15.
字符串匹配(KMP 算法 含代码)
有关字符串的基本知识
传统的串匹配法
模式匹配的一种改进算法(KMP算法)
“部分匹配值”就是”前缀”和”后缀”的最长的共同拥有元素的长度。以”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.
字符串匹配(KMP 算法 含代码)
有关字符串的基本知识
传统的串匹配法
模式匹配的一种改进算法(KMP算法)
“部分匹配”的实质是,有时候。字符串头部和尾部会有反复。

比方,”ABCDAB”之中有两个”AB”,那么它的”部分匹配值”就是2(”AB”的长度)。搜索词移动的时候,第一个”AB”向后移动4位(字符串长度-部分匹配值),就能够来到第二个”AB”的位置。


小样例

求字符串 ‘ababaabab’的next和nextval,结果例如以下

j 1 2 3 4 5 6 7 8 9
s[ j ] a b a b a a b a b
next 0 1 1 2 3 4 2 3 4
nextval 0 1 0 1 0 4 1 0 1

(1)计算next

计算next的时候须要遵循的规则例如以下

A、 j = 0 next[j] = 0
B、假设存在 Max{ k | k 属于 (1,j-1) 且‘P1…Pk-1’= ‘Pj-k+1…Pj-1’ } (next[j] = k)
C、其它情况 next[j] = 1

j ( 1, k-1 ) str(1,k-1) ( j-k+1 , j-1 ) str( j-k+1, j-1) 规则 next[ j ]
2 NULL NULL NULL NULL C 1
3 (1,1) a (2,2) b C 1
4 (1,2) ab (2,3) ba
4 (1,1) a (3,3) a B 2
5 (1,3) aba (2,4) bab
5 (1,2) ab (3,4) ab B 3
6 (1,4) abab (2,5) baba
6 (1,3) aba (3,5) aba B 4
7 (1,5) ababa (2,6) babaa
7 (1,4) abab (3,6) abaa
7 (1,3) aba (4,6) baa
7 (1,2) ab (5,6) aa
7 (1,1) a (6,6) a B 2
8 (1,6) ababaa (2,7) babaab
8 (1,5) ababa (3,7) abaab
8 (1,4) abab (4,7) baab
8 (1,3) aba (5,7) aab
8 (1,2) ab (6,7) ab B 3
9 (1,7) ababaab (2,8) babaaba
9 (1,6) ababaa (3,8) abaaba
9 (1,5) ababa (4,8) baaba
9 (1,4) abab (5,8) aaba
9 (1,3) aba (6,8) aba B 4

(2)计算nextval

nextval[i]的求解须要比較s中next[i]所在位置的字符是否与s[i]的字符一致。假设一致则用s[next[i]]的nextval的值作为nextval[i]。假设不一致,则用next[i]做为nextval[i]。

i next[i] s[i] s[ next[i] ] yes/no nextval[i]
1 0 a NULL no next[i] = 0
2 1 b a no next[i] = 1
3 1 a a yes s[ next[i] ]的nextval = 0
4 2 b b yes s[2]nextval = 1
5 3 a a yes s[2]nextval = 0
6 4 a b no next[6] = 4
7 2 b b yes s[2] nextval = 1
8 3 a a yes s[3] nextval = 0
9 4 b b yes s[4]nextval = 1

代码

该代码參考 数据结构(C语言版) 严蔚敏 吴伟民 编著。。。

/**
 * @filename      kmp.cc
 * @Synopsis      KMP algorithm 
 * @author       XIU
 * @version      1
 * @date         2016-04-21
 */
// 此代码中所用的数组,或者是字符串都是从下标1開始

#include<iostream>
#include<string.h>

using namespace std;

/* ============================================================================*/
/**
 * @Synopsis      the next index data of the model string s_mode
 *
 * @Param         s_mode: the string
 * @Param         next  : the next array
 * @Param         len   : the length of the string
 */
/* ============================================================================*/
void get_next( string s_mode, int *next, int len )
{
    int i = 1;
    int j = 0;
    next[1] = 0;

    //cout << len << endl;
    while( i<len )
    {

        if( j==0 || s_mode[i] == s_mode[j] )
        {
            ++i;
            ++j;
            if( i>len ) break;
            next[i] = j;
            if( j>len ) break;
            //以下的是修正的next算法(nextval)。
            /*
            if( s_mode[i] != s_mode[j]) 
                next[i] = j;
            else next[i] = next[j];
            */
        }
        else
        {
            j = next[j];
        }
    }
    for( int i=1; i<len; i++ )
    {
        cout << next[i] << " ";
    }

    cout << endl; 

}

/* ============================================================================*/
/**
 * @Synopsis      利用模式串s_mode中的next函数求s_model在主串 s_primary中第pos个字符之后的位置
 *
 * @Param         s_primary
 * @Param         s_mode
 * @Param         pos
 * @Param         next
 *
 * @Returns       
 */
/* ============================================================================*/
int Index_KMP( string s_primary, string s_mode, int pos, int *next )
{
    int i = pos;
    int j = 1;

    int len_p = s_primary.size();
    int len_m = s_mode.size();

    while( i < len_p && j < len_m )
    {

        if( j == 0 || s_primary[i] == s_mode[j] )
        {
            ++i;
            ++j;
        }
        else
            j = next[j];
    }

    if( j >= len_m )
        return i - len_m;
    else
        return 0;
}

/* ============================================================================*/
/**
 * @Synopsis      output function to check the result
 *
 * @Param         s_primary
 * @Param         s_mode
 * @Param         len
 * @Param         next
 * @Param         index
 */
/* ============================================================================*/
void output( string s_primary, string s_mode, int len, int *next, int index )
{
    cout << "s_primary = " << s_primary << endl;
    cout << "s_mode = " << s_mode << endl;

    for( int i=1; i<len; i++ )
    {
        cout << next[i] << " ";
    }

    cout << endl; 

    cout << "index = " << index << endl;

}
int main()
{
    string s_primary = " acabaabaabcacaabc";
    string s_mode    = " abaabcac";
    int len = s_mode.size() ;

    int *next = new int[len];

    get_next( s_mode, next, len );

    int tmp = Index_KMP( s_primary, s_mode, 1, next );

    output( s_primary, s_mode, len, next, tmp );


    delete [] next;
    return 0;
}

參考网址
【1】字符串匹配的KMP算法 - 阮一峰的网络日志
http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html
【2】KMP算法具体解释 - joylnwang的专栏 - 博客频道 - ****.NET
http://blog.****.net/joylnwang/article/details/6778316
【3】经典算法研究系列:六、教你初步了解KMP算法、updated - 结构之法 算法之道 - 博客频道 - ****.NET
http://blog.****.net/v_JULY_v/article/details/6111565