回文串 Manacher's Algorithm

今天刚学了Manacher算法,也就是马拉车算法,是用来查找一个字符串中的回文串的算法,算法复杂度是线性的,比朴素的暴力求解快很多。

一、预处理

对于一个字符串,要找它的回文串,首先想到的就是从头到尾一个个找过来,而且回文串存在两种情况,一种是长度为奇数的回文串,还有一种是长度为偶数的回文串。所以每次循环到一个位置,都要分奇偶回文串进行查找,很麻烦。
比如对于字符串:adccbcc 循环到字符c的时候存在一个长度为2的偶数长回文串"cc",而循环到b的位置的时候存在一个长度为5的奇数长度的回文串"ccbcc"。
我们可以先在字串的前后和每两个字符中间插入一个没有出现过的字符,比如上面的那个字符串,插入’#'预处理之后就变成了,#a#d#c#c#b#c#c#。处理之后的字符串肯定是个奇数串,只用查找奇数回文串就好了。

二、算法实现

对于朴素算法,比如字符串T =“a d c a c d c a c d b d”对于每个位置都要查询一遍,而马拉车算法在朴素地算法上增加了一部分的剪枝,如果已经知道T[5]的位置存在回文串 “dcacdcacd” 而且知道了T[5]之前的每个位置的回文串的长度,由于奇数长度的回文串对于中心堆成,那么T[7]位置的回文串“cac”在之前T[3]的位置已经出现过了,所以可以直接得出T[7]位置的回文串的长度为3并且和T[3]处的回文串相同。
我们需要记录回文串的结尾下标最靠右(把最右端称为右界)的那个回文串中心字符的下标md,还有以每个位置为中心开始的回文串的长度+1的一半(也就是回文串的半边长度加上中间位置),用数组P来记录。

接下来分两种情况讨论:
①:查询点的下标小于右界,又可以分两种小case:
1、查询点的下标加上对称点的回文串半边长度小于右界 这时直接把对称点回文串的长度赋值给查询点就行了。
2、查询点下标加上对称点的回文串半边长度大于等于右界 这时在右界之前的回文串长度已知,对右界及之后的点进行匹配直到失配,更新md下标。
②:查询点下标大于右界:
这种情况下,只可能查询点下标比右界大1,按朴素的办法进行匹配就好了,然后更新md下标。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 1e4+7;
char in[maxn],text[maxn];
int P[maxn];
void pre_treat(){
    int len = strlen(in);
    for(int i=0;i<len;i++){
        text[i*2] = '#';
        text[i*2+1] = in[i];
    }
    text[len*2]='#';text[len*2+1]='\0';
}
void manacher(){
    P[0] = 1;                                   //第一个字符的回文串肯定是1
    int len = strlen(text);
    int md = 0;
    for(int i=1;i<len;i++){
        if(i<md+P[md]){
            int k = P[md*2-i];                  //找到对称点的回文串长度
            if(i+k<md+P[md]) P[i] = k;          //回文串没有超过右界
            else{
                k = md+P[md]-i;
                while(i+k<len&&i+k>=0&&text[i+k]==text[i-k]) k++;
                md = i;
                P[i] = k;
            }
        }
        else{
            int k = 1;
            while(i+k<len&&i+k>=0&&text[i+k]==text[i-k]) k++;
            md = i;
            P[i] = k;
        }
    }
    for(int i=0;i<len;i++) printf("%d ",P[i]);      //(处理之后的每个点的回文串的长度)/2+1
}
int main(){
    scanf("%s",in);
    pre_treat();
    manacher();
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是使用QT实现Manacher算法的代码,同时包括了QT窗口的输入和输出。 首先,我们需要在QT Creator中创建一个新的Qt Widgets项目,然后在mainwindow.cpp文件中添加下面的代码: ``` #include "mainwindow.h" #include "ui_mainwindow.h" #include <QString> #include <QDebug> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); connect(ui->pushButton, SIGNAL(clicked()), this, SLOT(findLongestPalindrome())); } MainWindow::~MainWindow() { delete ui; } void MainWindow::findLongestPalindrome() { QString str = ui->lineEdit->text(); QString result = longestPalindrome(str); ui->label->setText(result); } QString MainWindow::longestPalindrome(QString s) { QString t = "$#"; for (int i = 0; i < s.length(); ++i) { t += s.at(i); t += "#"; } int p[t.length()] = {0}; int mx = 0, id = 0, maxLength = 0, centerIndex = 0; for (int i = 1; i < t.length(); ++i) { p[i] = mx > i ? std::min(p[2 * id - i], mx - i) : 1; while (t[i + p[i]] == t[i - p[i]]) { ++p[i]; } if (mx < i + p[i]) { mx = i + p[i]; id = i; } if (maxLength < p[i]) { maxLength = p[i]; centerIndex = i; } } QString res; for (int i = centerIndex - maxLength + 1; i < centerIndex + maxLength; ++i) { if (t[i] != '#') { res += t[i]; } } return res; } ``` 在这段代码中,我们使用了QT的信号槽机制,将按钮的点击事件连接到了findLongestPalindrome()槽函数。该函数中,首先获取输入框中的字符串,然后调用longestPalindrome()函数来计算最长回文子串,最后将计算结果显示在label标签中。 longestPalindrome()函数实现了Manacher算法,对输入字符串进行预处理,并维护了变量p、mx、id、maxLength和centerIndex,最终返回最长回文子串。 下面是mainwindow.h文件的代码: ``` #ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); QString longestPalindrome(QString s); public slots: void findLongestPalindrome(); private: Ui::MainWindow *ui; }; #endif // MAINWINDOW_H ``` 最后,在mainwindow.ui文件中添加下面的控件: - QLineEdit:用于输入字符串。 - QPushButton:用于触发计算最长回文子串的按钮。 - QLabel:用于显示计算结果。 然后,将这些控件与对应的槽函数和变量进行连接,就可以运行这个程序了。 注意:为了能够正常使用QString类和std::min函数,需要在mainwindow.cpp文件中添加下面两行代码: ``` #include <QString> #include <algorithm> ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值