社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
Manacher算法,俗称马拉车算法,是一种比较高效的回文串查找算法。
对于这个算法,我主要是从https://www.cnblogs.com/fan1-happy/p/11166182.html这篇博客中学习的,不过我觉得这篇文章太…应该说中二吧,反正我看得是有点小小的不习惯,当然内容还是有质量的。
Manacher算法主要是通过对已经查找过的回文串来缩短对未查找过的回文串的查找速度,有点dp的思想,也是很巧妙的思路。
先说一下两种回文串的定义:
奇回文:长度为奇数的回文串(例子:oopoo);
偶回文:长度为偶数的回文串(例子:oppo)。
Manacher算法呢是以串中每一个字符为中心点查找回文串的,也就是查找奇回文,那对于偶回文的情况又应该怎么解决呢?首先需要对原串进行一些修改,使得对于串中的每个可能存在的回文串都可以通过以查找奇回文的方式来进行查找。
具体的方式就是在原串的头尾,以及每个字符之间插入一个原串中不存在的字符。
上面说的那篇博客里呢那位博主还在插入后的串中的前尾分别插入了两个串中不存在的字符,这种方式应该可以叫做“哨兵”吧,这里就是为了防止越界访问,其实更重要的可以减少判断语句,待会在代码中会说。
接下来是最核心的部分了。按顺序遍历新串中的每一个字符,以该字符为中心查找回文串。
这里根据以查找过的回文串来提高查找效率的方式就是通过回文串的特点,以及对称点的知识来进行的。
如图:i是当前循环要进行判断的中心点(也就是要计算以该点为中心的回文串长度);id是遍历过程中所记录到的以id为中心点的回文区间的右端点最靠右的回文中心点的位置,其回文区间的右端点为maxright - 1;j是i相对于id的对称点。(对称点位置的计算方法:j = 2 * id - i)
用数组palindradius[i]来记录以点i为中心点的回文串的半径,可以得到原串中以该点为中心点时的回文串长度就为(palindradius[i] - 1),当(palindradius[i] - 1)为奇数时,该回文串为奇回文;为偶数时,该回文串为偶回文,且中心点不存在。
根据回文串的对称性就可以得到:
palindradius[i] = min(palindradius[j], maxright - i)
细心的同学就可以会发现,当上式结果为 palindradius[i] = maxright - i 的时候所得到的回文串半径有可能并不是最长的回文串半径,此时就需要继续向左右两边比较。
#include<iostream>
#include<vector>
#include<string>
using namespace std;
#define get_min(a,b) ((a) < (b) ? (a) : (b))
int manacher(const string &str) {
string nstr(2 * str.size() + 3, '#');
nstr[0] = '$'; nstr[nstr.size() - 1] = '&';
for (size_t i = 0, npos = 2; i < str.size(); ++i) {
nstr[npos] = str[i];
npos += 2;
}
vector<int> palindradius(nstr.size(), 0);
int id = -1, maxright = -1;
int maxlength = 0;
for (int i = 1; i < nstr.size() - 1; ++i) {
if (i < maxright)
palindradius[i] = get_min(palindradius[2 * id - i], maxright - i);
while (nstr[i + palindradius[i]] == nstr[i - palindradius[i]]) //首尾插入两个不相同的字符就是方便了这里不用进行越界判断
++palindradius[i];
if (maxright < i + palindradius[i]) {
id = i;
maxright = i + palindradius[i];
}
if (palindradius[i] - 1 > maxlength) maxlength = palindradius[i] - 1;
}
return maxlength;
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!