编程语言
首页 > 编程语言> > 串的基本算法理解

串的基本算法理解

作者:互联网

1.简单模式匹配算法:

主串和模式串进行匹配,如果不匹配,模式串向右移动一位,直到结束。

此时主串从i回溯到模式串向右移动一个位置的位置,也就是移动到i-(模式串已经匹配的字符)+1=i-(j-1)+1=i-j+2的位置。模式串从j回溯到j=1的位置。最好先让主串回溯再让模式串回溯,不然修改了j的值i不好利用j修改之前的值。
代码如下

int index(String S,String T){
    int i=1,j=1;
    while(i<=S.length&&j<=T.length){
        if(S.ch[i]==T.ch[j]){
            ++i;
            ++j;//继续比较后继字符
        }
        else
          i=i-j+2;
          j-1;//指针后退重新匹配
    }
   if(j>T.length)
    return i-T.length;//匹配成功
    else 
    return 0;
}

2.KMP算法

引出:在简单的模式匹配算法中,每一趟失配都是模式串后移一位再重头开始比较。而每趟已经匹配的字符序列是某个前缀,这种频繁的重复操作比较相当于模式串不断地进行自我比较,极其低效。如果已经匹配相等的前缀序列中有某个后缀正好是模式的前缀,那么就可以将模式向右滑动到与这些相等字符对齐的位置,主串指针无需回溯,并从该位置开始继续比较。

当模式串与主串发生不匹配时,模式串的指针回溯到位置k,使得前缀1,2,…k-1=后缀j-k+1…j-1,并且使得前缀后缀相等的长度是最大的,即不存在k’>k,符合该条件。回溯到该位置后,模式串可以滑动到已经匹配的序列的下一个,即不用和已经匹配的序列进行匹配。也就是说,在进行模式匹配时,如果主串和模式串发生不匹配时,模式串的最大匹配前缀的最后一个元素的指针滑动到主串前面一个位置(k-1)。

规定这个k-1就是当前失配的指针j的前的所有的字符串组成的序列中最长的前缀和后缀长,k就是指针j失配时模式串需要移动到的满足j前已经匹配的最大长度的位置。把k所有的可能放在next[]数组中,那么如何确定这个k呢,也就是next[]数组中的任意值。如果手算的话,k就是当前字符j前所有的字符串组成的序列中最长的前缀和后缀长。当主串与模式串第一个元素不匹配时,即前缀=后缀=空集,规定这时候字符串滑动到j=0处,即主串第i个字符和模式串第一个字符前面的空位置对齐,也就是模式串后移一位,这个时候KMP算法退化成了BP算法。

后续的next[]数组怎么求?可以利用前面已经求得的next[]的值,问题就转化成了:

已经求得了模式串在位置j发生不匹配时,需要回退到(已经匹配了最长的前后缀)回退到的位置,求模式串下一个元素不匹配时需要移动到的位置,这时需要找新的最大匹配长度,如果新的最大匹配长度是k,模式串就回退到k+1与主串对齐,也就是主串前k个字符已经和模式串匹配不需要进行比较了。

具体怎么实现呢?前面已经提到了next[]的概念,就是保存主串与模式串失配时模式串需要回溯到的与主串对齐的位置,如果当前主串的位置j与模式串发生不匹配,next[j]=k,那么主串前k-1个元素与模式串相匹配(有k-1个最大匹配长度),不需要比较,模式串直接在第k个位置与主串进行比较。
假设模式串长为length,刚才已经求得了next[1]=0,现在就可以利用这个值依次求得next[2],next[3]…next[length]的值。假设在主串第j个字符不匹配,模式串滑动到next[j]=k的位置。

现在考虑以下情况:
① 如果主串第j个字符匹配,也就是模式串(滑动到k的位置)前面k-1个位置已经匹配,即最大匹配长度为k,那么主串j+1发生不匹配时,模式串滑动到第k+1处。
② 如果主串第j个字符失配,那么需要重新找主串与模式串的最大匹配长度,就可以利用next[]数组中next[k]前面的值,就是利用已经求得的最大匹配长度。如果不匹配,需要重新在模式串找到最大匹配长度,也就是找已经找到最大匹配长度的j-k+1…j-1(匹配序列是1…k-1),加上j失配时,以1…j重新计算的最大匹配长度,可以直接利用模式串失配元素的next值找出新的串的最大匹配长度k’,最佳情况,模式串再滑动到k’处与主串对齐,此时主串前k’-1个元素与模式串均相等,不用比较。如果此时主串的j与k’不相等,需要利用k’找出更小的最大匹配长度k’’,此时再判断k’’与j是否相等(1…k‘‘-1已经和主串匹配不需要比较)。最差情况,如果到k’’(length-1个’)=1还是k’’(length-1个‘)≠j,此时k’’(length个)=0,也就是找不到任何的匹配长度了,KMP算法退化为了BP算法,模式串需右移一位,递归回来,也就是k’=0,即next[j+1]=0。
现在基本上理解了,其实求next数组本质就是利用前面已经求得的next[]值求得后面一个next[]得值。这里规定next[1]=0(主串第一个不匹配退化到BP算法,模式串右移一位,或者与模式串第一个字符前空位置对齐),可以利用next[1]依次求得2…length的next[]。
KMP算法就是基于next[],在发生不匹配时模式串不需要重头匹配,而是模式串回退到相应的next[]处与主串对齐,此时模式串next[]前的字符必定已经和主串匹配上。
代码怎么实现呢?求next[]的代码如下:

void get_next(String T,int next[]){
    int i=1,j=0;
    next[1]=0;
    while(i<T.length){
        if(j==0||T.ch[i]==T.ch[j]){
            ++i;
            ++j;
            next[i]=j;//若pi=pj,则next[j+1]=next[j]+1
        }
        else 
        j=next[j];//否则另j=next[j],继续
    }
}

本质就是模式串自己和自己匹配的过程,详细得说就是模式串已经匹配了的最长匹配前缀找下一个最长匹配前缀的过程。令next[1]=0,利用循环依次求next[2]…next[length]的值, i从1开始,j从0开始是为了利用前面已经求得的next[1]值求以后后继所有的next[]值。i存放的是需要求的next[]的位置,所以从1开始,j存放了i-1的next值,从0开始就是为了满足符合定义的next[1]=0,可以理解为模式串和模式串含一个空字符进行匹配的过程。那么i既然这么说为什么不从2开始呢,next[1]不是已经求得了吗?这是因为程序可以利用声明的next[1]=0的特性,在j=0时也能处理i++,j++,对模式串需要滑动到i与主串对齐的位置的下一个位置的next值,也就是模式串需要滑动到的next值与i+1对齐,此时不需要考虑滑动到的next值与i+1是否对齐,因为当前模式串和主串需要匹配的只有第一个字符,固然是相等的。一般情况,以后每次求的i+1的next[],都需要判断当前主串i的字符与模式串滑动过来的位置j的字符是否相等,若相等,则最大匹配长度为j,也就是模式串需要滑动到第j+1与主串对齐,也就是next[i+1]=j+1。如果不相等,根据前面的推理,
next[i+1]=next[next[j]]+1,若还不相等,next[i+1]=next[next[next[j]]]+1,还不相等,依次类推,直到next[i+1]=next[1]+1=1。这里就是利用if…else解决这个问题,判断i与j对应字符是否匹配,若匹配,next[i+1]=j+1,若失配,则重新利用已经匹配的1…j-1找更小的前缀并滑动到i(i+1前面的不用比较),此时,若更小匹配前缀的下一个字符能与i+1匹配,模式串需要滑动到与主串对齐的位置就是j=next[j]+1,如果失配则需要找更小的匹配前缀,直到匹配前缀为空字符,而让模式串右移。j=0可以和刚才说的判断操作形成或语句从而不用考虑主串开始和模式串的空字符串匹配的过程,因为模式串的空字符串肯定和主串不会匹配,那么肯定时需要模式串右移一位,而主串的下一个元素的前面的序列只有一个字符肯定和模式串移动到下一个字符(第一个字符)相等。依次从头往后进行就肯定能够求出从next[2]…next[length]的值。
现在求next值的函数搞清楚了,接下来说KMP实现的函数,就简单了
代码如下:

int Index_KMP(String P,String T,int next[]){
    int i=1,j=1;
    while(i<=S.length&&j<=length){
        if(j==0||S.ch[i]==T.ch[i]){
            ++i;
            ++j;//继续比较后继字符
        }
        else
        j=next[j];//模式串向右移动
    }
if(j>T.length)
return i-T.length;//匹配成功
 else 
    return 0;
}

主串与模式串从头进行匹配,匹配时肯定不能超过主串和模式串的串长,可以利用一个循环从主串、模式串的串头串尾依次进行匹配,若失配,模式串回退到next[]值的位置(next[]值前的元素全部已经匹配),知道模式串从头到最后均能与主串匹配的上,此时模式串向右移动刚好超过模式串的串长,故可以利用这个特性来判断匹配成功。若主串和模式串匹配的过程主串都已经到头了模式串还是没有匹配成功,那么模式串肯定还没有移动到超过串长的位置,那么这种情况就是失配了。

**3.改进的KMP算法:

**
就算是KMP算法,当主串和模式串有大量相同的字符时,如果主串与模式串发生不匹配,那么模式串移动后相同的字符肯定还是不会匹配,但是KMP算法还是会继续判断。
这个时候可以修改next[]为nextval[],让主串直接与相同的字符的一个(由next[]原本需与主串匹配的最后一次出现的相同的字符串)
所以nextval[]值可以在求next[]的函数中进行,可以开始认为nextvalue[]就是next[]。
不过如果当前的next[]值的元素(还没确定当前的next[],只是假设)与之前的next值的元素相同,把nextval[]值修改为前面相等元素的next[]值,否则则当成next[]的求法依次进行。
现在来贴求nextval[]的函数,本质就是求next[]的函数,只是循环在前面next[]的字符和当前预赋的next[]值对应元素判断是否相等,上面已经说过。循环从头到尾就可以得到一个nextval[]。代码如下:

void get_nextval(String T,int nextval[]){
    int i=1,j=0;
    nextval[1]=0;
    while(i<T.length){
        if(j==0||T.ch[i]==T.ch[j]){
            ++i;
            ++j;
            if(T.ch[i]!=T.ch[j])nextval[i]=j;
            else 
            nextval[i]=nextval[j];
        }
        else 
        j=nextval[j];
    }
}

可以看出就是在将要把当前的i+1的next[]赋值j+1前,判断前一个的next[]值对应的元素的值是否与当前的元素相等,如果相等就把i+1的nextval更新成为这个next值。如果不相等当成正常的next[]进行求解。

4.总结:

①串的模式匹配还需要看串的定义是从1开始还是从0开始的,以前的算法都是基于从1开始的,若是从0开始,需要额外减去1,利用‘/0’进行判串的最大长度。②从1开始也可以用0来存储串的长度,不过存储空间只有char类型的空间大小,即只能存储0-255的串长。③在题目中还是用手算的方法求next[],nextval[]数组。

标签:基本,主串,匹配,字符,nextval,模式,next,算法,理解
来源: https://blog.csdn.net/qq_44076176/article/details/117739230