幾種回文算法的比較
前言
這是我的第一篇博文,獻給算法。
學習和研究算法可以讓人變得更加聰明。
算法的目標是以更好的方法完成任務。
更好的方法的具體指標是:
1. 花費更少的執行時間。
2. 花費更少的內存。
在對方法的不斷尋找,對規律的不斷探索中,個人的思考能力能夠被加強。當快捷的思考能力成為一種固有特征時,人就變得聰明起來。
研究算法其實是研究事物的規律。對事物的變化規律掌握的越準確、越細致、越深入,就能找到更好的算法。
在探索的過程當中,一定會經歷失敗。但是這種失敗是值得的,它為解決其它問題提供了基礎。
回文算法:
回文指從左往右和從由往左讀到相同內容的文字。比如: aba,abba,level。
回文具有對稱性。
回文算法的目標是把最長的回文從任意長度的文本當中尋找出來。比如:從123levelabc中尋找出level。
框架代碼
框架代碼包含除核心算法代碼的所有其他部分代碼。
1. main()函數,使用隨機數產生1M長度的字符串。然後調用核心算法代碼。
2. 運行時間統計函數,用於比較不同算法耗時的差別。
#include <vector> #include <iostream> #include <string> #include <minmax.h> #include <time.h> #include <Windows.h> #include<random> #include <assert.h> using namespace std; __int64 get_local_ft_time(){ SYSTEMTIME st; __int64 ft; GetLocalTime(&st); SystemTimeToFileTime(&st, (LPFILETIME) &ft); return ft; } int diff_ft_time_ms(__int64 subtracted, __int64 subtraction){ return(int)((subtracted - subtraction) / 10000); } int main() { string s = ""; srand(time(NULL)); for (int i = 0; i < 1024 * 1024; i++){ s += (char) ((rand() % 26) + ‘a‘); } // 此處調用回文算法函數。 //cin.get(); }
回文算法: 原始算法
原始算法指按照回文的原始定義,利用數據的對稱性(s[i - x] = s[i + x])來尋找回文的算法。
void palindrome_raw(string t) { cout << "palindrome_raw" << endl; __int64 start = get_local_ft_time(); int max = 0; // 最長回文的起點 int l_max = 1; // 最長回文的長度(l: length, 長度的意思) for (int i = 1; i < t.size(); i++) { // i為對稱點 int d = 1; // d為回文擴展半徑 while (i - d >= 0 && i + d < t.size() && t[i - d] == t[i + d]){ // 以i為中心對稱。aba d++; } d--; if (2 * d + 1 > l_max){ max = i - d; l_max = 2 * d + 1; } // 循環結束時d總不滿足判斷條件,所以減1 d = 0; // d為回文擴展半徑 while (i - d >= 0 && i + 1 + d < t.size() && t[i - d] == t[i + 1 + d]){ // 以i後面空隙為中心對稱。abba d++; } d--; if (2 * (d + 1) > l_max){ max = i - d; l_max = 2 * (d + 1); } } cout << t.substr(max, l_max) << " " << max << endl; __int64 end = get_local_ft_time(); cout << "處理時間: " << diff_ft_time_ms(end, start) << "ms" << endl; }
算法說明:
對每個數據位置i, 分別尋找
1. 以i為對稱點的回文。比如文本: aba,以b對稱。
2. 以i與i+1直接的空隙對稱的回文。比如文本abba,以bb之間的空隙對稱。
所以,對每個點,輪詢兩次。
回文算法: 馬拉車(Manacher)算法
馬拉車算法使用空間換取時間,把每個點的回文半徑存儲起來。為了避免輪詢兩次,算法把原始文本的每個字符讓固定字符(比如#)前後包圍起來,這樣,對於原始文本abcd,處理後的文本變成#a#b#c#d#。
算法把回文半徑存儲起來,在一個已經確定大的回文當中,右半部分的數據的回文與已經確定的左邊部分的回文具有對稱性,所以節省掉一部分輪詢的時間。
如上圖,m點的回文半徑已經確定是p[m],那麽對於m點右側的i點,總有一個沿m點對稱的j點。由於m點回文的對稱性,j點的回文與i點的回文在m回文的區域是一定對稱的。這是馬拉車算法規律的基礎。
代碼直接引用自: https://www.cnblogs.com/grandyang/p/4475985.html
void Manacher(string s) { cout << "Manacher" << endl; // Insert ‘#‘ string t = "$#"; for (int i = 0; i < s.size(); ++i) { t += s[i]; t += "#"; } // Process t vector<int> p(t.size(), 0); __int64 start = get_local_ft_time(); int mx = 0, id = 0, resLen = 0, resCenter = 0; for (int i = 1; i < t.size(); ++i) { p[i] = mx > i ? 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 (resLen < p[i]) { resLen = p[i]; resCenter = i; } } cout << s.substr((resCenter - resLen) / 2, resLen - 1) << " " << (resCenter - resLen) / 2 << endl; __int64 end = get_local_ft_time(); cout << "處理時間: " << diff_ft_time_ms(end, start) << "ms" << endl; }
回文算法: 自己嘗試的算法
把文本數據看做函數曲線,則有下面的規律:
1. 遞增或者遞減的區間內,一定沒有對稱性。
2. 恒值區間,一定有對稱性。
3. 遞增、遞減的屬性變化時,在最高點或最低點(拐點),可能存在對稱性。
4. 遞增或者遞減變化成恒值時,一定沒有對稱性。
根據以上的規律,寫出相應的代碼:
void palindrome_zjs(string t) { cout << "palindrome_zjs" << endl; __int64 start = get_local_ft_time(); int l = 0; // 起點l(left,左邊的意思) int s = 0; // 符號s(sign, 符號的意思),代表上升,下降或者平坦 (1, -1, 0) int max = 0; // 最長回文的起點 int l_max = 1; // 最長回文的長度(l: length, 長度的意思) for (int r = 1; r < t.size(); r++) { // 終點r(right, 右邊的意思) int s_n = t[r] - t[r - 1]; // 與前面一個點比較 if (s_n){ s_n = s_n > 0 ? 1 : -1; // 上升、下降或者不變? } if (s_n == s) { // 處在遞增、遞減或者恒值的階段中,此時不作處理 ; } else if(s_n == 0){ // 由遞增、遞減變成不變 l = r - 1; // 新線段的起點 s = s_n; // 增減屬性 } else if (s == 0) { // 不變的區域結束。恒值區總是自對稱,比如aa, aaa int i = 1; int right = r - 1; // right指向最後一個恒值區的位置 while (l - i >= 0 && right + i < t.size() && t[l - i] == t[right + i]){ // 沿恒值區向左右擴展即可。 i++; } i--; // 循環結束時i總不滿足判斷條件,所以減1 if (right + i - (l - i) + 1 > l_max){ max = l - i; l_max = right + i - max + 1; } l = r; // 新線段的起點 s = s_n; // 增減屬性 } else if (s_n != 0) { // 遞增變成遞減,或者遞減變成遞增 int i = 1; int c = r - 1; // c是拐點(最低或者最高點)。 while (c - i >= 0 && c + i < t.size() && t[c - i] == t[c + i]){ // 拐點為對稱點。 i++; } i--; // i總不滿足條件,所以減1 if (2 * i + 1 > l_max){ max = c - i; l_max = 2 * i + 1; // + 1是加拐點本身 } l = r; // 新線段的起點 s = s_n; // 增減屬性 } assert(1); } cout << t.substr(max, l_max) << " " << max << endl; __int64 end = get_local_ft_time(); cout << "處理時間: " << diff_ft_time_ms(end, start) << "ms" << endl; }
幾種算法的比較
算法 格外的內存 運算時間(1M字節的隨機文本)
原始算法 不需要 400ms
馬拉車算法 2倍的文本 1311ms
自己的代碼 不需要 300ms
結果讓人費解,為什麽馬拉車算法如此耗時?
如果馬拉車算法又耗內存又耗時間,使用這種算法的意義在哪裏呢?
如果馬拉車算法的時間級數為O(n),那麽1M個循環,其運行時間應在在us級才對。粗略地說,1秒可以出來1M*1M的指令呢。
如果有大俠路過,請賜教。
幾種回文算法的比較