[数据结构与算法]BF算法与KMP算法实现

BF算法与KMP算法实现
    BF 算法全称为 Brute Force 算法,是一种普通的字符串匹配算法; KMP 算法全称为 Knuth-Morris-Pratt 算法,是一种改进的字符串匹配算法。两者作用效果一样,区别只在于效率高低的问题。
   假设有主串 S = " abcabcabdabcabc ",子串 T="abd" 。要判断主串S中是否含有子串T,且告知其位置。就是这样的一个问题促使人们发展出这两种算法。
   回到问题,如果用数组保存两个字符串,可以发现S中含有T,判断为真,且位置为下标 6->8 ,即 S[6]==T[0];S[7]==T[1];S[8]==T[2]。即S="abcabcabdabcabc"。
          
  下面使用图片的方式分别对BF算法和KMP算法的流程进行说明。
BF算法
[数据结构与算法]BF算法与KMP算法实现_第1张图片
从主串S的索引0处和子串的索引0处进行匹配,遇到第一个不匹配的位置停下
[数据结构与算法]BF算法与KMP算法实现_第2张图片
从主串S的索引1处和子串T的索引0处进行匹配,遇到第一个不匹配的位置停下
[数据结构与算法]BF算法与KMP算法实现_第3张图片
从主串S的索引2处和子串T的索引0处进行匹配,遇到第一个不匹配的位置停下
[数据结构与算法]BF算法与KMP算法实现_第4张图片
从主串S的索引3处和子串T的索引0处进行匹配,遇到第一个不匹配的位置停下
[数据结构与算法]BF算法与KMP算法实现_第5张图片
从主串S的索引4处和子串T的索引0处进行匹配,遇到第一个不匹配的位置停下
[数据结构与算法]BF算法与KMP算法实现_第6张图片
从主串S的索引5处和子串T的索引0处进行匹配,遇到第一个不匹配的位置停下

从主串S的索引6处和子串T的索引0处进行匹配,直到子串遍历完也没有遇到不匹配的问题,说明匹配成功

        通过这一系列的图,可以很明白的看出,子串T在主串S下移动,同时对比每个相对的元素。如果相同,进行下一位对比;如果不等,即遇到第一个不等情况时,立即将子串T的索引移动到0处,进行下一轮的对比。
        下面作者将完整代码贴出来,仅供参考。
#include
#include
#include
#define S_SIZE 100
#define T_SIZE 50
using namespace std;
void BF(char *S,char *T);
int main()
{
    char S[S_SIZE] = {'\0'};
    char T[T_SIZE] = {'\0'};
    cout<<"请输入主串:"<"<

  KMP算法
      下面说说经过优化而产生的KMP算法,现在教科书大部分已经不在讲解KMP算法了,究其缘由就是其比较难理解。
      KMP算法之所以较之BF算法优化,要先从时间复杂度说起。简而言之,若S长度为m,T长度为n,则BF时间复杂度为O(m×n),而KMP时间复杂度为(m+n),可见一斑。这里关于时间复杂度不做详细说明。
      下面通过简化图进行感性认识一下KMP算法的流程。
[数据结构与算法]BF算法与KMP算法实现_第7张图片
从主串S索引0处和子串T索引0处开始匹配。

[数据结构与算法]BF算法与KMP算法实现_第8张图片
匹配到索引5时,出现读一个不匹配位置,停止。

[数据结构与算法]BF算法与KMP算法实现_第9张图片
直接将T溯回到索引3,而不是溯回到索引0,再进行正常匹配。

      仔细看图,且不必去管其中的Next项的意义,且听作者徐徐道来。
      步骤一中从主串S的索引0处和子串的索引0处开始比对。然后直到在S的索引5和T的索引5处遇到第一个不匹配点索引5处停止。下一步从主串S的索引5处和子串T的索引2处开始。注意,这里子串不是从0开始了,是从2开始进行比对,于是就达到了减少运行时间的目的。
       为什么可以直接从子串T的索引2处进行比对呀?原因就在于子串T的特殊结构。图中T = "aasaay",其中有相同的部分,T[0]==T[1]==T[3]==T[4]。间接地进行了对比。
        有点明白了吗?
        尽可能的使子串的回溯过程减少。通过利用子串的特殊模式或者说是特征来减少子串T的不必要回溯,来尽可能的减少运行时间。而BF算法中的回溯都是统一回溯到索引0处的位置。但KMP算法却可以尽可能的回溯到下标较大的位置来减少不必要的回溯。
       那么究竟要回溯到哪里呢?这里就需要额外的存储空间来记录这个下标位置了。这里称为“模式值”---Next。如果两个字母不相等了,就可以直接移动到Next的值所指的位置处。
     然而,关于Next的值究竟是多少?这是KMP算法的核心内容。
      依然是引用百度百科的条目:
      (1)Next[0]=-1,任何串的第一个字符的模式值规定为-1。
      (2)Next[j],模式串中下标为j的字符,如果与首字母相同,且j的前面的1-k个字符与开           头的1-k个字符不等(或者相等但T[k]==T[j])(1<=k
      (3)Next[j]=k,模式串T中下标为j的字符,如果j的前面k个字符与开头的k个字符相                 等,且T[j]!=T[k](1<=k
      (4)Next[k]=0,除(1)(2)(3)的其他情况。

      依然是看不懂,然而作者自己悟到了一幅图。归结起来就一句话:要求移动到最近的可用位置,能少移动就少移动。请看下图:
[数据结构与算法]BF算法与KMP算法实现_第10张图片
从主串S索引0处和子串T索引0处进行匹配
[数据结构与算法]BF算法与KMP算法实现_第11张图片
遇到第一个不匹配的位置,停止检查
[数据结构与算法]BF算法与KMP算法实现_第12张图片
开始推测最短的回溯距离。回溯一个单位时,从S索引1和T索引0处开始对比,出现第一个不匹配时停止或者索引到4之后停止,即上一步停止的S索引处。
[数据结构与算法]BF算法与KMP算法实现_第13张图片
回溯两个单位时,从S索引2和T的索引0处开始对比,出现第一个不匹配时停止或者索引到4之后停止。
[数据结构与算法]BF算法与KMP算法实现_第14张图片
回溯三个单位时,从S索引3和T的索引0处开始对比,直到S索引4之后仍未出现不匹配,回溯成功!

可能代码写起来有点困难,但还是写出来了。如下是计算模式值Next的函数,也是KMP算法之核心:
int NextOfKMP(char *T,int n,int *Next)
{
    int i;
    if(n == 0)
     {
        Next[n] = -1;
        return 1;
     }
     //开始历遍m为移动偏移量
    for(int m=1;m<=n;m++)
    {
        if(m == n && T[0] != T[n])
        {
            Next[n] = 0;
            return 1;
        }
        if(m == n && T[0] == T[n])
        {
            Next[n] = -1;
            return 1;
        }
        i = 0;//从0遍历
        while((i+m) != n)
        {
            if(T[i] != T[i+m])
                break;
            ++i;
        }
        if(((i+m) == n) && (T[i] != T[n]))
        {
           Next[n] = i;
           return 1;
        }
    }
}

最后,把一个完整的程序贴出来作为本篇博文之结束,同时也希望诸位能够批评指正。
#include
#include
#include
#define S_SIZE 100//主串S长度
#define T_SIZE 50//子串T长度
using namespace std;
int KMP(char *S,char *T);
int NextOfKMP(char *T,int n,int *Next);
int main()
{
    char S[S_SIZE] = {'\0'};
    char T[T_SIZE] = {'\0'};
    cout<<"请输入主串:"<"<



你可能感兴趣的:(数据结构与算法)