考研复试系列——第十节 字符串问题
2017-03-11 11:09
423 查看
考研复试系列——第十节 字符串问题
前言
关于字符串的问题可能是考试题目当中出现次数最多,涉及内容最广的内容了。主要有以下几个方面:字符串的匹配(暴力,KMP,Sunday,DFS等等)。求一个字符串的子串,字符串的反转,字符统计,字符查找。内容很多,但考试并不难,KMP可以不用,后缀树只在ACM中见过。另外别忘记还有C++强大的
STL给我们提供关于string的一系列操作,在本文最后,会总结下STL的string库的使用。
由例题出发
例题一
给你一个文本,里面有各种字符串标点啥的,问你是不是有符合 ab*de(*表示多个任意字符)的匹配。思考:乍一看就是一个正则表达式的问题,那我们实际做时该怎么做呢?想想编译原理的知识,我们可以使用DFA来做,只要清楚它的状态转换图,我们就很容易
完成这道题目了。
首先做出NFA如下图所示:
(其中o表示除d和e的其他字符)
然后再求它的DFA,如下图所示:
注:上图没有验证是否为最简DFA,不过到这里已经可以编程实现了。当然如果化为最简DFA后对于编程实现来说代码会更简洁。
接下来就可以编码实现了:
#include<iostream> #include<fstream> using namespace std; const int state_zero = 0, //定义四个状态 state_one = 1, state_two = 2, state_three = 3, state_four = 4; int now_state = 0;//记录当前状态 void checkStr(char *str) { now_state = 0;//初始化当前状态为0 int length = strlen(str);//计算字符串长度 for(int i=0;i<length;i++) { if(now_state == 0 && str[i] == 'a') now_state = 1; else if(now_state == 1 && str[i] == 'b') now_state = 2; else if(now_state == 2 && str[i] != 'd') now_state = 2; else if(now_state == 2 && str[i] == 'd') now_state = 3; else if(now_state == 3 && str[i] == 'd') now_state = 3; else if(now_state == 3 && str[i] != 'd' && str[i] != 'e') now_state = 2; else if(now_state == 3 && str[i] == 'e') now_state = 4; else if(now_state == 4 && str[i] == 'd') now_state = 3; else if(now_state == 4 && str[i] != 'd') now_state = 2; } } int main() { ifstream file("F:\\shangji\\string.in"); if(!file.is_open())//文件打开失败 exit(1); char buff[100]; while(!file.eof()) { file.getline(buff,100); checkStr(buff); if(now_state == 4) cout<<buff<<" 匹配成功!"<<endl; else cout<<buff<<" 匹配失败!"<<endl; } file.close(); return 0; }
例题二
一道简单的字符串匹配题目:给定两个字符串A和B判断B是否是A的子串。我们考虑暴力搜索的方法,当你想不起来别的方法时,就用它吧,反正复试上机一般没有效率的限制。
#include<iostream> #include<string> using namespace std; int main() { string ss1,ss2; cin>>ss1>>ss2; int leng1 = ss1.length(),leng2 = ss2.length();//假设第一个字符串长 bool isSub = false; int i,j; for(i=0;i<=leng1-leng2;i++) { for(j=0;j<leng2;j++) { if(ss1[i+j] != ss2[j]) break; } if(j == leng2) { isSub = true; break; } } if(isSub) cout<<ss2<<" 是"<<ss1<<" 的子串"<<endl; else cout<<ss2<<" 不是"<<ss1<<" 的子串"<<endl; return 0; }当然也可以考虑一下效率更高的算法,比如我们在数据结构上学过的KMP。对于字符串的查找和这个实现的测率是一样的,其实也是一个字符串的匹配问题。
例题三
输入字符串带空格的分割字符串 (输入字符串,倒序输出,例如:输入 I come from China.输出 China from come I. (单词不需倒序,只是句子倒了)
#include<iostream> using namespace std; char ss[20][100]; int main() { char str[100]; gets(str); int leng = strlen(str); str[leng] = ' ';//把最后也加一个空格,方便统一处理 str[leng+1] = '\0'; cout<<leng<<endl; int j = 0,k = 0; for(int i=0;i<=leng;i++) { char temp[100]; if(!isspace(*(str+i))) { temp[j++] = *(str+i); } else//遇到空格就处理一个子串 { temp[j] = '\0'; j = 0; strcpy(ss[k++],temp);//保存该子串 } } for(j=k-1;j>=0;j--) cout<<ss[j]<<" "; cout<<endl; return 0; }
string类
string作为STL中的一个重要组成部分还是很重要的,尤其是解题时可以带来很大的方便。#include<iostream> #include<string> using namespace std; /* * string类的介绍,更详细的内容可以参照C++ primer */ int main() { //string的构造函数 char s[10] = "cumt csdn";//string str(const char *s) -----用c字符串s初始化 string str1(s); cout<<str1<<endl; string str2(4,'c');//string str(int n,char c) ------用n个字符初始化 cout<<str2<<endl; //字符操作 char temp[10]; str1.copy(temp,4,5);//int copy(char *s,int n,int pos=0)将当前串中以pos开始的n个字符copy到字符数组s中 temp[4] = '\0'; cout<<temp<<endl; //赋值操作 string str3 = str1 + str2; //字符串拼接操作 cout<<str3<<endl; str3.assign(s);//string &assign(const char *s)用c类型字符串s赋值//还有string &assign(const string &s) cout<<str3<<endl; str3.assign(s,4);//string &assign(const char *s,int n)用c字符串开始的n个字符赋值给当前串 cout<<str3<<endl; string str4;//string &assign(const string &s,int start,int n) str4.assign(str1,5,4);//把字符串s中从start开始的n个字符赋值给当前串 cout<<str4<<endl; //连接操作 str4.append(s);//string &append(const char *s)把c类型字符串连接到当前字符串串尾 str4.append(s,4);//string &append(const char *s,int n)把c类型字符串的前n个字符连接到当前字符串串尾 str4.append(str3,1,4);//string &append(const string &s,int pos,int n)把字符串s从pos开始的n个字符连接到当前字符串 //比较操作 /* * 除了使用基本的逻辑符号比较外,还可以使用compare函数 * int compare(const char *s) * int compare(int pos,int n,const char *s) * int compare(int pos,int n,const char *s,int pos2) * 对于string类型同上面是一样的 */ //子串 cout<<str4.substr(4,4)<<endl;//string substr(int pos=0,int n=npos) const;返回pos开始的n个字符构成的字符串 //交换 str4.swap(str3);//void swap(string &s2) 交换当前串与s2的值 //查找 /* * 查找操作的函数非常多,这里只例举下经常用到的 * int find(char c,int pos=0) const; //从pos开始查找字符c在当前字符串中的位置 * int find(const char *s,int pos) const; //从pos开始查找字符串s在当前串中的位置 * int find(const char *s,int pos,int n); //从pos开始查找字符串s的前n个字符在当前串中的位置 * int find(string &s,int pos=0) const; //作用同上 * 注:对于rfind是从后面开始查找,使用和find一样,find_first_of查找第一个满足条件的,还有 * find_last_of最后一个满足条件的,使用起来从语法上一样。 */ return 0; }
下面我们考虑使用string来解决前面的例题三
#include<iostream> #include<string> #include<vector> using namespace std; vector<string> svec; void split(string str,char ch) { str += ch;//拓展字符串,方便后面统一操作 int size = str.length(); int i,pos; for(i=0;i<size;i++) { pos=str.find(ch,i); if(pos<size) { string s=str.substr(i,pos-i); svec.push_back(s); i=pos; } } } int main() { char ss[100]; gets(ss); //在vc6中使用getline直接从控制台读入string需要两次回车 //所以这里先用gets函数读入再进一步转换为string string str(ss); split(ss,' '); int size = svec.size(); for(int i=size-1;i>=0;i--) cout<<svec[i]<<" "; cout<<endl; return 0; }
后缀数组
后缀数组算是字符串处理中一个经典的数据解构了,对于很多问题可以很好的解决,大多数情况下直接使用后缀数组就OK了,不用使用后缀树。所以这里也只说说后缀数组。(本部分内容参考《算法问题实战策略》人民邮电出版社)
例如:哈利波特中的阿拉霍洞开这个词 “ alohomora ” (后缀9个)的后缀数组 A[ ] 如下 :
我们要做的就是生成这个表。代码如下:
#include<iostream> #include<string> #include<algorithm> #include<vector> using namespace std; struct SuffixComparator { const string& s; SuffixComparator(const string &s):s(s) {} bool operator() (int i,int j) { //不用s.substr()函数而使用strcmp()函数,则能够减少生成临时对象的开销 return strcmp(s.c_str() + i,s.c_str() + j) < 0; } }; vector<int> getSuffixArrayNaive(const string& s) { vector<int> perm; for(int i=0;i<s.size();i++) perm.push_back(i); sort(perm.begin(),perm.end(),SuffixComparator(s)); return perm; } int main() { string str = "alohomora"; vector<int> array = getSuffixArrayNaive(str); for(int i=0;i<array.size();i++) cout<<i<<"\t"<<array[i]<<"\t"<<str.substr(array[i])<<endl; return 0; }上面算法的思想很简单,开始时数组初始化为0,1,2,。。。8分别表示后缀(整数表示子串的开始位置),然后使用sort函数依照字典序列进行排序。
输出结果就是上表。
利用后缀树就可以求解多个字符串的最长公共子串,一个字符串中的最长重复子串等问题,在机试中貌似后缀数组的出现频率不高。
相关文章推荐
- 字符串反转系列问题
- 面试100题系列之5字符串的排列组合问题
- 2017考研复试通关一定要解决的8个问题
- SSIS系列之-BT的OLAP数据库连接字符串导致的问题
- 考研复试系列——第十二节 后缀表达式&约瑟夫环
- 考研复试系列——第十一节 map的使用
- [考研系列之数据结构]线性表之字符串
- 字符串系列函数在序列化中的问题
- [每天解决一问题系列 - 0004] Excel 公式中拼接字符串
- 考研复试口语常见问题
- 考研复试系列——第六节 最小生成树
- 字符串系列之相加(发现问题,解决问题)
- 字符串反转系列问题
- 考研复试系列——第二节 最大堆&最小堆
- 深搜:西工大2015年考研复试上机最后一题(连阴雨问题)
- 考研复试系列——第九节 数论基础
- 牛客网算法课系列(一):字符串和二叉树打印问题
- 工作小总结(字符串包含,获取当前页面的url等系列问题)
- 考研复试系列——第五节 并查集
- 2009年考研复试英语复习:精彩问题应答75例句