多益一道机试题目,共有3道,这应该算是最难的一道了。题目如下:
现有字符串str1和str2,如果str1通过移位,可以包含str2,那么就说str1包含str2,否则不包含。要求尽可能高效。
输入:str1和str2。
输入:如果包含则输出yes,否则输出no。
例如:ABCDEF包含FA(移位后包含,例如可以变为FABCDE),而ABCDEF不包含FFA。
我想这道题目应该主要想考察KMP或BM算法,虽然了解,但是当时不能上网,我也不能保证可以写对,就直接暴力求解了。多益给的开发环境是vs2005,这样的匹配查找也有对应的库函数,可是记不住,vs2005提示的库函数中,我有没找到。
思路分析:一起见到的题目都是字符串匹配,没有移位。加上移位,需要变通一下,最简单的是暴力求解,每移位一次,匹配一次,直到穷尽移位。
移位过程中,字母左右两边挨着的字母不变(认为第一个和最后一个是挨着的),而字符串匹配,匹配过程中也是挨个匹配。这样想的话,我们可以把母串首位相接,这就包含了所有移位的情况。如果母串首位相接(母串=母串+母串)后包含子串,那么就包含子串(前提是母串长度不小于子串,首位相接会使母串长度加倍)。
解法1:用C++库函数
先看一下这个库函数
int find(const string &s, int pos = 0) const;//从pos开始查找字符串s在当前串中的位置,查找失败返回-1。
#include<iostream> #include<string> using namespace std; int main() { string str1; string str2; cin>>str1>>str2; if(str1.length()>=str2.length())//母串不能短于子串 { str1=str1+str1; int pos=str1.find(str2); if(pos!=-1) cout<<"yes"<<endl; else cout<<"no"<<endl; return 0; } cout<<"no"<<endl; return 0; }解法2:KMP算法。
我想多益考察的应该不是怎么用这个库函数吧!“尽可能高效”也提示我们要用高效算法。下面给出KMP算法实现。
其中KMP算法参考http://blog.csdn.net/v_JULY_v/article/details/6545192
#include<iostream> #include<string> using namespace std; //求KMP的Next数组 void GetNext(const char *p,int *Next) { int len=strlen(p); int i=0; int j=-1; Next[i]=j; while(i<len-1) { if(j==-1||p[i]==p[j]) { i++; j++; if(p[i]!=p[j])//不允许出现P[i]==P[next[i]] Next[i]=j; else Next[i]=Next[j]; } else { j=Next[j]; } } } //S为母串,p为匹配子串,如果匹配返回匹配位置,否则返回-1 int KMPSearch(const char *s,const char *p) { int Slen=strlen(s); int Plen=strlen(p); int *Next=new int[Plen];//Next数组存储位置 GetNext(p,Next);//求得Next数组 int i=0;//在S串中的下标 int j=0;//在P串中的下标 while(i<=Slen-Plen&&j<Plen) { if(j==-1||s[i]==p[j]) { i++; j++; } else j=Next[j]; } delete[] Next; if(j==Plen) return i-Plen; else return -1; } int main() { string str1; string str2; cin>>str1>>str2; if(str1.length()>=str2.length())//母串不能短于子串 { str1=str1+str1; const char *s1=str1.c_str(); const char *s2=str2.c_str(); int pos=KMPSearch(s1,s2); if(pos!=-1) cout<<"yes"<<pos<<endl; else cout<<"no"<<endl; return 0; } cout<<"no"<<endl; return 0; }
BM匹配是从后向前搜索匹配,匹配失败,前进的距离依靠坏字符和好后缀。
坏字符相对比较简单,如果不包含坏字符,则移动length距离,否则坏字符与匹配串最右边的对应字符对其。
初始化坏字符移动距离如下:
/* p为匹配子串,BmBc为坏字符集 */ void GetBmBc(const char *p,int BmBc[]) { int len=strlen(p); for(int i=0;i<256;i++)//初始化坏字符集 BmBc[i]=len; for(int i=0;i<len;i++) BmBc[*(p+i)]=len-i-1; }
BM好后缀需要的是ZBox的变形,ZBox是从左向右计算,而BM好后缀需要从后向前(从右向左)。因此在计算ZBox前要先反转匹配子串,计算的ZBox值在使用前也要反转。
计算ZBox代码如下:
/* ZBox匹配算法 p为字符串,len为字符串长度,ZBox[]为存储的值 */ void GetZBox(const char *p,int len,int ZBox[]) { ZBox[0]=len; int left=0; int right=0; for(int i=1;i<len;i++) { //如果i在ZBox匹配字符串的范围内 if(i<right) { int k=i-left; if(ZBox[k]<right-i+1) ZBox[i]=ZBox[k]; else { //否则接着前面的继续匹配 int j=right+1; while(j<len&&p[j]==p[j-i]) j++; //更新left和right left=i; right=j-1; ZBox[i]=right-left+1; } } //i不在ZBox匹配范围内,之内重新匹配了,不能利用之前的值了 else { int j=0; while(p[j]==p[i+j]&&i+j<len) { j++; } //更新left和right left=i; right=i+j-1; ZBox[i]=right-left+1; } } }计算好ZBox后可以计算好后缀了,好后缀计算分三种情况,参考http://blog.csdn.net/sealyao/article/details/4568167#
但是作者写的代码不全,完整版如下:
/* p匹配子串,BmGc好后缀移动距离存储,ZBox存储ZBox的原始值 */ void GetBmGc(const char *p,int BmGc[], int ZBox[]) { int len=strlen(p); //先把ZBox反转 int start=0; int end=len-1; while(start<end) { int temp=ZBox[start]; ZBox[start]=ZBox[end]; ZBox[end]=temp; start++; end--; } for(int i=0; i<len; i++)//第一种情况 BmGc[i]=len; int j=0; for(int i=0; i<len; i++)//第二种情况 if(ZBox[i]==i+1) for(;j<=len-i-1; j++) BmGc[j]=len-1-i; for(int i=0; i<=len-2; i++)//第三种情况 BmGc[len-1-ZBox[i]]=len-1-i; }