manacher 線性查找演算法 manacher演算法中需要知道的概念: 迴文半徑: 迴文中心 到 迴文邊界的距離. 迴文半徑數組: radius[i]表示以 i 為迴文中心的最大迴文半徑. 迴文最右邊界: 出現的迴文邊界中最右的位置. 首次迴文中心: 迴文最右邊界首次出現時的迴文中心. 首次迴文左邊 ...
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, "\nerror location: file = %s, func = %s, line = %d.\n", \
__FILE__, __func__, __LINE__ ); \
fprintf( stderr, "error message: %s%s.\n\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++]) != '\0'; tempA[j++] = ch ) {} radiusA = malloc( sizeof(*radiusA) * (slen * 2 + 1) ); for( i = 0; tempA[i] != '\0'; ++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++]) != '\0'; ta[j++] = ch ) {} radius = malloc( sizeof(*radius) * (slen * 2 + 1) ); for( i = 0; ta[i] != '\0'; ++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. // ∴ '\0'出現的位置一定是在 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.
重點參考人物: 左神.