求最长回文子串的四种算法

问题描述:

输入一个字符串求出其中最长的回文子串,在判断时,应该忽略大小写,但输出应该保持原样。输入字符不超过5000,输出回文长度和回文字符串。

样例输入:Confuciuss say : Madam,I'm Adam

样例输出:Madam,I'm Adam

主要给出四种算法解答,1:循环比较,2:扩展比较,3、动态规划,4、Manacher算法

为了介绍Manacher算法,我们规定,直接输入由小写字母组成的串来处理。本质上是一样的。

1、循环比较

# include <stdio.h>
# include <string.h>
# include <ctype.h> 
//最长回文子串 
# define MAXN 5000 + 10
char buf[MAXN],s[MAXN]; //buf存原字符串,s存过滤掉非字母字符的字符串
int p[MAXN];  //记录字母在原串中的位置,待找到回文串后输出。 
//暴力法 
int main(){
	
	int n,m=0,max=0; //max存回文长度
	int i,j,k;
	int x,y;   //记录回文串的上下标 
	
	fgets(buf,sizeof(s),stdin); //推荐使用fgets输入
	n=strlen(buf);
	//将所有的字母符号都转变成大写,便于比较 
	for(i=0;i<n;i++){
		if(isalpha(buf[i])){
			p[m]=i; 
			s[m++] = toupper(buf[i]);
		} 
	}
	
	//利用除去其他字符的数组s判断回文
	for(i=0;i<m;i++){
		for(j=i;j<m;j++){
			int ok=1; //默认i <-> j是回文
			//判断从i到j是否为回文 
			for(k=i;k<=j;k++){//找对称节点的公式,i+j等价于中心节点的2倍,对称轴公式,a点关于b点的对称节点,2*b-a
				if(s[k] != s[i+j-k])
					ok=0; 
			} 
			//如果是回文更新max的值,max保存最长的回文串 
			if(ok==1 && j-i+1 > max){
				x=p[i];
				y=p[j];
				max= j - i + 1;
			}
		}
	} 
	printf("max = %d\n",max);
	for(int t=x;t<=wegwaegy;t++){
		printf("%c",buf[t]);
	}
	printf("\n");
	return 0;
}

2、扩展比较

# include <stdio.h>
# include <string.h>
# include <ctype.h> 
//最长回文子串 

# define MAXN 5000 + 10
char buf[MAXN],s[MAXN];
int p[MAXN];  //记录字母在原串中的位置,待找到回文串后输出。 
//中心扩展法 
int main(){
	
	int n,m=0,max=0;
	int i,j,k;
	int x,y;   //记录回文串的上下标 
	
	fgets(buf,sizeof(s),stdin);
	n=strlen(buf);
	//将所有的字母符号都转变成大写,便于比较 
	for(i=0;i<n;i++){
		if(isalpha(buf[i])){
			p[m]=i; 
			s[m++] = toupper(buf[i]);
		} 
	}
	
	for(i=0;i<m;i++){//判断以i节点为中心的串是不是回文串 
		//奇数回文判断 aba 
		for(j = 0; i-j >= 0 && i+j<m; j++){
			if(s[i-j] != s[i+j])//以i为节点分别向两遍扩展j个单位。什么时候结束判断?就是for循环给出的条件。 
				break;
			if(j*2+1 > max){
				max=2*j+1;
				x=p[i-j];
				y=p[i+j];
			} 
		}
		//偶数回文判断abba 
		for(j=0;i-j>=0 && i+j+1<m;j++){
			//以i和i+1为中心,向两边扩展。 
			if(s[i-j] != s[i+1 + j])
				break;
			if(j*2+2 > max){//更新max 
				max=j*2+2;
				x=p[i-j];
				y=p[i+j+1];
			}
		}
	} 

	printf("max = %d\n",max);
	for(int t=x;t<=y;t++){
		printf("%c",buf[t]);
	}
	printf("\n");
	return 0;
}

3、动态规划

# include <stdio.h>
# include <string.h>
# include <ctype.h> 
//最长回文子串 

# define MAXN 5000 + 10
char buf[MAXN],s[MAXN];
int p[MAXN];  //记录字母在原串中的位置,待找到回文串后输出。 
int dp[MAXN][MAXN]; //保存 i <-> j 是否为回文串。 

//动态规划法 
/*
使用dp[i][j]的值表示i到j的串是否为回文串,值为1表示是,值为0表示不是。
基本思想是,当我要计算i到j的串是否是回文串时,依赖于两个条件: 
	1) s[i]与s[j]是否相等.
	2) dp[i+1][j-1]是否是回文。 

初始条件:
dp[i][i]=1;   //长度为1的串是确定为回文的。 
dp[i][i+1];   //长度为2的串可用一层循环来判断。 

状态转移方程:
        dp[i][j] = 1;                   if(j-i == 0)  串长为1
     dp[i][j] = (s[i]==s[j])            if(j-i == 1)  串长为2
dp[i][j] = (s[i]==s[j] && dp[i+1][j-1]) if(j-i >= 2)  串长大于等于3 

*/
int main(){
	
	int n,m=0,max=0;
	int i,j,k;
	int x,y;   //记录回文串的上下标 
	
	fgets(buf,sizeof(s),stdin);
	n=strlen(buf);
	//将所有的字母符号都转变成大写,便于比较 
	for(i=0;i<n;i++){
		if(isalpha(buf[i])){ 
			p[m]=i; 
			s[m++] = toupper(buf[i]);
		} 
	}
	memset(dp,0,sizeof(dp));
	
	//初始化i到i是回文串 
	for(int i=0;i<m;i++){
		dp[i][i] = 1;
		x=p[i]; 
		y=p[i]; 
	} 
	//初始化i到i+1是否为回文串 
	for(int i=0;i<m-1;i++){
		if(s[i] == s[i+1]){
			dp[i][i+1]=1;
			max=2;
			x=p[i];   //记录在原串中的下标。
			y=p[i+1]; 
		}
	} 
	
	//前面长度为1和长度为2的串都判断完毕,现在从长度为3开始。 
	for(int len=3;len<=m;len++){
		//从串的首端开始判断,每次len个长度,往后移动。 
		for(int i=0; i<m-len+1; i++){
			int j=i+len-1;
			if(s[i] == s[j] && dp[i+1][j-1] == 1){
				dp[i][j]=1;
				max=len;
				x=p[i];
				y=p[j];
			}
		}
	} 
	
	printf("max = %d\n",max);
	for(int t=x;t<=y;t++){
		printf("%c",buf[t]);
	}
	printf("\n");
	return 0;
}

4、Manacher算法

简介:Manacher算法,俗称马拉车算法。这个算法的基本思想是,后面字符回文的判断可以利用前面的结果。为了介绍Manacher算法本质,我们只求回文长度,回文串的输出稍加修改即可。

1)算法首先有一个预处理,在字符串中间加入字符#或者其他,这样可以将偶数字符串和奇数字符串都转换成奇数串,统一操作。另外也可以在字符串前面加上@符号,这样下标变成1 - n-1,避免不必要的麻烦。

2)算法利用P[]数组记录字符串中每个字符的回文半径,例如下面的字符串串回文长度,

S     #   a  #  b  #   b  #  a   #  b  #  c  #   b   #  a  #
P     1   2  1  2  5   2  1  4   1  2  1  6   1  2   1  2  1

3)因此算法的关键就是求P数组。


基本思想就是,前面的P值已经求出来了,后面要求的P值就可以利用前面已经计算出来的结果。而这个利用是有一定条件的,比如上图,下标等于11的字符它的回文半径很长,左边[2-10],右边[12-20],这两个区间的字符串对应相等。那么在求i=[12 - 19]的回文半径时候我们可否利用一下区间[2 - 10]的结果,因为他们的回文半径早已经求出来了,比方说在求i=13字符b的回文半径时,和i=9时的回文半径是相同的,所以等于1。那是不是就可以一直这么算下去,算到i=19?其实不然,当i=15时,对称的i=7就是图中的 i' ,它的回文半径为7.这个值超出了[2....11]的范围,也即超出了[11....20]的范围。当没超出范围时,因为对称可以直接赋值,现在超出了,就说明在R右侧的部分还没有比较,并不知道是不是回文。那只能老老实实比较了,这里不用从i=15开始分别向两边比较,因为以i=15为中心的字符串已经有一部分是对称的了,因为i'的回文半径为7,而你这里R-i = 20-15=5,所以分别从i=20开始向右,i=15-(20-15) 向左比较就可以了。因此这里的 R-i 和P[i']的值决定了不同的情况。

要保存这个R值,并不断更新对应代码 if(i + p[i] > R ),它是已求的所有P[i]值中,最右的边界,并不一定是P[i]值最大就是最右边界。初始值为1,代表第一个字符的回文半径为1,默认为最长的回文半径。

从前往后遍历,依次求每个字符的回文值,看R和i的关系来确定从何处开始匹配,也就是给P[i]一个初始值,如果i在R右边时,只能重新匹配,没有信息可以利用,p[i]=1; 如果在左侧,还要看i对应i' 的P[i']的值,R-i和P[i']取较小值。对应min(R-i,p[i_mirror])。

# include <stdio.h>
# include <string.h>
# include <math.h>
# include <algorithm>  
using namespace std;

const int MAX=1000000;
int len,p[2*MAX];
char str[MAX],newstr[2*MAX];
/*
Manacher算法求最长回文子串 
*/
int main(){
	
	int i,j,k,t,n,ans = 0,temp =1;
	while(scanf("%s",&str)){
		if(strcmp(str,"END") == 0) break;
	
		//预处理字符串 
		n=strlen(str);
		j=0;
		newstr[j++]='@';
		for(i=0;i<n;i++){
			newstr[j++]='#';
			newstr[j++]=str[i];
		}
		newstr[j++]='#';
		newstr[j]='\0';
		n=j;
		
		//算法开始 
		ans = 0;
		int R=1,id=1;
		for(i=1;i<n;i++){
			
			int i_mirror = 2*id-i; //equals to i' = id-(i-id)
			p[i] = (R>i) ? min(R-i,p[i_mirror]) : 1;
			//往两边扩展比较 
			while(newstr[i+p[i]] == newstr[i-p[i]]){
				p[i]++;
			} 
			if(i+p[i] > R){
				id=i;
				R=i+p[i];
			}
		}
		
		//输出 
		int maxlen = 0;
		for(i=0;i<n;i++){
			if(p[i]>maxlen)
				maxlen=p[i];
			printf("%d ",p[i]);
		}
		printf("Case %d: %d\n",temp++,maxlen-1);			
	}
}



参考文章:http://blog.csdn.net/zhangjun03402/article/details/50514722








  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

随风浪仔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值