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

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; 
}
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数组的确定:

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

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

该图就是字符串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]!!!

讲到现在大家再回去看代码,是否比一开始更清晰了一点呢?《c++ 数据结构 字符串的自定义类 (文章最后解释了KMP算法)》

    原文作者:KMP算法
    原文地址: https://blog.csdn.net/sinat_34927324/article/details/53790788
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞