我反思了一下前两篇的问题,发现讲知识点还是要简明扼要
常见的字符串里的名词
空串:不含任何字符的串,串长度=0
空格串:仅由一个或多个空格组成的串
子串:由串中任意个连续的字符组成的子序列。
主串:包含子串的串。 如:A=’Shenzhen University’ B=’University’ A为主串,B为子串
位置:字符在主串中的序号。子串在主串中的位置以子串第一个字符在主串中的位置来表示。
串相等的条件:当两个串的长度相等且各个对应位置的字符都相等时才相等。
模式匹配:确定子串在主串中首次出现的位置的运算
在串的基本操作中,通常以“串的整体”作为操作对象,如:在串中查找某个子串、在串的某个位置上插入一个子串等。很少以字符为操作单位(否则和线性表没有区别)
最小操作集:
这些操作不可能利用其他串操作来实现,反之,其他串操作(除串清除ClearString和串销毁DestroyString外)可在这个最小操作子集上实现。
怎么理解这个最小操作集?
例如判断串是否为空串的函数和判断串的字符个数(长度)的函数其实可以用同一个函数经过略微修改实现,比如我只需求串长,当长度=0即为空,本质上这两个函数都是求串长,则求串长是这两个操作的最小操作。
右边五种是#include<cstring>中已有的函数。左边五种是我们自己编写的函数,有这十个函数我们可以完成串的操作。
这是字符串的静态表示
而关于字符串的动态表示
首先是堆的概念 。
关于查找子串
int Index(SString S, SString T, int pos) {
// 返回子串T在主串S中第pos个字符之后的位置。若不存在,
// 则函数值为-1。其中,T非空,0≤pos≤S.length-1)。
i = pos; j = 0;
while (i <= s.length-1 && j <= t.length-1) {
if (S.ch[i] == T.ch[j]) { ++i; ++j; } // 继续比较后继字符
else { i = i-j+1; j = 0; } // 指针后退重新开始匹配
}
if (j ==t.length) return i-t.length;
else return -1;
} // Index`
朴素算法,优点是简单直接,缺点是时间复杂度高【最少m+n,最多m*n】
改进算法:KMP算法 —— 解决掉朴素算法的冗余比较
前置:next数组
什么是next数组?怎么求?我们另开一文来讲。机考复习我们直接上程序
本质上,next数组有两个版本,有模式串从j==1开始的,也有从j==0开始的,以上是从1开始,从零开始只需要将上述结果都-1即可。
求好了next数组,我们得以真正书写KMP算法,在上KMP算法之前,请看朴素算法,两者对比学习:
#include<iostream>
#include<string>
using namespace std;
//从pos位置开始,返回子串在主串中的位置
int BF(string M,string T,int pos)
{
int i=pos;
int j=0;
int Mlen=M.length();//主串的范围[0,Slen)
int Tlen=T.length();//子串的范围[0,Tlen)
if(pos<0 && pos>Mlen)
return -1;
while(i<Mlen && j<Tlen)
{
if(M[i]==T[j])
{
++i;
++j;
}
else
{
i=i-j+1;
j=0;
}
}
if(j>=Tlen)
return i-Tlen;
else
return -1;
}
int main()
{
string M="abcdefabcdx";
string T="abcdx";
cout<<BF(M,T,1)<<endl;
return 0;
}
KMP算法正是从朴素算法改进而来:
#include<iostream>
#include<string>
using namespace std;
//返回子串T的next数组。注意数组作为形参是传的指针
int getnext(string T,int *next,int Tlong);
int KMP(string M,string T,int pos)
{
int i=pos;
int j=0;
int Mlen=M.size();//主串的范围[0,Slen)
int Tlen=T.length();//子串的范围[0,Tlen)
int next[255];//定义next数组 new
getnext(T,next,Tlen);//new
if(pos<0 && pos>Mlen)
return -1;
while(i<Mlen && j<Tlen)
{
if(M[i]==T[j])
{
++i;
++j;
}
else
{
j = next[j];//new
}
}
if(j>=Tlen)
return i-Tlen;
else
return -1;
}
int main()
{
string M="abcdefabcdx";
string T="abcdx";
cout<<KMP(M,T,1)<<endl;
return 0;
}
int getnext(string T,int *next,int Tlong){
int i,k;
i=1;
k=0;
next[1]=0;
while(i<Tlong){//T[0]是串T的长度
if(k==0||T[i]==T[k])
{
++i;
++k;
next[i] = k;
}
else
k=next[k];
}
}
不过有点问题。
下面是验证通过版:
#include<iostream>
#include<string>
using namespace std;
int *getnext(string T);
int KMP(string M,string T)
{
int i,j;
int *next = getnext(T);
for(i=0,j=0;i<M.size() && j<(int)T.size();)
{
if(j==-1||M[i]==T[j])
{
++i;
++j;
}
else
{
j = next[j];//new
}
}
delete []next;
if(j==(int)T.size())
return i-j;
else
return -1;
}
int main()
{
string M="abcdefabcdx";
string T="abcdx";
cout<<KMP(M,T)<<endl;
return 0;
}
int *getnext(string T){
int j,k;
int *next = new int [T.size()];
j=0,k=-1;
next[j] = k;
while(j<T.size()){
if(k==-1||T[j]==T[k])
next[++j] = ++k;
else
k = next[k];
}
return next;
}
KMP算法改进先略。