您的位置:首页 > 理论基础 > 数据结构算法

c++ 数据结构 字符串的自定义类 (文章最后解释了KMP算法)

2016-12-21 20:18 399 查看
c++语言提供了一个string.h类,提供了许多操作字符串的函数,为程序员编写有关文字处理的应用,给予了极大的方便。但在许多更复杂的应用中,程序员一般会定义新的string类,加入更丰富的操作,使得程序编写更为简洁,功能更为强大。

下面我们给出自定义字符串类及其操作的实现:

1.类声明:

# include<iostream>
# include<string.h>
# define defaultSize 128
using namespace std;
class AString{
private:
char *ch; //字符串数组指针
int curLength; //字符串的实际长度,不包括空字符
int maxSize; //字符串可容纳的字符个数,包括空字符
int *next; //next数组:用于fastFind函数来避免回溯
public:
AString(int sz=defaultSize); //构造函数:构造一个实际长度为0的空字符串
AString(const char *init); //串构造函数:根据字符串init构造字符串
AString(const AString& ob); //复制构造函数
~AString(){delete[]ch;} //析构函数
int Length()const{return curLength;} //返回字符串的实际长度
AString& operator()(int pos,int len); //取子串
int operator==(AString& ob)const { return strcmp(ch,ob.ch)==0;} //判断两字符串是否相等
int operator!=(AString& ob)const {return strcmp(ch,ob.ch)!=0;}
int operator !()const {return curLength==0;} //判断空字符串
AString& operator=(AString& ob); //赋值运算符重载
AString& operator +=(AString& ob); //把串ob连在调用函数的字符串后面
char& operator [](int i); //取第i个字符
int Find(AString& pat,int k)const; //朴素的模式匹配:在调用该函数的目标串中找第一次出现子串pat的位置
int fastFind(AString& pat,int k,int next[])const; //快速匹配
int* getNext(AString& pat); //获取子串的next数组:用来指定当子串第下表为j的元素失配后,子串指针应该退回的位置
};2.操作的实现:
# include"AString.h"
# include<iostream>
# include<assert.h>
# include<string.h>
using namespace std;
AString::AString(int sz){ //构造最多可容纳sz个非空字符的空字符串
maxSize=sz+1;
ch=new char[maxSize];
assert(ch!=NULL);
curLength=0;
ch[0]='/0';
}
AString::AString(const char *init){ //串构造函数
int len=strlen(init); //判定字符数组可容纳的字符个数应为多少
maxSize=(len>defaultSize)?len+1:defaultSize+1;
ch=new char[maxSize];
assert(ch!=NULL);
strcpy(ch,init); //字符串复制
curLength=len;
4000

}
AString::AString(const AString& ob){ //复制构造函数
curLength=ob.curLength;
maxSize=ob.maxSize;
ch=new char[maxSize];
assert(ch!=NULL);
strcpy(ch,ob.ch);
}
AString& AString::operator()(int pos,int len){ //从位置pos开始,连续取len个字符
AString temp; //子串temp
if(pos<0||pos>curLength-1||len<0||pos+len-1>maxSize-1){ //起始位置pos不能小于0,不能大于curLength-1,c长度len不能小于零,要取得最后一个字符的位置pos+len-1要<=maxSize-1
temp.curLength=0; //子串不存在,干脆返回一个空串
temp.ch[0]='/0';
}
else{
if(pos+len-1>curLength-1) //确定子串的实际长度
len=curLength-pos;
for(int i=0,j=pos;i<len;i++,j++)
temp.ch[i]=ch[j];
temp.ch[len]='/0';
}
return temp;
}
AString& AString::operator=(AString& ob){ //赋值运算符重载
if(this!=&ob){ //不是字符串自身赋值
delete[]ch;
ch=new char[ob.maxSize];
assert(ch!=NULL);
curLength=ob.curLength;
maxSize=ob.maxSize;
strcpy(ch,ob.ch);
}
else
cout<<"字符串自身赋值出错!\n";
return *this;
}
AString& AString::operator +=(AString& ob){ //连接ob串在调用串后面
char *temp=ch;
int n=curLength+ob.curLength;
int m=(maxSize>n)?maxSize:n+1;
ch=new char[m];
assert(ch!=NULL);
strcpy(ch,temp);
strcat(ch,ob.ch);
delete[]temp;
return *this;
}
char& AString::operator [](int i){ //取第i个字符
if(i<1||i>curLength){
cout<<"字符串下标超界!/n";
exit(1);
}
return ch[i-1];
}
int AString::Find(AString& pat,int k)const{ //朴素的模式匹配
int i,j;
for(i=k;i<=curLength-pat.curLength;i++) //从规定的位置k开始直到最后一个目标串中可能存在子串的位置
for(j=0;j<pat.curLength;j++) //子串与目标串元素依次进行比较
if(ch[i+j]!=ch[j]) break;
//相等,接着进行内循环比较下一位置;不相等,跳到外循环从目标串下一个可能存在子串的位置从头比较
if(j==pat.curLength) //找到
return i;
else //没找到
return -1;
}
int AString::fastFind(AString& pat,int k,int next[])const{ //快速匹配
int posP=0; //子串指针
int posT=k; //目标串指针
int lengthP=pat.curLength;
int lengthT=curLength;
while(posP<lengthP&&posT<lengthT){
if(posP==-1||pat.ch[posP]==ch[posT]){ //相等,接着比较下个位置,如果pat.ch[0]!=ch[k],next[0]=-1;
posP++;
posT++;
}
else //不相等,目标串指针仍指向失配的位置,子串指针回溯到next数组指定的位置
posP=next[posP];
}
if(posP==lengthP) return posT-pat.curLength; //在目标串中找到子串
else return -1; //未找到
}
int* AString::getNext(AString& pat){ //确定next数组
pat.next=new int [pat.curLength];
next[0]=-1; //初始值
int j=0;
int k=-1;
while(j<pat.curLength-1){ //假设next[j]及之前的数组元素已经确定,来确定next[j+1]
if(k==-1||pat.ch[j]==pat.ch[k]){
if(pat.ch[j+1]==pat.ch[k+1]){
next[++j]=next[++k];
}
else{
next[++j]=++k;
}
}
else{
k=next[k];
}
}
return next;
}
............................................分割线
关于next数组的确定,我们来说几点:

朴素的匹配算法:在目标串和子串进行比较的过程中,如果有字符不相等,目标串指针需要跳到k+1的位置(k为该趟比较目标串开始比较的地方),而子串指针需要跳到位置0,然后重新开始比较。

而快速匹配目标指针则不需要回溯,只需要在失配的位置不动,然后子串的指针跳回到next[j]的位置,j为子串该次失配的位置。 然后两串继续比较。 

该算法的关键就是next数组的确定:

下面我们来看一个图:(本图转载自糖小喵的微博)



该图就是字符串pat,我们用归纳法确定next数组,即假设next[j]及以前的数组元素都已经确定,然后根据之前的元素来确定next[j+1]。

先解释一下该图

(1)假设第j个位置失配,子串指针需要回溯到next[j]位置,该位置就是上图的k,如果k位置失配,子串指针就要回溯到next[k]位置,同理如果绿色位置失配,子串指针回溯到黄色区域位置。

(2)寻找位置k的本质就是找失配字符之前的那段子串的最大的前后缀相同子串。

(3)j位置失配,字串指针回溯到k,说明A1=A2;同理B1=B2,C1=C2; 通过几何关系,不难看出B1=B2=B3,C1=C2=C3=C4;

因此,假设子串在j+1位置失配,会发生两种情况:

(1) pat.ch[k]==pat.ch[j],显然next[j+1]==k+1,用代码来写就是next[++j]=++k;

(2) pat.ch[k]!=pat.ch[j],子串指针只好回溯到next[k]的位置,用代码来写就是k=next[k];

重复进行一二步。

细心的朋友可能会发现在第一步,如果pat.ch[k]==pat.ch[j],貌似k+1就是子串应该回溯到的位置,但如果pat.ch[k+1]==pat.ch[j+1]的话,子串与目标串仍然是失配的,但是我们知道next[k+1],所以next[j+1]应该回溯到的位置就是next[k+1]!!!

讲到现在大家再回去看代码,是否比一开始更清晰了一点呢?
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: