字符串匹配:two-way算法

本文介绍了一种高效的字符串匹配算法——Two-Way算法,并详细解释了其背后的CriticalFactorization理论,包括如何寻找字符串的周期(period)、弱周期(weak period)及局部周期(local period)。同时介绍了该算法的实现过程,特别是如何通过maximalsuffix方法找到关键分解(critical factorization)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

字符串匹配是一维模式匹配的特例;是查找在字符串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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值