字符串算法在各大高级比赛中均有用到,所以,学习好字符串算法对我们而言十分重要。那么,今天我们就给大家介绍一个快速求回文串的算法,Manacher算法,我们也习惯性叫它马拉车算法。

一.引入

首先我们要知道什么是回文串——当一个字符串它从右到左和从左到右读是一样的,我们就称它为回文串。考虑一下最暴力的算法,我们可以枚举字符串的每个子串,判断其是否为回文串,时间复杂度是O(n3)。当然,我们可以加点优化,枚举每个中心点,然后向两边匹配,时间复杂度是O(n2)。不过这个复杂度依然不让人满意,因此,我们引入Manacher算法, 将时间复杂度降到线性,提高了算法效率。

二.算法流程

由于回文串分为奇回文和偶回文,因此给算法带来不小的麻烦,所以我们可以在字符串中间加入一些字符,使得其一定为奇回文,如 s= 'abaoyyo',转换后就成了 s_new= '#&a&b&a&o&y&y&o&^'(前后加字符只是为了防止越界,后面会讲),这样,原有的回文串 'ababa' 和 'oyyo' 便变成了 '&a&b&a&' 和 '&o&y&y&o&' ,都是奇回文了。同时,我们要引入一个数组 p,p[i] 代表以 i 为中心的回文串的最大半径,如:

i 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
s_new # & a & b & a & o & y & y & o & ^
p 0 1 2 1 4 1 2 1 2 1 2 5 2 1 2 1 0

为什么开始和最后面是0呢,是因为我们在计算的时候一般不考虑这两个边界,只是防止越界用的。并且我们可以看到,p[i]-1 对应的就是在原串中以 s[i] 为中心的回文串的长度(不包括添加的字符)。那么,为什么Manacher算法要比一般的算法要快呢?因为它在求 p 的时候有一个捷径,如下图:



p[i] 是按顺序求的,我们记录 Max 为以 s_new[id] 为中心,右端点最大的值,即为 p[i]+i,其中,i,j 关于 id 对称,红色箭头代表对于一个点的扩张半径。如果 i < Max 的话,我们则有

  1. if (i<Max)
  2. p[i]=min(p[id*2-i],Max-i);

三.解释

就上图而言,p[i]=p[j] 这点是毋庸置疑的,也就是 p[i]=p[id*2-i](因为i,j 关于 id 对称)。那么,为什么要取min呢?这是我们要保证 p[i] 在直接更新的时候,右端点不能超过 Max 。那么为什么不能超过 Max 呢?我们画个图理解下



假定 p[j] 的左边界超过 id 的左边界,那么当我们直接令 p[i]=p[j] 时,i 的右边界就会超过 id 的右边界,那么这种情况是否存在呢,答案是否定的。

因为根据假设可得 j 的红色扩张部分和 i 的红色扩张部分是一样的,并且由于对称,绿色的箭头也也是对称的,既然如此,那么id的边界为什么不到两个绿色箭头的端点呢?

因此,在这种情况下,p[i] 不能直接等于 p[j],p[i] 最大只能到 Max 的右边界,即 p[i]=Max-i 。同时,我们可以知道,在这种情况下,p[i] 是不能再扩张的。

Manacher还有其他的一些情况,如下图



如果 p[j] 的左右边界都在 id 内部,那么在 p[i]=p[j] 后,p[i] 还能继续扩张吗?答案依然是否定的。

若 i 能扩张,则必定有一段扩张在 id 内部,即绿色部分。那么根据对称可知,j 也会有两段对称的绿色,那么 p[j] 为什么不扩张呢?

因此,这种情况下,p[i] 也是不能扩张的。

那么,是不是 p[i]=min(p[id*2-i],Max-i) 就好了呢?答案依然是否定的



如果说j的左边界与 id 的左边界重合,那么i的右边界就和 Max 重合。在这个情况下,i 是可以继续扩张的,之后的扩张,就只能不断的暴力匹配了

四.补充

我们开始讲到的所有情况都是建立在 i < Max 的基础之上的。那么,如果 i > Max 的话该如何呢?其实,当 i > Max 的时候,我们没有办法对 i 做出任何的假设,只能令其等于1,然后暴力匹配即可

对于 id 和 Max 而言,每次更新完 i 后进行比较,取最大值即可

暴力匹配的时候很有可能导致数组越界,因此我们在最前面和最后面加上两个不同的字符来保证其失配

五.算法复杂度

由于本算法对于匹配过的字符串基本不匹配,没有匹配过的字符串也只是O(n)扫过,因此时间复杂度可以看为是线性的,十分优秀

六.代码

  1. #include<cmath>
  2. #include<cstdio>
  3. #include<cstring>
  4. #include<iostream>
  5. #include<algorithm>
  6. #define inf 0x7f7f7f7f
  7. using namespace std;
  8. typedef long long ll;
  9. typedef unsigned int ui;
  10. typedef unsigned long long ull;
  11. inline int read(){
  12. int x=0,f=1;char ch=getchar();
  13. for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
  14. for (;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
  15. return x*f;
  16. }
  17. inline void print(int x){
  18. if (x>=10) print(x/10);
  19. putchar(x%10+'0');
  20. }
  21. const int N=1e6;
  22. char s[N*2+10],t[N+10];
  23. int p[N*2+10];
  24. int main(){
  25. printf("请输入字符串\n");
  26. scanf("%s",t+1);
  27. int len=strlen(t+1),Max=0,ID=0,Ans=0,cnt=0;
  28. for (int i=1;i<=len;i++) s[i<<1]=t[i],s[i<<1|1]='&'; //添加字符,使其变为奇串
  29. len=len<<1|1;
  30. s[1]='&',s[0]='%',s[len+1]='#'; //防止越界
  31. for (int i=1;i<=len;i++){
  32. p[i]=Max>i?min(p[ID*2-i],Max-i):1; //核心部分
  33. while (s[i+p[i]]==s[i-p[i]]) p[i]++; //暴力匹配
  34. if (Max<i+p[i]) Max=p[ID=i]+i; //更新Max
  35. if (Ans<p[i]) Ans=p[i],cnt=i-p[i]; //更新答案
  36. }
  37. cnt>>=1;
  38. printf("最长回文串为\n");
  39. for (int i=cnt+1;i<cnt+Ans;i++) putchar(t[i]);
  40. putchar('\n');
  41. return 0;
  42. }

浅谈算法——Manacher的更多相关文章

  1. 浅谈算法和数据结构: 七 二叉查找树 八 平衡查找树之2-3树 九 平衡查找树之红黑树 十 平衡查找树之B树

    http://www.cnblogs.com/yangecnu/p/Introduce-Binary-Search-Tree.html 前文介绍了符号表的两种实现,无序链表和有序数组,无序链表在插入的 ...

  2. 浅谈算法和数据结构: 十 平衡查找树之B树

    前面讲解了平衡查找树中的2-3树以及其实现红黑树.2-3树种,一个节点最多有2个key,而红黑树则使用染色的方式来标识这两个key. 维基百科对B树的定义为“在计算机科学中,B树(B-tree)是一种 ...

  3. 转 浅谈算法和数据结构: 十 平衡查找树之B树

    前面讲解了平衡查找树中的2-3树以及其实现红黑树.2-3树种,一个节点最多有2个key,而红黑树则使用染色的方式来标识这两个key. 维基百科对B树的定义为"在计算机科学中,B树(B-tre ...

  4. 浅谈算法——AC自动机

    在学习AC自动机之前,你需要两个前置知识:Trie树,KMP 首先我们需要明白,AC自动机是干什么的(用来自动AC的) 大家都知道KMP算法是求单字符串对单字符串的匹配问题的,那么多字符在单字符上匹配 ...

  5. 浅谈算法——KMP

    KMP是啥?KMP当然是KMPlayer的简称啦 KMP算法是用来解决字符串匹配的一种算法,由D.E.Knuth.J.H.Morris和V.R.Pratt同时发现,然后它可以用来干啥呢?我们上个例题: ...

  6. 浅谈算法——线段树之Lazy标记

    一.前言 前面我们已经知道线段树能够进行单点修改和区间查询操作(基本线段树).那么如果需要修改的是一个区间该怎么办呢?如果是暴力修改到叶子节点,复杂度即为\(O(nlog n)\),显然是十分不优秀的 ...

  7. 浅谈算法——FWT(快速沃尔什变换)

    其实FWT我啥都不会,反正就是记一波结论,记住就好-- 具体证明的话,推荐博客:FWT快速沃尔什变换学习笔记 现有一些卷积,形如 \(C_k=\sum\limits_{i\lor j=k}A_i*B_ ...

  8. 浅谈算法——splay

    BST(二叉查找树)是个有意思的东西,种类巨TM多,然后我们今天不讲其他的,我们今天就讲splay 首先,如果你不知道Splay是啥,你也得知道BST是啥 如上图就是一棵优美的BST,它对于每个点保证 ...

  9. 浅谈大型web系统架构

    动态应用,是相对于网站静态内容而言,是指以c/c++.php.Java.perl..net等服务器端语言开发的网络应用软件,比如论坛.网络相册.交友.BLOG等常见应用.动态应用系统通常与数据库系统. ...

随机推荐

  1. POJ 1061 青蛙的约会(扩展GCD求模线性方程)

    题目地址:POJ 1061 扩展GCD好难懂.. 看了半天.最终把证明什么的都看明确了. .推荐一篇博客吧(戳这里),讲的真心不错.. 直接上代码: #include <iostream> ...

  2. Java使用三种不同循环结构对1+2+3+...+100 求和

    ▷//第一种求法,使用while结构 /** * @author 9527 * @since 19/6/20 */ public class Gaosi { public static void ma ...

  3. Android 4.4.2 动态加入JNI库方法记录 (二 app应用层)

    欢迎转载,务必注明出处:http://blog.csdn.net/wang_shuai_ww/article/details/44458553 源代码下载地址:http://download.csdn ...

  4. Apache Hadoop 3.0.0 Release Notes

    http://hadoop.apache.org/docs/r3.0.0/hadoop-project-dist/hadoop-common/release/3.0.0/RELEASENOTES.3. ...

  5. 网络驱动移植之net_device结构体及其相关的操作函数

    内核源码:Linux-2.6.38.8.tar.bz2 在Linux系统中,网络设备都被抽象为struct net_device结构体.它是网络设备硬件与上层协议之间联系的接口,了解它对编写网络驱动程 ...

  6. HDU1083 Courses —— 二分图最大匹配

    题目链接:https://vjudge.net/problem/HDU-1083 Courses Time Limit: 20000/10000 MS (Java/Others)    Memory ...

  7. YTU 2875: 倒霉蛋买饭去

    2875: 倒霉蛋买饭去 时间限制: 1 Sec  内存限制: 128 MB 提交: 22  解决: 17 题目描述 早春星期天的某个早晨,大风呼呼地刮.一个宿舍n个人,谁也不想起床买饭去.他们定了一 ...

  8. Kotlin 单例

    单例的实现方法,可以通过同伴对象,或者 lazy. 示例: class Hello private constructor() { companion object { val instance = ...

  9. Local Databases with SQLiteOpenHelper

    Overview For maximum control over local data, developers can use SQLite directly by leveraging SQLit ...

  10. c语言和oc对比

    1)源文件对比 思考&实现1: 1)在C语言中,我们遇到不同后缀的文件有哪些? .c .o .out .h 2.基本语法对比 1)数据类型对比学习 2)变量的定义对比 3)流程控制语句对比 1 ...