Manacher 算法實現

Manachaer算法詳解

1.Manacher算法原理

下面介紹Manacher算法的原理與步驟。

首先,Manacher算法提供了一種巧妙地辦法,將長度爲奇數的迴文串和長度爲偶數的迴文串一起考慮,具體做法是,在原字符串的每個相鄰兩個字符中間插入一個分隔符,同時在首尾也要添加一個分隔符,分隔符的要求是不在原串中出現,一般情況下可以用#號。下面舉一個例子:


(1)Len數組簡介與性質

Manacher算法用一個輔助數組Len[i]表示以字符T[i]爲中心的最長迴文字串的最右字符到T[i]的長度,比如以T[i]爲中心的最長迴文字串是T[l,r],那麼Len[i]=r-i+1。

對於上面的例子,可以得出Len[i]數組爲:



Len數組有一個性質,那就是Len[i]-1就是該回文子串在原字符串S中的長度,至於證明,首先在轉換得到的字符串T中,所有的迴文字串的長度都爲奇數,那麼對於以T[i]爲中心的最長迴文字串,其長度就爲2*Len[i]-1,經過觀察可知,T中所有的迴文子串,其中分隔符的數量一定比其他字符的數量多1,也就是有Len[i]個分隔符,剩下Len[i]-1個字符來自原字符串,所以該回文串在原字符串中的長度就爲Len[i]-1。

有了這個性質,那麼原問題就轉化爲求所有的Len[i]。下面介紹如何在線性時間複雜度內求出所有的Len。

(2)Len數組的計算

首先從左往右依次計算Len[i],當計算Len[i]時,Len[j](0<=j<i)已經計算完畢。設P爲之前計算中最長迴文子串的右端點的最大值,並且設取得這個最大值的位置爲po,分兩種情況:

第一種情況:i<=P

那麼找到i相對於po的對稱位置,設爲j,那麼如果Len[j]<P-i,如下圖:



那麼說明以j爲中心的迴文串一定在以po爲中心的迴文串的內部,且j和i關於位置po對稱,由迴文串的定義可知,一個迴文串反過來還是一個迴文串,所以以i爲中心的迴文串的長度至少和以j爲中心的迴文串一樣,即Len[i]>=Len[j]。因爲Len[j]<P-i,所以說i+Len[j]<P。由對稱性可知Len[i]=Len[j]。

如果Len[j]>=P-i,由對稱性,說明以i爲中心的迴文串可能會延伸到P之外,而大於P的部分我們還沒有進行匹配,所以要從P+1位置開始一個一個進行匹配,直到發生失配,從而更新P和對應的po以及Len[i]。



第二種情況: i>P

如果i比P還要大,說明對於中點爲i的迴文串還一點都沒有匹配,這個時候,就只能老老實實地一個一個匹配了,匹配完成後要更新P的位置和對應的po以及Len[i]。



2.時間複雜度分析

Manacher算法的時間複雜度分析和Z算法類似,因爲算法只有遇到還沒有匹配的位置時才進行匹配,已經匹配過的位置不再進行匹配,所以對於T字符串中的每一個位置,只進行一次匹配,所以Manacher算法的總體時間複雜度爲O(n),其中n爲T字符串的長度,由於T的長度事實上是S的兩倍,所以時間複雜度依然是線性的。





mx:最右迴文串 右端下一個字符
p0:最右迴文串中心
p[i]:str[i]的擴充長度

關鍵代碼

p[i] = mx > i ? min(p[2 * p0 - i], mx - i) : 1;

while ((i - p[i] >= 0 && i + p[i] < len)
&& (NewStr[i - p[i]] == NewStr[i + p[i]]))
{
++p[i];

}

#include<iostream>
#include<string>
#include<cstring>
#include<algorithm>
#include<iterator>
using namespace std;
void InitString(string& Str);
int Manacher(const string& Str);
void InitString(string& Str)
{
	if (Str.empty())
	{
		return;
	}
	//string ret(0, 2 * Str.length() + 1);
	string ret="#";
	ret.reserve(2*Str.length()+1);
	for (size_t i = 0; i < Str.length(); i++)
	{
		ret = ret + Str[i] + "#";
	}
	Str = ret;
}

int Manacher(const string& Str)
{
	if (Str.empty())
	{
		return 0;
	}
	string NewStr = Str;
	InitString(NewStr);
	cout << "newStr:   " << NewStr << endl;
	int len = (int)NewStr.length();
	int* p = new int[NewStr.length()]();
	int ans = 0, i = 0, mx = 0, p0 = 0;
	//mx 表示最右邊字符的下一個
	for (i = 0; i < len; ++i)
	{
		p[i] = mx > i ? min(p[2 * p0 - i], mx - i) : 1;
		if (mx - i > p[2 * p0 - i])
		{//這一句加上會加速尋找過程
			continue;
		}
		while ((i - p[i] >= 0 && i + p[i] < len)
			&& (NewStr[i - p[i]] == NewStr[i + p[i]]))
		{//用p[i]作爲循環變量是很巧妙的
			++p[i];
		}
		if (i + p[i] > mx)
		{
			//更新mx 與p0 
			mx = p[i] + i;
			p0 = i;
		}
		ans = max(p[i], ans);
	}
	copy(p, p + len, ostream_iterator<int>(cout, " "));
	return ans - 1;
}
int main(void)
{
	string Str = "1221";
	cout<<"\nManacher: "<<Manacher(Str)<<endl;
	Str = "111";
	cout << "\nManacher: " << Manacher(Str) << endl;
	return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章