考研复试系列——第十节 字符串问题

19 篇文章 0 订阅
12 篇文章 5 订阅

考研复试系列——第十节 字符串问题

前言


关于字符串的问题可能是考试题目当中出现次数最多,涉及内容最广的内容了。主要有以下几个方面:字符串的匹配(暴力,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函数依照字典序列进行排序。
输出结果就是上表。
利用后缀树就可以求解多个字符串的最长公共子串,一个字符串中的最长重复子串等问题,在机试中貌似后缀数组的出现频率不高。








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值