求字符串中最长回文子串的长度,标记数组筛选法(自创)&&马拉车算法(详解)

求字符串中最长回文子串的长度,标记筛选法(自创)&&马拉车算法(Manacher算法)

    • 分析:
  • 解法一:标记数组筛选法(自创)
    • 思路:
    • 代码:
    • 分析:
  • 解法二:马拉车算法(Manacher算法)
    • 思路:
    • 代码:

分析:

根据对一些回文串的观察可以发现,回文串分为两类(依据回文串的对称中心)。
1,aa型 例如:aa,abba,abccba;
2, aba型 例如:aba,aabaa,abcba;
所以有了 解法一:根据回文串对称中心下手。

解法一:标记数组筛选法(自创)

思路:

1,先遍历一次字符串,出现挨着相等的字符则将其下标存入一个数组a,出现间隔相等的字符则将其下标存入一个数组b。即筛选出子串,并用一个数组标记。
2,再遍历一次数组a,通过存入的下标对字符串进行左右探索,相同则长度+2,不同立刻跳出。记录最大值
3,再遍历一次数组b,通过存入的下标对字符串进行左右探索,相同则长度+2,不同立刻跳出。记录最大值
4,最长回文子串长度即为2,3获取到的最长回文串子度两者的更大值

代码:

#include 
#include
int main()
{
	char a[100]={0},b[100]={0},s[100];		//数组a存放aa类回文数的第二个a的下标,数组b存放aba类回文数的b的下标 ,数组s为输入的字符串  
	int max1=1,max2=1,t1=2,t2=3;			//max1为最大的aa类回文数长度,max2为最大的aba类回文数长度 //t是否个具体的回文串的长度 
	printf("输入一个字符串:");
	scanf("%s",s);
	int l=strlen(s);	
	for(int i=0,j=0,k=0;i<l-1;i++)
	{
		if(s[i]==s[i+1])	a[j++]=i+1;		//查找aa类回文数,//存取第二个a的下标
		if(s[i]==s[i+2])	b[k++]=i+1;		//查找aba类回文数,//存取b的下标  
	}
	if(a[0]!=0)													//获取aa型最长回文子串的长度,若a[0]!=0,说明存在aa型,长度至少为2 
		for(int i=0;a[i]!=0;i++,max1=max1>t1?max1:t1,t1=2)		//遍历a数组获取下标,更新最大值,重置回文串长度t=2  
		{
			for(int j=1;a[i]-1-j>=0&&a[i]+j<=l;j++)
			{
				if(s[a[i]-1-j]==s[a[i]+j])	t1+=2;				//仍然对称,该回文子串长度+2 
				else	break;									//一旦失败,立即退出当前循环 
			}	
		}	
	if(b[0]!=0)													//获取aba型最长回文子串的长度,若b[0]!=0,说明存在aba型,长度至少为3 
		for(int i=0;b[i]!=0;i++,max2=max2>t2?max2:t2,t2=3)		//遍历b数组获取下标,更新最大值,重置回文串长度t=3
		{
			for(int j=1;b[i]-1-j>=0&&b[i]+1+j<=l;j++)
			{
				if(s[b[i]-1-j]==s[b[i]+1+j])	t2+=2;			//仍然对称,该回文子串长度+2 
				else	break;									//一旦失败,立即退出当前循环 
			}
		}
	printf("输入的字符串中最长回文子串长度为:%d\n",max1>max2?max1:max2);	//输出两者的更大值 
	return 0; 
}

//BY 陌生兔 

分析:

第一种方法讲到,回文串分为aa型,aba型,导致需要分情况讨论,给算法带来不便。所以有了 解法二,避开回文类型。

解法二:马拉车算法(Manacher算法)

思路:

1,马拉车算法 创造性的用一个符号(如#)插入到字符串之间形成一个新串,强制使得新串中回文串都变成aba型。例如:aa处理后变为#a#a#,aba处理后变为#a#b#a#。
这仅是马拉车的第一个神奇之处,当然这个方法也可以用到第一个解法里去。
2,马拉车算法,定义了一个回文半径函数,
例如:#a#a#半径为3,#a#b#a#半径为4。
这是马拉车算法最核心的地方,减少了许多不必要的操作。使得时间复杂度趋向于线性O(n)。
3,回文半径的作用,每个回文串都有一个回文半径,由于回文串的特性,顺逆读取相同,或者说关于某一点对称。由于大家对数学了解很多,因此我用数学概念来类比,方便大家理解。
回文串=偶函数----------------------------dabac的aba---------------------变量s
回文串对称点--------原点------------------aba中的b----------------------变量 centre
回文串边界-----------定义域边界-------------dabac的d和c----------------变量right_border(由于是从左往右扫描,左边界用不着)
回文串半径-----------定义域边界到原点的距离--------dabac中aba的半径为2---------变量radius
4,
利用回文串的特点(对称性/正逆相同性),减少一些不必要的操作。
接下来是核心中的核心了,很多人并未完全了解马拉车算法,只知道个大概,原因就是不知道该句算法的内核。

if(i>=right_border)   
	radius[i]=1;								             
else	
	radius[i]=radius[2*centre-i]<right_border-i?radius[2*centre-i]:right_border-i;

我来解释一下:该代码的意图是
(1)如果i超出了上一回文串的边界,那么该回文串i的半径赋初值为1.
(2)如果i仍处于上一回文串的边界内,那么radius[i]取radius[2centre-i]和right_border-i中的小值。
其中radius[2
centre-i]是i关于centre的对称点的值,其中right_border-i是i与右边界的距离。按照常识,因为对称性,radius[i]=radius[2centre-i],但是因为我们所有的操作都是在一个普通字符串中,输入的字符串本身绝大可能不是回文串,因此对称性不能完全存在。例如:a badab e,当centre指向d时,即centre=3,显然right_border指向e,即right_border=6。i++后,当i指向第二个b时,即i=5,此时第一个b的半径为2,而第二个b的半径显然为1,≠2.这是为什么呢,因为第一个b左边的那个a并不是以d为centre的回文串 中的字符。所以我们应该考虑只由定义域内的回文半径,即right_border-i,对称的那个点的半径在定义域内的最多最多是right_border-i,所以要取其中的小值。
确实不太好理解,再简述一遍,
若2
centre-i处的定义域全在centre的定义域内,那么radius[i]=radius[2centre-i];
若2
centre-i处的定义域超出了centre的定义域,那么其定义域在centre的定义域的范围为2centre-i到左边界的值,即right_border-i。
求字符串中最长回文子串的长度,标记数组筛选法(自创)&&马拉车算法(详解)_第1张图片
由于是从左往右扫描,2
centre-i的半径早已经存入了,所以将radius[2*centre-i]的有效半径作为radius[i]的起始值,然后再执行后续的代码,该值是否增加由后面的判断决定。由于初始值不是从0开始,所以减少了大量不必要的操作。使得while循环接近线性复杂度O(n)。

代码:


#include
#include
int main() 
{
	int ls,max=0;
	char str[100],s[200],radius[200];
	printf("输入一个字符串:");										
	scanf("%s",str);
    ls=strlen(str);
	s[0]='*';s[1]='#';
	for(int i=0;i<ls;i++)
	s[i*2+2]=str[i],s[i*2+3]='#';
	ls=ls*2+2; 
	int centre=0,right_border=0;											
	for(int i=1;i<ls;i++)                                                  
	{
	    if(i>=right_border)   radius[i]=1;								             
	    else	radius[i]=radius[2*centre-i]<right_border-i?radius[2*centre-i]:right_border-i;		
	    while(s[i+radius[i]]==s[i-radius[i]])	radius[i]++;					
	    if(radius[i]+i>right_border)    right_border=radius[i]+i,centre=i;				
	    max=max>radius[i]?max:radius[i];								
	    }
    printf("最长回文串长度为%d\n",max-1);
    return 0;
}

//BY陌生兔 

纯手打原创,转载请注明出处。

你可能感兴趣的:(经典算法,经典算法,马拉车,字符串,回文,算法详解)