manacher算法中需要知道的概念:
回文半径: 回文中心 到 回文边界的距离.
回文半径数组: radius[i]表示以 i 为回文中心的最大回文半径.
回文最右边界: 出现的回文边界中最右的位置.
首次回文中心: 回文最右边界首次出现时的回文中心.
首次回文左边界: 回文最右边界首次出现时的回文左边界.
i镜像点i': 若i位置在长回文串中, 那么i位置 以 长回文串的回文中心 作出的 对称点.
manacher 流程:
预处理字符串: 例如将s1="sasd"处理成s2="#s#a#s#d#".
将回文最右边界和首次回文中心都置为最左边界即-1索引.
遍历处理后的字符串s2, 根据 i位置 与 回文最右边界 的关系可分为2种情况:
i位置在回文最右边界外面:
radius[i] = 暴力中心扩展后的回文半径.
i位置在回文最右边界里面(包括回文最右边界), 则根据 i镜像点i'的回文左边界 与 首次回文左边界 的关系又可分为3种情况:
i'的回文左边界在首次回文左边界里面(不包括首次回文左边界):
radius[i] = i镜像点i'的最长回文半径(即是radius[i']).
i'的回文左边界在首次回文左边界外面:
radius[i] = 回文最右边界与i位置的距离.
i'的回文左边界在首次回文左边界的边界上:
radius[i] = 暴力中心扩展后的回文半径.
经过上面得到radius[i]后,根据radius[i]的状况更新回文最右边界,首次回文中心,首次回文左边界,全局最大回文半径等等信息.
创建3个文件:manacher.h、manacherArray.c、manacherArrayTest.c。
manacherArray.h
#ifndef MANACHER_ARRAY_H_
#define MANACHER_ARRAY_H_
// 功能: 最长回文子串的长度.
// 参数: s(字符串首地址).
// 返回: 最长回文子串的长度.
// 注意: 当 s=NULL 时, 将错误退出程序.
extern int32_t manacherArray( char s[] );
#endif
manacherArray.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include "manacherArray.h"
// 功能: 打印错误信息后就错误退出程序.
// 参数: expression(错误判断表达式), message(需打印的错误信息).
// 返回: 无.
// 注意: 当表达式 expression 为真时, 才触发.
#define ERROR_EXIT( expression, message )
if( (expression) ) {
fprintf( stderr, "
error location: file = %s, func = %s, line = %d.
",
__FILE__, __func__, __LINE__ );
fprintf( stderr, "error message: %s%s.
a",
(message) != NULL ? (message) : __func__,
(message) != NULL ? "" : " function error" );
exit( EXIT_FAILURE );
}
萌新版本。
int32_t manacherArray( char s[] ) { char *tempA = NULL, ch = '#'; int *radiusA = NULL; // 回文半径数组. int right = -1; // 回文最右边界. int center = -1; // 首次回文中心. int left = -1; // 以 center 为回文中心的回文左边界. int mirror = -1; // i 以 center 为中心的镜像 i'. int mirrorLeft = -1; // 以 i' 为回文中心的回文左边界. int i = 0, j = 1, answer = 0, slen = 0; ERROR_EXIT( s == NULL, "NullPointerException" ); slen = strlen( s ); tempA = malloc( sizeof(*tempA) * (slen * 2 + 2) ); for( tempA[0] = ch; (tempA[j++] = s[i++]) != ' '; tempA[j++] = ch ) {} radiusA = malloc( sizeof(*radiusA) * (slen * 2 + 1) ); for( i = 0; tempA[i] != ' '; ++i ) { if( i > right ) { // i位置在回文最右边界外, 暴力扩. for( j = 1; i - j >= 0 && tempA[i - j] == tempA[i + j]; ++j ) {} radiusA[i] = j - 1; } else { left = center - radiusA[center]; // 以 center 为回文中心的回文左边界. mirror = center * 2 - i; // i 以 center 为中心的镜像 i'. mirrorLeft = mirror - radiusA[mirror]; // 以 i' 为回文中心的回文左边界. // i位置在回文最右边界里面,根据以 i' 为回文中心的回文左边界又可分为3种情况. if( mirrorLeft == left ) { for( j = right - i + 1; i - j >= 0 && tempA[i - j] == tempA[i + j]; ++j ) {} radiusA[i] = j - 1; } else if( mirrorLeft < left ) { radiusA[i] = right - i; } else { radiusA[i] = radiusA[mirror]; } } if( i + radiusA[i] > right ) { // 出现较右的回文右边界. center = i; right = i + radiusA[i]; } if( radiusA[i] > answer ) { // 出现较大的回文半径. answer = radiusA[i]; } } free( radiusA ); free( tempA ); return answer; }
左神版本。
char *manacherArray( char s[] ) { char *ta = NULL, ch = '#'; int *radius = NULL; // 回文半径数组. int right = -1; // 出现过的回文最右边界. int center = -1; // 第一次出现回文最右边界时的回文中心. int answer = -1; int i = 0, j = 1, slen = 0; ERROR_EXIT( s == NULL, "NullPointerException" ); slen = strlen( s ); ta = malloc( sizeof(*ta) * (slen * 2 + 2) ); for( ta[0] = ch; (ta[j++] = s[i++]) != ' '; ta[j++] = ch ) {} radius = malloc( sizeof(*radius) * (slen * 2 + 1) ); for( i = 0; ta[i] != ' '; ++i ) { // i在回文最右边界外,让 radius[i] 默认取只有自己本身距离1. // i在回文最右边界内,让 radius[i] 默认取较短的距离, 因为较长距离会越界或不是回文. // center * 2 - i 表示 i位置的镜像i'. radius[i] = i >= right ? 1 : MIN( radius[center * 2 - i], right - i ); // 为什么 while 循环 不用判断 i + radius[i] < strlen( ta ) 呢? // ∵ 循环中判断了 i - radius[i] >= 0. // ∴ 在循环中 i + radius[i] 是合法索引不会引发越界. // ∵ i 和 radius[i] 不会是负数 且 radius[i]在循环开始前的默认最小值是1. // ∴ i - radius[i] 位置 永远在 i + radius[i] 位置 前面. // ∵ radius[i] 在循环中的增长值是1. // ∴ ' '出现的位置一定是在 i + radius[i] 位置. while( i - radius[i] >= 0 && ta[i - radius[i]] == ta[i + radius[j]] ) { ++radius[i]; } --radius[i]; // 减去i位置自己本身距离1. if( i + radius[i] > right ) { // 出现较右的回文右边界. center = i; right = i + radius[i]; } if( radius[i] > answer ) { // 出现较大的回文半径. answer = radius[i]; } } free( radius ); free( ta ); return answer; }
manacherArrayTest.c
实现对数器
manacherArrayTest.sh
# !/bin/bash
for(( i = 1; i <= 21; ++i )) do
printf "%02d" ${i}
echo -n ______________
./manacherArrayTest
done
参考: https://www.zhihu.com/question/37289584
参考: https://segmentfault.com/a/1190000008484167
参考书籍:
[1] 严蔚敏,吴伟民.数据结构(C语言版)[M].北京:清华大学出版社,2007.
[2] 高一凡.《数据结构》算法实现及解析[M].第二版.西安:西安电子科技大学出版社,2004.
[3] Andrew Binstock,John Rex著,陈宗斌等译.Practical Algorithms for Programmers.北京:机械工业出版社,2009.
重点参考人物: 左神.