manacher——马拉车算法(图文详解)

这篇具有很好参考价值的文章主要介绍了manacher——马拉车算法(图文详解)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

简要介绍

 马拉车算法,Manacher‘s Algorithm 是用来查找一个字符串的最长回文子串的线性方法,是一个叫Manacher的人在1975年发明的,这个方法的最大贡献是在于将时间复杂度提升到了线性O(N)

实际应用

  1. 刷题——最长回文子串,回文子字符串的个数。
  2. 生物中的基因排列可能会用到,DNA/RNA遗传信息。

算法详解

  • 本质:利用已有经验不断迭代

首先我们在求一个字符串的回文子串时,会有一个问题,长度为奇数的和长度为偶数的字符串求法不一

  1. 奇数长度——比如:aaa
    manacher——马拉车算法(图文详解)
  2. 偶数长度——比如:bb
    manacher——马拉车算法(图文详解)
    有些朋友到这可能会问了,中心有啥用?
    肯定有用了,下面我们就来说,有啥用。

假如给你一个字符串,一步一步找最长回文子串,你会怎么找?

青铜级选手:列出所有的子串,然后判断是否是回文子串,如果是,则计算子串长度,并跟当前的最长回文子串进行比较,如果比之长,则更新。
首先我们需要一层循环,确定长度,再需要一层循环,取遍当前确定长度的所有子串,还需要一层循环确定是否是回文字符串。——O(N3)
那么C语言写出的代码是这样的:

char * longestPalindrome(char * s)
{
    int len_s = strlen(s);
    int max_len = 0;
    int left_str = 0;
    int right_str = -1;
    for(int len = 0; len < len_s; len++)
    {
        int left = 0;
        int right = len;
        while(right < len_s)
        {
            //判断是否是回文子字符串
            int begin = left;
            int end = right;
            int is_true = 1;
            while(begin<=end)
            {
                if(s[begin]!=s[end])
                {
                    is_true = 0;
                    break;
                }
                begin++;
                end--;
            }
            if(is_true)
            {
                left_str = left;
                right_str = right;
                break;
            }
            left++;
            right++;
        } 
    }
    //开辟数组存储子串
    char * str = (char*)malloc(sizeof(char)*(len_s+1));
    memset(str,0,sizeof(char)*(len_s+1));
    for(int left = left_str,i = 0; left <= right_str; left++,i++)
    {
        str[i] = s[left];
    }
    return str;
}
  • 虽然能过,但是这是优化了一点,你试着把两个break去掉,就不能过了
  • 暴力求解中心下标可没有用!

黄金级选手:如果是回文子串,那么其中心下标,向两边进行扩展,不还是回文子串吗?这里需要注意的是偶数的中心下标有两种情况,那我们就假设字符串的每一个元素都是中心下标,遍历,然后需要注意第一步确定边界,往两边延伸一次可能是回文,往前面延伸一次也可能是回文。因此我们都要试一次,确定一个边界,再进行两边延伸。总结一下:中心下标一次循环,确定起始边界(两种可能性),然后向两边走,不嵌套的两次循环,因此时间复杂度为——O(N2)。
图解:
manacher——马拉车算法(图文详解)

因此根据这种思路写出的C代码是这样的:

char * longestPalindrome(char * s)
{
    int ce_sut = 0;//center_subscript
    int len = strlen(s);
    int left_str = 0;
    int right_str = 1;
    int max_len = 0;
    for(ce_sut = 0; ce_sut < len; ce_sut++)
    {
        //确定起始边界
        if(s[ce_sut] == s[ce_sut+1])
        {
            int begin = ce_sut;
            int end = ce_sut + 1;
            while(begin>=0&&end<len\
            &&s[begin] == s[end])
            {
                begin--;
                end++;
            }
            //越界或者不是回文字符串
            begin++;
            end --;
            int len_cur = end - begin + 1;
            if(len_cur>max_len)
            {
                max_len = len_cur;
                left_str = begin;
                right_str = end+1;
            }
        }

        if(ce_sut-1>=0&&ce_sut+1<len&&\
        s[ce_sut-1]== s[ce_sut + 1])
        {
            //确定起始边界

            int begin = ce_sut-1;
            int end = ce_sut + 1;
            while(begin>=0&&end<len\
            &&s[begin] == s[end])
            {
                begin--;
                end++;
            }
            //越界或者不是回文字符串
            begin++;
            end --;

            int len_cur = end - begin + 1;
            if(len_cur>max_len)
            {
                max_len = len_cur;
                left_str = begin;
                right_str = end+1;
            }
        }
    }
    //开辟空间拷贝字符串
    char * str = (char*)malloc(sizeof(char)*(len+1));
    memset(str,0,sizeof(char)*(len+1));
    for(int left = left_str,i = 0; left < right_str; left++,i++)
    {
        str[i] = s[left];
    }
    return str;
}

大师级选手:利用manacher——马拉车算法 + 小技巧。

  1. 小技巧:n个字符,有n+1个空隙,因此可以插入n+1个相同的字符,总共2*n+1个字符,这样是不是就是不管是偶数还是奇数长度的字符,最后都变为了奇数个长度的字符。由于处理过后前后的字符并不相等,因此只管往两边扩!更细一步:为了防止出现越界的情况,我们需要再在开头和结尾放置两个与之都不同两个字符。
  2. 马拉车算法,前几种只是为了为此铺垫,如果弄清则对现在的理解会有一定的帮助。
    我们以一个字符串为例进行介绍
    处理之前: wxvxv
    处理之后:$#w#x#v#x#v#!
     因为既然是往两边扩的话我们也可以只扩右边(对称的性质),只需算一下右边扩的时候与中心坐标对称的的另一个下标的字符是否相等即可。比如中心下标为2,那么如果向右扩到了3,其对称下标为2*2 - 3等于1,那我们设中心下标为Mid,则左边的下标为Left,右边的下标为Right,则关系式就为—————— Right+Left = 2*Mid,则我们可以看这时的回文区间就是【Left,Right】闭区间。
     如果这里搞懂了,下面就更容易理解了,我们用这个例子一步一步走。

manacher——马拉车算法(图文详解)
如果体验过这一个例子,离成功只剩一步之遥了!——总结思路。

第一步:求当前的中心坐标的长度,并保存此长度
第二步: 计算范围[Left,Right]。
第三步:从中心下标+1开始,到Right-1结束,判断当前坐标的对称坐标回文长度,是否等于当前坐标的回文长度——看对称坐标范围是否到边界,如果等于,就继续走,如果不等于就更新中心下标到当前坐标。
第四步:计算出处理后最大回文子串的长度,由处理前的最大回文子串的长度/2即可。因为处理后的最大回文长度必定是奇数,我们举a,处理之后为#a#,处理后的最大回文子串长度为3,/2等于1,再举一个例子,#a#a#,处理之后最大回文子串的长度为5,/2等于2,就等于原来的回文的字串的长度,要说为什么因为是/2,是相0取整的!

因此根据这种思路写出的C代码是这样的。文章来源地址https://www.toymoban.com/news/detail-477072.html

char * longestPalindrome(char * s)
{
    //处理字符串
    //开头和结尾需要加上都不同的两个字符,处理越界,这里我加上$和!
    //中间隔开的字符我们用#——字符串的长度+1
    //不要忘了还要为\0开辟空间
    //带上原来的字符串的长度
    //总计:n + n + 1 + 2 + 1 == 2*n + 4 
    int len_s = strlen(s);
    char* str = (char*)malloc(sizeof(char)*(2*len_s+4));
    memset(str,0,sizeof(char)*(2*len_s+4));
    //开头的下标为:0
    //结尾的下标为:2*len_s + 2
    str[0] = '$';
    str[2*len_s + 2] = '!';
    printf("%c",str[0]);
    //奇数放'#'
    //偶数放s的字符
    for(int i = 0;i < len_s; i++)
    {
        str[2*i + 1] = '#';
        str[2*i + 2] = s[i];
        printf("%c%c",str[2*i+1],str[2*i+2]);
    }
    //a这里只会处理成$#a!倒数第二个字符没有处理因此我们需要还要加上
    str[2*len_s + 1] = '#';
    printf("%c%c\n",str[2*len_s + 1],str[2*len_s + 2]);
    //manacher思路
    //记录中心下标回文字符串长度的数组
    int * infor = (int*)malloc(sizeof(int)*(2*len_s+2));
    memset(infor,0,sizeof(int)*(2*len_s+2));
    //记录最长回文子符串的长度
    int max_len = 0;
    //记录最长回文字符串的左右下标
    int left_str = 0;
    int right_str = 1;
    for(int mid = 1; mid < 2*len_s + 2;)
    {
        //先让中心下标往两边进行扩
        int left = mid;
        int right =  mid;
        int count = 0;
        while(str[left]==str[right])
        {
            left--;
            right++;
            count ++;
        }
        //这里不是回文字符串,但是我们往前再走一步就是回文字符串
        left++;
        right--;
        //我们需要保存一下最大回文半径的值
        infor[mid] = count;
        //优化
		if(right == 2*len_s + 1)
        {
            break;
        }
        //这里我们需要判断一下回文字符串的长度大于不大于当前最大的回文字符串的长度
        int len_cur = right - left + 1;//这里是左闭右闭
        if(len_cur > max_len)
        {
            max_len = len_cur;
            left_str = left;
            right_str = right+1;//左闭右开
        }
        //如果字符串的长度大于3就可能会有
        if(mid+1<right)
        {
            int k = mid + 1;
            while( k < right)
            {
            	//优化
            	//可能的最大半径
                int may_len = 2*len_s + 1 - k + 1;
                //当前的最大半径
                int cur_r = right - mid + 1;;
                if(may_len < cur_r)
                {
                    no_need = 1;
                    break;
                }
                //有两种情况
                //我们需要计算出k的对称下标的左边界与当前中心下标的左边界进行比较。
                int n = 2*mid - k;//求出中心下标对应的对称坐标
                int r = infor[n];//最大回文子串的半径
                int left_n = n - r + 1;//对称下标的左边界
                //如果对称下标的左边界小于等于中心下标的左边界,就不一定会有 
                if(left_n <= left)
                {
                    mid = k;
                    break;
                }
                else
                {
                    infor[k] = infor[n];
                }
                k++;
            }
        }
        else
        {
            mid++;
        }

    }
    //储存字符串
    char* str1 = (char*)malloc(sizeof(char)*(len_s+1));
    memset(str1,0,sizeof(char)*(len_s+1));
    int i = 0;
    for(int left = left_str; left < right_str; left++)
    {
        if(str[left]!='#')
        {
            str1[i++] = str[left]; 
        }
    }
    return str1;
}

到了这里,关于manacher——马拉车算法(图文详解)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 排序算法——希尔排序图文详解

    注1:本篇是基于对直接插入排序法的拓展,如果对直接插入法不了解,建议先看看 直接插入排序 注2:本篇统一采用升序排序 希尔排序法又称缩小增量法。 希尔排序其实是直接插入排序的改进。 其 基本思想是 : 先选定一个整数gap,把待排序文件中所有记录分成数组,所有

    2024年02月07日
    浏览(38)
  • 算法模板之栈图文详解

    🌈个人主页: 聆风吟 🔥系列专栏: 算法模板、数据结构 🔖少年有梦不应止于心动,更要付诸行动。      💬 hello! 各位铁子们大家好哇,我们上期已经学习了双链表的算法模板,不知道大家都已经掌握了吗?如果你还有缺漏可以通过下面专栏自行跳转学习,今天作者

    2024年02月04日
    浏览(35)
  • 归并算法:分治而治的高效算法大揭秘(图文详解)

    🎬 鸽芷咕 :个人主页  🔥 个人专栏 : 《数据结构算法》《粉丝福利》 ⛺️生活的理想,就是为了理想的生活! 归并算法是我们算法中最常见的算法之一,其思想非常巧妙。本身归并是只能归并有序数组但是当我们利用了二路归并分治法之后,就可以使用归并的思想来帮我

    2024年02月03日
    浏览(48)
  • C语言--直接插入排序【排序算法|图文详解】

    直接插入排序又叫简单插入排序,是一种 简单直观 的排序算法,它通过构建有序序列,对于未排序的数据,在已排序序列中从后向前扫描,找到相应位置并插入。 算法描述: 假设要排序的列表为arr,列表的第一个元素arr[0]默认已经是有序序列。 从第二个元素开始,即arr[

    2024年01月19日
    浏览(44)
  • 【算法】顺时针打印矩阵(图文详解,代码详细注释

    目录 题目 代码如下: 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。例如:如果输入如下矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则打印出数字:1 2 3 4 8 12 16 15 14 13 9 5 6 7 11 10 这一道题乍一看,没有包含任何复杂的数据结构和高级算法,似乎蛮简单的。但

    2024年04月26日
    浏览(37)
  • 算法模板之单调栈和单调队列图文详解

    🌈个人主页: 聆风吟 🔥系列专栏: 算法模板、数据结构 🔖少年有梦不应止于心动,更要付诸行动。      💬 hello! 各位铁子们大家好哇,今天作者给大家带来了单调栈和单调队列的算法模板讲解,让我们一起加油进步。      📚 系列专栏:本期文章收录在《算法

    2024年02月02日
    浏览(46)
  • 【数据结构】排序算法复杂度 及 稳定性分析 【图文详解】

    前面给大家讲述了各大排序算法的原理、思路以及实现步骤、代码码源,下面让我们来对比一下各大排序之间的算法复杂度以及稳定性分析优劣,加深我们对于各排序算法的理解,帮助我们以后能更快的在具体场景下选择出最适的排序算法。 【数据结构】冒泡排序 (码源实

    2024年02月05日
    浏览(99)
  • BFS算法(宽度优先搜索)超强解析 BFS迷宫问题图文详解 DFS与BFS的区别

      前情回顾:DFS练习-迷宫(最短路径)问题详解 一波三折 图片+文字 以及你需要会的基础:手搓数据结构之队列queue C/C++语言版(BFS算法预备知识) 广度优先搜索(Breadth First Search)简称广搜或者 BFS, 是遍历图存储结构的一种算法 。 BFS的原理是 “逐层扩散” ,从起点出发

    2024年02月22日
    浏览(48)
  • 构建机器学习算法简要说明

    机器学习中的一个核心问题是设计不仅在训练数据上表现好,而且能在新输入上泛化好的算法 设计一个在训练数据上表现好且能在新输入上泛化好的算法是机器学习的一个核心问题。泛化能力是指模型在没有见过的新数据上的预测能力,它是评估模型真正的实用性的重要指标

    2024年02月10日
    浏览(56)
  • C语言递归+DFS(深度优先搜索算法)详解 图文并茂,手把手教你画树状图

    目录 一.标准定义 二.跳台阶(典型递归题目) 三.递归实现指数型枚举 四.递归实现排列型枚举 五.递归实现组合型枚举 六.DFS算法模板 深度优先搜索算法(Depth First Search,简称DFS): 一种用于遍历或搜索树或图的算法 。 沿着树的深度遍历树的节点, 尽可能深的搜索树的分

    2024年02月04日
    浏览(76)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包