字符串匹配之KMP算法
一.简单的字符串匹配
我们经常需要在某一串字符中查找是否存在给定的子串,我们将给定的子串叫做模式,查找子串的问题叫做模式匹配。
进行字符串的模式匹配,最简单的方法就是依次将子串与给定的字符串依次进行比较。算法如下:
int patFind(const char* str, const char* pat){
int pos1,pos2;
bool isfind;
int i=0;
pos1=i;
pos2=0;
while(str[pos1]!='\0'&&pat[pos2]!='\0'){
if(str[pos1++]!=pat[pos2++]){
pos1=++i;
pos2=0;
}
}
if(pat[pos2]=='\0'){
return i;
}
return -1;
}
简单的匹配算法存在回溯现象,复杂度为O(m*n)
二.KMP算法的思想
在匹配的时候经常会碰到回溯,而根据对Pattern的先验计算,能避免回溯,提高匹配效率,KMP算法就是通过对Pattern进行先验计算来避免回溯的算法。
如p0...pi与ts...ts+i匹配,pi+1与ts+i+1不等,这时按照简单的匹配算法,将继续第s+1趟比较比较p0...pi-1...pm与ts+1...ts+i...ts+1+m,如果这两者匹配,那么有
p0...pi-1=ts+1...ts+i=p1...pi
这时,如果已知两者不等,那么就不用做第s+1趟比较了。同理,如果已知p0...pi-2不等于p2...pi那么就不用做第s+2趟比较。
依此类推,直到找到最大的k,使得p0...pk=pi-k...pi,这时可以直接做第s+i-k趟比较,并因为已知p0...pk=pi-k...pi=ts+i-k...ts+i了,直接比较pk+1和ts+i+1即可。
这时我们可以定义一个失效函数f(i),比较至pi,使得pi匹配,pi+1不匹配时,其值等于使得p0...pk=pi-k...pi且小于i的最大k值,如果找不到则值为-1.
https://www.doczj.com/doc/559402858.html,
对于模式pat=abaabcaba,
f(0)为-1
因为p0!=p1, f(1)为-1
因为p0=p2,f(2)为0
因为p0=p3,f(3)为0
因为p0p1=p3p4,f(4)为1
f(5)为-1
f(6)为0
f(7)为1
f(8)为2
如果第s趟比较在第i位失配,则下一趟直接比较pf(i-1)+1和ts+i
三.失效函数的计算
已知f(j-1)=k,则有p0...pk=pj-1-k...pj-1,这时如果pk+1=pj,则f(j)=k+1,如果不等,则继续找f(k),因为p0...pf(k)=pj-1-f(k)...pj-1,这时如果pf(k)+1=pj,则f(j)=f(k)+1,依此类推。我们可以得到下面的方法来计算失效函数。
void failFunc(const char* pat, int* fail){ fail[0]=-1;
int len=strlen(pat);
int k;
for(int j=1;jk=fail[j-1];
while(k>=0&&pat[k+1]!=pat[j]){
k=fail[k];
}
if(pat[k+1]==pat[j]){
fail[j]=k+1;
}
else{
fail[j]=-1;
}
cout<}
}
https://www.doczj.com/doc/559402858.html,
四.KMP模式匹配算法的实现
int kmpFind(const char* str, const char* pat){ int pos1=0,pos2=0;
int patlen=strlen(pat);
int* fail=new int[patlen];
failFunc(pat,fail);
while(str[pos1]!='\0'&&pat[pos2]!='\0'){
if(str[pos1]==pat[pos2]){
pos1++;
pos2++;
}
else if(pos2==0){
pos1++;
}
else{
pos2=fail[pos2-1]+1;
}
}
if(pat[pos2]=='\0'){
return pos1-patlen;
}
return-1;
}
虽然计算失效函数需要浪费一定的时间和空间,可是从代码我们可以看到KMP没有任何的回溯,时间复杂度仅为(m+n),当模式比较长时KMP算法比起简单的模式匹配有显著的优势。
运行结果:
int_tmain(int argc, _TCHAR* argv[])
{
char str[] = "ababaabaabaabcabaaaabbcccc";
char pat[] = "abaabcaba";
cout<cout<return0;
}
各种排序算法的实现
一.基本概念
1.稳定排序与不稳定排序:
对于A,B两个键值相等的对象,且在排序前,A在B之前,如果排序后A肯定还在B之前,则为稳定排序,如果B可能在A之前,为不稳定排序。
2.内排序和外排序:
内排序是指在排序期间数据对象全部存放在内存的排序
外排序是指在排序期间数据对象太多,不能同时存放在内存,必须依照排序过程的要求,不断在内外存之间移动的排序。
二.各种排序算法的实现
1.插入排序
基本思想:插入第i个对象时前i-1个对象已经排好了,将第i个对象插入前i-1个对象的合适地方,使得前i个对象有序。是稳定排序,比较次数和移动次数复杂度都为O(n^2)
void insertSort(int* disorder, int size){
int temp=0,i=0;
for(int j=1;jtemp=disorder[j];
for(i=j;i>0;i--){
if(tempdisorder[i] = disorder[i-1];
}
else{
break;
}
}
disorder[i]=temp;
}
}
2.希尔排序
基本思想:将数列以gap作为间隔分成子序列,分别对子序列进行插入排序,再逐步缩小间隔进行插入排序。shell提出gap取floor(n/2),gap=floor(gap/2).这是一种不稳定的排序。
void shellSort(int* disorder, int size){
int gap=size/2;
int temp=0,i=0;
while(gap>0){
for(int g=0;gfor(int j=g+gap;jtemp=disorder[j];
for(i=j;i>gap-1;){
if(tempdisorder[i] = disorder[i-gap];
i=i-gap;
}
else{
break;
}
}
disorder[i]=temp;
j=j+gap;
}
}
gap=gap/2;
}
}
3.起泡排序
基本思想:依次比较key(n)和key(n-1),直到key(2)和key(1),这样会使最小的对象被起泡到第一个,依次进行起泡,完成排序。是稳定排序,比较次数和移动次数复杂度都为O (n^2)
void bubbleSort(int* disorder, int size){
int temp=0,modified=0;
for(int j=1;jmodified=0;
for(int i=size-1;i>j-1;i--){
if(disorder[i]temp = disorder[i];
disorder[i] = disorder[i-1];
disorder[i-1]=temp;
modified++;
}
}
if(modified==0){
break;
}
}
}
4.快速排序
基本思想:任取一个对象作为基准,按照关键码的大小,将排序队列分为左右两个子序列,小于该对象的都放在左序列,大于该对象的都放在右序列,然后分别对两个子序列重复上述方法,直到所有对象有序排列。是不稳定排序,比较次数和移动次数复杂度都为O(n*log n)
void quickSort1(int* disorder, int start, int end){
if(start>=end){
return;
}
int middle=disorder[start];
int temp=0;
int pivotpos=start+1,i=start+1;
for(;i<=end;i++){
if(disorder[i]if(i!=pivotpos){
temp=disorder[i];
disorder[i]=disorder[pivotpos];
disorder[pivotpos]=temp;
}
pivotpos++;
}
}
disorder[start]=disorder[pivotpos-1];
disorder[pivotpos-1]=middle;
quickSort1(disorder,start,pivotpos-2);
quickSort1(disorder,pivotpos,end);
}
void quickSort(int* disorder, int size){
quickSort1(disorder,0,size-1);
}
5.选择排序
基本思想:每一趟在后面n-i个待排序对象中选出关键码最小的对象,作为有序对象集的第i个对象。不稳定的排序,比较次数为O(n^2),移动次数为O(n)
void selectSort(int* disorder, int size){
int temp=0,small;
for(int j=0;jtemp=disorder[size-1];
small=size-1;
for(int i=size-2;i>j-1;i--){
if(disorder[i]temp=disorder[i];
small=i;
}
}
disorder[small]=disorder[j];
disorder[j]=temp;
}
}
6.锦标赛排序
基本思想:利用胜者树来进行选择排序。稳定的排序,比较次数为O(n*logn).
锦标赛排序虽然节省时间,但是对空间有较大浪费,不推荐
7.堆排序
基本思想:利用最大堆,将得到的最大元素依次调整到最后。不稳定的排序,时间复杂度为O(n*logn).
void filterDown(int* disorder, int pos, int size){
int temppos=pos,temp=0;
while(tempposif(2*temppos+2if(disorder[2*temppos+1]>disorder[2*temppos+2]){
if(disorder[temppos]temp=disorder[temppos];
disorder[temppos]=disorder[2*temppos+1];
disorder[2*temppos+1]=temp;
temppos=2*temppos+1;
}
else{
break;
}
}
else{
if(disorder[temppos]temp=disorder[temppos];
disorder[temppos]=disorder[2*temppos+2];
disorder[2*temppos+2]=temp;
temppos=2*temppos+2;
}
else{
break;
}
}
}
else if(disorder[temppos]temp=disorder[temppos];
disorder[temppos]=disorder[2*temppos+1];
disorder[2*temppos+1]=temp;
temppos=2*temppos+1;
}
else{
break;
}
}
}
void heapSort(int* disorder, int size){
int bottomRowSize=2;
while(bottomRowSizebottomRowSize*=2;
}
int temp=0,i=0;
for(int j=size/2-1;j>=0;j--){
filterDown(disorder, j, size);
}
for(int j=size-1;j>0;j--){
temp=disorder[0];
disorder[0]=disorder[j];
disorder[j]=temp;
filterDown(disorder,0,j-1);
}
}
8.归并排序
基本思想:先两两排序,再逐步进行归并。稳定的排序,时间复杂度为O(n*logn).
void merge(int* source,int*dest, int gap, int size){
int pos1,pos2,end1,end2,destpos=0;
for(int i=0;ipos1=i;
end1=i+gap>size?size:i+gap;
pos2=i+gap;
end2=i+2*gap>size?size:i+2*gap;
while(pos1if(source[pos1]