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

来源:互联网 发布:php curl 大文件 编辑:程序博客网 时间:2024/04/30 22:13

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

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

1.类声明:

# include<iostream># include<string.h># define defaultSize 128using 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; }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;         //子串tempif(pos<0||pos>curLength-1||len<0||pos+len-1>maxSize-1){ //起始位置pos不能小于0,不能大于curLength-1,c长度len不能小于零,要取得最后一个字符的位置pos+len-1要<=maxSize-1temp.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);}elsecout<<"字符串自身赋值出错!\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]!!!

讲到现在大家再回去看代码,是否比一开始更清晰了一点呢?大笑



0 0