字符串匹配:two-way算法

字符串匹配是一維模式匹配的特例;是查找在字符串text中是否存在字符串pattern的一種操作。
字符串匹配的形式化定義大體如下:
    設字符集合A。text和pattern是定義在字符集合A上的兩個字符串;設|text|是text的長度,
    |pattern|是pattern的長度。找到非負整數pos(pos < |text|-|pattern|),使等式:
        pattern[i] = text[pos+i]     (i < |pattern|)            (1)
    對所有使pattern[i]有意義的非負整數i成立。

有三個著名的字符串匹配算法:
1. Knuth, Morris, and Pratt的KMP算法;
2. Boyer and Moore的BM算法;
3. two-way算法;
這三個算法的時間複雜度都是O(n)。空間複雜度前兩個是O(n),two-way算法是O(1)。
two-way是對前兩個算法的一種改進。
我機器上的GNU Lib C中的strstr函數是用two-way算法實現。

two-way算法主要依據Critical Factorization理論。以下簡述Critical Factorization理論。
詳細敘述和證明可在參考文獻中找到。

要理解Critical Factorization理論,先要理解字符串的period:
    設w是定義在字符集A上的非空字符串。設|w|是w的長度。存在正整數p,對所有滿足模p同餘的
    非負整數i,j (i,j < |x|),等式:
        w[i] = w[j]                                       (2)
    都成立。則p稱爲字符串w的period。w最小的period記作p(w)。

    若存在正整數wp使等式:
        w[i] = w[wp+i]                                     (3)
    對所有使等式兩邊有意義的i都成立,那麼wp稱爲字符串w的weak period。

    若正整數lp稱爲非空字符串w在位置l的local period,則有定義在A上的字符串u, v, x,使
    w=uv; |u|=l+1;|x|=lp;並使字符串r,s滿足下列條件之一:
        1. u = rx && v = xs
        2. x = ru && v = xs (r不是空字符串)
        3. u = rx && x = vs (s不是空字符串)
        4. x = ru && x = vs (r,s不是空字符串)
    字符串w在位置l的最小local period記作p(w, l)。若此時p(w, l)=p(w),則字符串對(u, v)
    稱爲w的Critical Factorization, l稱爲critical position。

two-way算法的第一步就是找到Critical Factorization。使用maximal suffix方法,依據如下理論:
    字符串w的子字符串記作w[i..e)=w[i]w[i+1]...w[e-1]。w的最大後綴定義爲:
    存在整數i(i >= 0 && i < |w|),使得對字符串w有意義的所有整數j滿足下式:
        u[j..|w|) <= u[i..|w|)                          (4)
    字符串的逆向最大後綴的定義與上述定義相似,只是將(4)式改成:
        u[j..|w|) >= u[i..|w|)                          (5)

    可以證明字符串w(|w| >= 2)至少有一個critical factorization,且l < p。此外設v是w的
    maximal suffix,且w = uv。設m是w的tilde maximal suffix,且w = nm。如果v <= m那麼
    (n,m)是w的是critical factorization。如果v > m那麼(u, v)是w的critical factorization。

two-way算法的執行過程可參見我寫的實驗代碼,這段代碼只是對算法的簡單直接演繹。
對two-way完整和優化的演繹可參見GNU Lib C中的strstr函數,它對許多特殊情況做了適當優化。
有關算法正確性的證明可參見參考文獻。

我對代碼進行一下簡要說明。tw_strstr的語義和C標準庫strstr的語義基本一樣,區別在於tw_strstr
返回的是數組索引而不是指針,如果沒找到返回-1。i, j分別是pattern的critical position
兩邊迭代的索引,s用於記錄pattern前綴的匹配情況,pos是每次匹配的基準點,p是local period。
"if(!memcmp(pat, pat + p, l + 1))"的目的是監測子串pattern[0..l]是否是子串pattern[l+1..l+p]
的後綴。要注意算法對變量pos和s的處理。之所以可以作這樣的處理,是因爲critical
factorization所具有的特性。

編譯:
    g++ -g -W -Wall -Wextra -o mytest main.cpp
執行:
    ./mytest

參考文獻:
MAXIME CROCHEMORE, DOMINIQUE PERRIN有關two-way的論文;
http://www-igm.univ-mlv.fr/~lecroq/string/node26.html#SECTION00260;
GNU Lib C strstr函數的源代碼;


=====================================================================================

main.cpp:

// 2010年10月16日 星期六 20時34分02秒 CST
// author: 李小丹(Li Shao Dan) 字 殊恆(shuheng)
// K.I.S.S
// S.P.O.T
/*
*
* Copyright © 2009 李小丹(Li Shao Dan)
*
* Permission to use, copy, modify, distribute and sell this software
* and its documentation for any purpose is hereby granted without fee,
* provided that the above copyright notice appear in all copies and
* that both that copyright notice and this permission notice appear
* in supporting documentation. 李小丹(Li Shao Dan) makes no
* representations about the suitability of this software for any
* purpose.  It is provided "as is" without express or implied warranty.
*/

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <ctime>

using namespace std;

#define TEXT_BUFSIZE 32
#define PAT_BUFSIZE 128

#define MAX(a, b) ((a) > (b) ? (a) : (b))


static void generate_text(char *, int);
static int max_suffix(const char *, int, int *);
static int max_suffix_tilde(const char *, int, int *);
static int tw_strstr(const char *, const char *);

int main()
{
    srand(time(0));

    char text[TEXT_BUFSIZE];
    char pat[PAT_BUFSIZE];
    int fp;

    generate_text(text, TEXT_BUFSIZE);
    cout << "text: " << text << endl;
    cout << "pattern: ";
    while(cin >> pat) {
        if((fp = tw_strstr(text, pat)) >= 0) {
            cout << "Position: " << fp << endl;
            cout << "Sub string: " << &text[fp] << endl;
        } else {
            cout << "Text: " << text << endl;
            cout << "Pattern: " << pat << endl;
            cout << "Can't find!" << endl;
        }
        cout << "============================================/n";
        generate_text(text, TEXT_BUFSIZE);
        cout << "text: " << text << endl;
        cout << "pattern: ";
    }
    return 1;
}

static int tw_strstr(const char *text, const char *pat)
{
    int tlen = strlen(text);
    int plen = strlen(pat);

    int l1, l2, p1, p2;
    l1 = max_suffix(pat, plen, &p1);
    l2 = max_suffix_tilde(pat, plen, &p2);
    int l, p;
    if(l1 > l2) {
        l = l1;
        p = p1;
    } else {
        l = l2;
        p = p2;
    }

    int pos = -1;
    int s = -1;
    int i, j;
    if(!memcmp(pat, pat + p, l + 1)) {
        while(pos + plen <= tlen) {
            i = MAX(l, s) + 1;
            while(i < plen && pat[i] == text[pos+i])
                ++i;
            if(i < plen) {
                pos += MAX(i - l, s - p + 1);
                s = -1;
            } else {
                j = l;
                while(j > s && pat[j] == text[pos+j])
                    --j;
                if(j <= s)
                    return pos;
                pos += p;
                s = tlen - p - 1;
            }
        }
    } else {
        p = MAX(l + 1, plen - l - 1) + 1;
        while(pos + plen <= tlen) {
            i = l + 1;
            while(i < plen && pat[i] == text[pos+i])
                ++i;
            if(i < plen) {
                pos += (i - l);
            } else {
                j = l;
                while(j >= 0 && pat[j] == text[pos+j])
                    --j;
                if(j < 0)
                    return pos;
                pos += p;
            }
        }
    }
    return -1;
}

static int max_suffix(const char *buf, int len, int *p)
{
    int i = -1, j = 0;
    int k = 1;
    *p = 1;
    char a, b;

    while(j + k < len) {
        a = buf[i + k];
        b = buf[j + k];
        if(b < a) {
            j += k;
            k = 1;
            *p = j - i;
        } else if(b == a) {
            if(k == *p) {
                j += *p;
                k = 1;
            } else {
                ++k;
            }
        } else {
            i = j;
            ++j;
            k = *p = 1;
        }
    }
    return i;
}

static int max_suffix_tilde(const char *buf, int len, int *p)
{
    int i = -1, j = 0;
    int k = 1;
    *p = 1;
    char a, b;

    while(j + k < len) {
        a = buf[i + k];
        b = buf[j + k];
        if(b > a) {
            j += k;
            k = 1;
            *p = j - i;
        } else if(b == a) {
            if(k == *p) {
                j += *p;
                k = 1;
            } else {
                ++k;
            }
        } else {
            i = j;
            ++j;
            k = *p = 1;
        }
    }
    return i;
}

static void generate_text(char *text, int len)
{
    static char alphabeta[] = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    static int ablen = sizeof(alphabeta) - 1;

    for(int i = 0; i < len - 1; ++i)
        text[i] = alphabeta[rand() % ablen];
    text[len-1] = 0;
}

發佈了43 篇原創文章 · 獲贊 12 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章