Next数组的实现步骤与代码,以及三个简单应用(包含KMP)

        简单介绍一下什么是next数组,毕竟网上有很多懂意思就行,本文的重点在代码解释与三个简单面试例题上。next数组就是记录str中每个位置前缀与后缀的匹配长度(不包含该位置自身)。例如:str="ababac",那么它的next数组就是[-1,0,0,1,2,3];第3个字符‘b’的前缀是‘a’,后缀也是‘a’,长度为1,则next[3]=1。最后一个字符‘c’的前缀就是‘aba’,后缀肯定和前缀相同也是‘aba’,长度为3,next[5]=3。前两位是人为规定的,分别为-1与0。在实际应用的时候,根据情况要多求一位next值,比如此时next[6]就等于0,待会下面例题有应用。

    如何求next数组?你可以暴力求解,每个等长的前缀与后缀都进行比较,当然时间复杂度肯定很高O(n^2)。

    这里简单介绍一下O(n)的方法。精炼下来就是:

第一个和第二个位置分别人为规定为-1,0。

第i(i>1)个位置的next值求解方式为:将(i-1)字符前缀的下一个字符与(i-1)位置的字符比较,相同的话next值较前一位+1;不等的话,递归查询已经部分生成的next数组,cn=next[cn],这里cn就是上次前缀的位置,直到字符相同或者cn变为0、-1为止。cn变为0、-1的时候,该位置的next值就为0。cn有两个含义:该位置1、前缀的长度 2、next值。

求next数组的具体代码如下:

int *NextArray(int next[],char str2[])
{
    next[0]=-1;//默认-1
    next[1]=0;//默认0
    int cn=0;//前缀的长度(前缀下一个字符的下标)以及记录next数组的值
    int i=2;//str2 位标从2开始
    while(i<strlen(str2))//next数组正常与str2长度相同,但根据不同题目有的要多求一位
    {
        if(str2[i-1]==str2[cn]){
            next[i++]=++cn;//相同的话next值较前一位+1
        }
        else if(cn>0){
            cn=next[cn];//不等的话,递归查询已经部分生成的next数组,确定较小前缀长度
        }else{
            next[i++]=0;//cn变为0、-1的时候,该位置的next值就为0
        }
    }
    return next;
}

应用实例1:KMP

有一个文本串str1,和一个模式串str2,现在要查找str2在str1中首次出现的位置。

暴力方法,依次挨个匹配,那么最差时间复杂度为O(m*n)。例如 aaaaaaab 与 aab

代码如下:

int main()
{
    char a[]="aaaaaaab";
    char b[]="aab";
    int na=strlen(a);
    int nb=strlen(b);
    for(int i=0;i<na;i++){
        int k=i;
        bool flag=true;
        for(int j=0;j<nb;j++){
            if(a[k]==b[j])
            {
                k++;
            }
            else{
                flag=false;
                break;
            }
        }
        if(flag){
            cout<<i;
            break;
        }
    }
}

KMP方法是首先也是挨个匹配,相同的时候,i++,j++。(i为str1的计位器,j为str2的计位器);出现不同值的时候,如果此时匹配的是str2[0],那str1的下标计数器i直接++,继续与str2[0]比较。如果不是str2[0],那么j=next[j],再继续比较str[i]与str[j]。

代码如下:

//得到str1中首次与str2相同的第一个下标位置
int getIndex(char str1[],char str2[])
{
    int *next=new int[strlen(str2)];//new一个next数组,长度与str2相同
    NextArray(next,str2);//获得next数组
    int i=0;//str1下标
    int j=0;//str2下标
    while(i<strlen(str1)&&j<strlen(str2))
    {//两结束条件1、str1到末尾都没有匹配成功 2、str2走至末尾说明成功
        if(str1[i]==str2[j]){
            i++;j++;
        }else if (next[j]==-1) {
     //等于-1的时候说明i位置以及之前没有任何与str2有能匹配的,next数组的首位,此时j必等于0
     //i++与str2重新开始从头匹配比较
            i++;
        }else {
            j=next[j];
        }
    }
    return i-j;
}

应用实例2

已知一个原始串str1,在其后面添加字符,生成str2,使其变成2个原始串且每个串的开头不同,要求添加的字符越少越好,求str2。例如:原始串str1为aaba,那么str2为aabaaba,添加aba,生成了两个原始串。str1=abcabc,那么str2=abcabcabc。

思路:为了添加的字符最少,即要求公共子序列的部分要尽可能大。公共部分即为原串末位再后一位的最长前缀。这里提及到本文开始提到的多求一位next值。str1=aaba,其next数组为[-1,0,1,0,1],多求了一位。next[4]=1,那么说明可以复用的最长前缀长度为1,原字符串去掉第0个位置的剩下的即为要添加的部分‘’aba‘’。str1=abcabc,next数组为[-1,0,0,0,1,2,3],next[6]=3,则原字符串前三位为公共部分,将剩余部分添加。

代码如下:

next数组多求一位即将int *NextArray(int next[],char str2[])中while循环条件加个=号,其余不变,代码改为:

while(i<=strlen(str2))

char *getnewstr(char str1[])
{
    int *next=new int[strlen(str1)+1];//生成的next数组长度+1
    NextArray(next,str1);
    int l=next[strlen(str1)];//l为末位前缀、最长公共子序列
    char *str2=new char[2*strlen(str1)-l];
    for(int i=0;i<strlen(str1);i++)
    {
        str2[i]=str1[i];
    }
    for(int i=strlen(str1);i<strlen(str2);i++)
    {
        str2[i]=str1[l];
        l++;
    }
    return str2;
}
应用实例3

判断一个字符串是否由一个子串重复获得,即str1=n*str2.例如:abcabcabc,就是由子串abc重复得到。

思路:本题同样先求n+1位的next数组。例如abcabcabc,其next数组为[-1,0,0,0,1,2,3,4,5,6],可以发现9-next[9]=3。再例如aabaaabaaaba,由aaba重复得到,其next数组为[-1,0,1,0,1,2,2,3,4,5,6,7,8],可以发现12-next[12]=4。即length-next[length]的结果如果能被length整除,则含有重复子串。

这是为什么呢?

如果字符串是由某个子串n倍重复构成的,那么其末位的前缀必然是n-1个子串的长度,用总长度减去n-1个子串的长度就是单个子串的长度。此长度必然能够被总长度整除。

如果字符串不是由某个子串n倍重复构成的,假设其有子串,其末位的前缀长度必定小于一个子串的长度,用总长度减去末位的前缀,这个长度肯定远大于一个子串的长度,即使整个字符串是由两个字符串组成的,也还是大于一个子串的长度。此时用总长度除以这个长度,肯定不能整除。

代码如下:

char *getstr(char str1[])
{
    int *next=new int[strlen(str1)+1];
    NextArray(next,str1);
    int l=strlen(str1)-next[strlen(str1)];
    if(strlen(str1)%l==0) {
        char *str2=new char[l];
        for(int i=0;i<l;i++)
        {
            str2[i]=str1[i];
        }
        return str2;
    }else {
        return 0;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值