攻克数据结构和算法——第三天:串

74 阅读6分钟

目录

一,串的基本操作

1,串的定义:

2,串的基本操作:

1)插入

2)删除

3)连接

4)找子串

5)串替换

二,串的模式匹配

1,BF模式匹配算法(简单匹配算法)

2,KMP匹配算法

3,KMP算法相比于简单模式匹配算法的改进之处是什么?

4,next值的计算思想:

 5,为什么模式串的next值和主串无关?

6,

7,

一,串的基本操作

串是一种特殊的线性表

特殊性在于:线性表的数据元素限定为字符集

一般来说,计算机硬件结构反映处理数值计算需要,而计算机上非数值处理的对象,大多就是字符串数据

1,串的定义:

是由零个或多个字符组成的有限序列。

空串:是指长度n=0的串,它不包含任何字符。


空格串:是仅由一个或多个空格组成的串,长度大于等于1。


子串:串中任意个连续的字符组成的子序列。


主串:包含子串的串相应地称为主串。


位置:字符在序列中的序号。子串在主串中的位置则以子串的第一个字符在主串中的位置来表示。


相等:两个串的长度相等,并且对应位置的字符都相等。

串和线性表的逻辑结构极为相似,区别为 ,串的数据对象约束为字符集。

但是在操作时却有着很大的差别,一般来说,线性表一般将单个数据作为处理对象,而串一般将串整体或者串的一部分作为操作对象。

2,串的基本操作:

1)插入

StrInsert(S, pos, T)
初始条件:串S和T存在, 1 <= pos <= StrLength(S)+1。 
操作结果:在串S的下标为pos的字符之前插入串T。

2)删除

StrDelete(S, pos, len)
初始条件:串S存在, 1 <= pos <= Length(S)
且O <= len <= Length(S)- pos +1。
操作结果:从串S中删除下标为pos的字符起长度为len的子串。

3)连接

StrCat(S,T)
初始条件:串S和T存在。
操作结果:返回由S和T联接而成的新串。

4)找子串

StrIndex(S, pos, T)
初始条件:主串S和T存在,T是非空串
操作结果:若主串S中存在和串T值相同的子串,则返回它在主串
S中从第pos个字符开始第一次出现的位置;否则函数值为0。

5)串替换

StrReplace(S, T, V)
初始条件:串S,T和V均已存在, 且T是非空串。
操作结果:用V替换主串S中出现的所有与(模式串) T相等的不重叠的子串。

二,串的模式匹配

​编辑

 串的模式匹配(串匹配):找子串在主串中从第pos个字符后首次出现的位置(字串的定位操作)

  ​编辑

 主串:目标串

子串:模式串

1,BF模式匹配算法(简单匹配算法)

主串S中的子串与模式串T进行比较,直到找到相同的子串为止。
如果存在相同的子串,则匹配成功,返回子串在主串S中的位置pos
否则匹配不成功。
子串与模式的比较策略:从前到后依次进行比较。
​编辑

 ​编辑

 ​编辑

 代码实现:

int Index(SStringS, int pos, SString T)
{ int i=pos, j=1;

    while(i <= S.len && j <=T.len)
    { 
        if(S.ch[i] == T.ch[jl)
        {     ++i;
          ++j;
        }

        else
        { 
            i= i-j+2; 
            j= 1;
        }
    }   
        if(j> T.len)
            return i- T.len;
        else
            return 0; 
    }
}

时间复杂度:o(m*n)

2,KMP匹配算法

KMP算法: KMP为(D.E.Knuth 与J.H.Morris和V.R.Pratt)同时发现的算法,因此人们称之为克努特-莫里斯-普拉特算法。
特点:在匹配的过程中,不需要回溯主串的指针i,且时间复杂度可以达到O(m+n)

在主串中设置指示器i表示主串S中当前比较的字符;在模式串T中设置指示器j表示模式串T中当前比
较的字符。

从主串的第pos个字符起和模式串的第一个字符比较

相等:继续逐个比较后续字符i++; j++;

j==0:继续逐个比较后续字符

j==0表示当前比较的是模式串首字符且不匹配应从主串后继字符起从头比较

不相等:从主串的位置不改变和模式串的第next[j]字符比较        j=next[j];

int Index_ KMP(SString S, int pos, SString T)
{ int i= pos, j=1;
    while (i <= S.len &&j <=T.len)
    {
        if(j==0 || S.ch[i] == T.ch[j])
        {
            ++i; 
            ++j;
        }

        else
            j = next[j];
    if (j > T.len)
        return i- T.len;
    else
        return 0;
}

3,KMP算法相比于简单模式匹配算法的改进之处是什么?

KMP算法的改进之处是它对子串做了一定的处理,使得每次匹配失败后,下一次匹配开始的位置尽可能的向后移。

KMP算法中,每当一趟匹配过程中出现失配时,主串S中的i指针不需要回溯,而是利用已经得到的“部分匹配”结果,将模式串向右“滑动”尽可能远的一段距离后,继续进行比较,从而快速达到匹配结果。

我们要在已获得的信息中做到不遗漏的同时尽可能多的四配---->A子串的后缀集合与B子串的前缀集合的交集里:最长的那个元素----->使B串后移的最少且在已知信息中四配的最多

最长元素的长度:就是j指针回溯的位置

A子串的后缀集合与B子串的前缀集合的交集里---->B子串的前缀集合与它本身的后缀集合的交集

j指针回溯的位置=B子串的最长公共前后缀

通过隐藏信息:匹配失败时A串与B串存在着一段相同的子串

j指针回溯的位置只与B串有关,与A串无关

在A、B字符串匹配之前就通过B串计算回溯位置存在一个数组next里
next与B串形成映射数组存的数据next[i]就是B[1]~B[i]的最长公共前后缀的长度

求next值

B串自己与自己匹配B[1]~B[i]的前缀与它的后缀匹配后缀为主串前缀为模式串
递推的方式求出next数组
目的:对于i属于[2,m]求出B[1]~B[i]的最长公共前后缀的长度
1.如果匹配:
next[j]=j+1 , j是B串前缀的指针
也就是当前字符匹配之前的最长公共前后缀的长度这里匹配成功了,要+1
2。如果不匹配,回溯j指针,j=next[j]直到匹配成功


KMP一次比较只会产生两种结果
1.匹配成功主串的指针向前移动
2.四配败模式串整体向前移动9
前面的移动必为n次,后面的移动最多n次
所以最大的时间复杂度为2n
同理也可得
next数组构建最大时间复杂度为2m

4,next值的计算思想:

由模式串本身发现。
​编辑

 5,为什么模式串的next值和主串无关?

每当一趟匹配过程中出现字符比较不相等时,不需要回溯主串的 i指针,而是利用已经得到的“部分匹配”的结果将模式子串向右“滑动”尽可能远的一段距离后,继续进行比较。如果 ok,那么主串的指示指针不回溯!算法的时间复杂度只和子串有关

6,

next值的计算仅和模式串本身有关

next[ ]是对模式串p进行预处理后的查询表

预处理也就是在模式p和目标进行匹配操作之前进行的操作对p而言,它并不关心要匹配的堤什么样子的,它首先要做的是很好的了解自己,也就是对自身模式的了解

​编辑

 这实际上也是一个匹配的过程,
不同在于:主串和模式串是同一个串

代码实现:

void Get_ Next(SString T, int next[])
{ int j=1, k =0;
    next[1]=0;
    while (j < T.len )
        { if(k==0 || T.ch[j] == T.ch[k] )
            {     ++j;
                ++k;
                next[j]=k;
            }
          else
                k = next[k]; 
    }
}

7,

当Si和Tj比较后,发现两者不相等时,但Tj和Tk相等,那就意味着Si和Tk不需要进行额外的比较了,因此j位置的nextval值仍然时k,即nextval[j] = next[j]。

void Get_ Nextval(SString T, int next[ ],int nextval[ ])
{
    int j= 2, k=0;

    Get_ Next(T, next);
    nextval[1]=0;
    next[i]=0;
    while(j <= T.len )
    {
        k=next[j];
        if( T.ch[j] == T.ch[k] )
            nextval[j]=nextval[k];
        else
            nextval[j =next[ij] ;
        j++;
    }
}

模式串的长度m比主串的长度n要小很多,且计算next或nextva1函数的时间复杂度为0(m)。因此,对于整个匹配算法来说,所增加的计算next或nextval是值得的。