1. 程式人生 > >幾種回文算法的比較

幾種回文算法的比較

color .html 技術 img span str right 指向 https

前言

這是我的第一篇博文,獻給算法。

學習和研究算法可以讓人變得更加聰明。

算法的目標是以更好的方法完成任務。

更好的方法的具體指標是:

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的指令呢。

如果有大俠路過,請賜教。

幾種回文算法的比較