數據結構- 串的模式匹配算法:BF和 KMP算法

Brute-Force算法的思想

1.BF(Brute-Force)算法  

Brute-Force算法的基本思想是:

1) 從目標串s 的第一個字符起和模式串t的第一個字符進行比較,若相等,則繼續逐個比較后續字符,否則從串s 的第二個字符起再重新和串t進行比較。

2) 依此類推,直至串t 中的每個字符依次和串s的一個連續的字符序列相等,則稱模式匹配成功,此時串t的第一個字符在串s 中的位置就是t 在s中的位置,否則模式匹配不成功。

Brute-Force算法的實現   

1.jpg

c語言實現:

// Test.cpp : Defines the entry point for the console application.    
//    
#include "stdafx.h"    
#include <stdio.h>    
#include "stdlib.h"  
#include <iostream>  
using namespace std;  
  
//宏定義      
#define TRUE   1      
#define FALSE   0      
#define OK    1      
#define ERROR   0    
  
#define  MAXSTRLEN 100  
  
typedef char    SString[MAXSTRLEN + 1];  
/************************************************************************/  
/*  
 返回子串T在主串S中第pos位置之后的位置,若不存在,返回0 
*/  
/************************************************************************/  
int BFindex(SString S, SString T, int pos)  
{  
    if (pos <1 ||  pos > S[0] ) exit(ERROR);  
    int i = pos, j =1;  
    while (i<= S[0] && j <= T[0])  
    {  
        if (S[i] == T[j])  
        {  
            ++i; ++j;  
        } else {  
            i = i- j+ 2;  
            j = 1;  
        }  
    }  
    if(j > T[0]) return i - T[0];  
    return ERROR;  
}  
  
  
  
void main(){  
    SString S = {13,'a','b','a','b','c','a','b','c','a','c','b','a','b'};  
    SString T = {5,'a','b','c','a','c'};  
    int pos;  
    pos = BFindex( S,  T, 1);  
    cout<<"Pos:"<<pos;  
}

2.KMP算法

2.1 算法思想:

每當一趟匹配過程中出現字符比較不等時,不需要回溯I指針,而是利用已經的帶的“部分匹配”的結果將模式向右滑動盡可能遠的一段距離后,繼續進行比較。

即盡量利用已經部分匹配的結果信息,盡量讓i不要回溯,加快模式串的滑動速度。

2.jpg

需要討論兩個問題:
①如何由當前部分匹配結果確定模式向右滑動的新比較起點k?
② 模式應該向右滑多遠才是高效率的?

現在討論一般情況:

假設 主串:s: ‘s(1)  s(2) s(3) ……s(n)’ ;  模式串 :p: ‘p(1)  p(2) p(3)…..p(m)’

現在我們假設 主串第i個字符與模式串的第j(j<=m)個字符‘失配’后,主串第i個字符與模式串的第k(k<j)個字符繼續比較。

此時,s(i)≠p(j):

3.jpg

由此,我們得到關系式:即得到到1 到  j -1 "部分匹配"結果:

 ‘P(1)  P(2) P(3)…..P(j-1)’   =    ’ S(i-j+1)……S(i-1)’

 從而推導出k 到 j- 1位的“部分匹配”:即Pj-1j-k=S前i-1~i- (k -1))位             

  ‘P(j – k + 1) …..P(j-1)’  =  ’S(i-k+1)S(i-k+2)……S(i-1)’

由于s(i)≠p(j),接下來s(i)將與p(k)繼續比較,則模式串中的前(k-1)個字符的子串必須滿足下列關系式,并且不可能存在  k’>k  滿足下列關系式:(k<j)

4.jpg

有關系式: 即(P的前k- 1 ~ 1位= S前i-1~i-(k-1) )位 ) ,:

‘P(1) P(2)  P(3)…..P(k-1)’ = ’S(i-k+1)S(i-k+2)……S(i-1)’

現在我們把前面總結的關系綜合一下,有:

5.jpg

由上,我們得到關系:

‘p(1)  p(2)  p(3)…..p(k-1)’  =   ‘p(j – k + 1) …..p(j-1)’ 

      反之,若模式串中滿足該等式的兩個子串,則當匹配過程中,主串中的第i 個字符與模式中的第j個字符等時,僅需要將模式向右滑動至模式中的第k個字符和主串中的第i個字符對齊。此時,模式中頭k-1個字符的子串‘p(1)  p(2)  p(3)…..p(k-1)’  必定與主串中的第i 個字符之前長度為k-1 的子串  ’s(j-k+1)s(j-k+2)……s(j-1)’相等,由此,匹配僅需要從模式中的第 k 個字符與主串中的第 i 個字符比較起 繼續進行。      若令 next[j] = k ,則next[j] 表明當模式中第j個字符與主串中相應字符“失配”時,在模式中需要重新和主串中該字符進行的比較的位置。由此可引出模式串的next函數:

根據模式串P的規律:  ‘p(1)  p(2)  p(3)…..p(k-1)’  =   ‘p(j – k + 1) …..p(j-1)’ 

由當前失配位置j(已知) ,可以歸納計算新起點k的表達式。

1.jpg

由此定義可推出下列模式串next函數值:

7.jpg

模式匹配過程:

1.jpg

KMP算法的實現:

第一步,先把模式T所有可能的失配點j所對應的next[j]計算出來;

第二步:執行定位函數Index_kmp(與BF算法模塊非常相似)

  1. int KMPindex(SString S, SString T, int pos)  
    {  
        if (pos <1 ||  pos > S[0] ) exit(ERROR);  
        int i = pos, j =1;  
        while (i<= S[0] && j <= T[0])  
        {  
            if (S[i] == T[j]) {  
                ++i; ++j;  
            } else {  
                j = next[j+1];  
            }  
        }  
        if(j > T[0]) return i - T[0];  
        return ERROR;  
    }

完整實現代碼:

// Test.cpp : Defines the entry point for the console application.    
//    
#include "stdafx.h"    
#include <stdio.h>    
#include "stdlib.h"  
#include <iostream>  
using namespace std;  
  
//宏定義      
#define TRUE   1      
#define FALSE   0      
#define OK    1      
#define ERROR   0    
  
#define  MAXSTRLEN 100  
  
typedef char    SString[MAXSTRLEN + 1];  
  
void GetNext(SString T, int next[]);  
int KMPindex(SString S, SString T, int pos);  
/************************************************************************/  
/*  
 返回子串T在主串S中第pos位置之后的位置,若不存在,返回0 
*/  
/************************************************************************/  
int KMPindex(SString S, SString T, int pos)  
{  
    if (pos <1 ||  pos > S[0] ) exit(ERROR);  
    int i = pos, j =1;  
    int next[MAXSTRLEN];  
    GetNext( T, next);  
    while (i<= S[0] && j <= T[0])  
    {  
        if (S[i] == T[j]) {  
            ++i; ++j;  
        } else {  
            j = next[j];  
        }  
    }  
    if(j > T[0]) return i - T[0];  
    return ERROR;  
}  
  
/************************************************************************/  
/*      求子串next[i]值的算法 
*/  
/************************************************************************/  
void GetNext(SString T, int next[])  
{   int j = 1, k = 0;  
    next[1] = 0;  
    while(j < T[0]){  
        if(k == 0 || T[j]==T[k]) {     
            ++j;  ++k;   next[j] = k;    
        } else {  
            k = next[k];   
        }  
    }  
}  
  
void main(){  
    SString S = {13,'a','b','a','b','c','a','b','c','a','c','b','a','b'};  
    SString T = {5,'a','b','c','a','c'};  
    int pos;  
    pos = KMPindex( S,  T, 1);  
    cout<<"Pos:"<<pos;  
}

k值僅取決于模式串本身而與相匹配的主串無關。

我們使用遞推到方式求next函數:
1)由定義可知:
     next[1] = 0;
2)  設 next[j] = k ,這個表面在模式串中存在下列關系:
    ‘P(1)  ….. P(k-1)’  =   ‘P(j – k + 1) ….. P(j-1)’ 
    其中k為滿足1< k <j的某個值,并且不可能存在k` > 滿足:
    ‘P(1)  ….. P(k`-1)’  =   ‘P(j – k` + 1) ….. P(j-1)’ 
    此時next[j+1] = ?可能有兩種情況:
   (1) 若Pk = Pj,則表明在模式串中:

  ‘P(1) ….. P(k)’  =   ‘P(j – k + 1) ….. P(j)’ 
          并且不可能存在k` > 滿足:  ‘P(1) ….. P(k`)’  =   ‘P(j – k` + 1) ….. P(j)’ 
          即next[j+1] = k + 1 推到=》:

         next[j+1] = next[j] + 1;

      (2)  若Pk數據結構- 串的模式匹配算法:BF和 KMP算法Pj 則表明在模式串中:

          ‘P(1) ….. P(k)’  數據結構- 串的模式匹配算法:BF和 KMP算法   ‘P(j – k + 1) ….. P(j)’ 
     此時可把next函數值的問題看成是一個模式匹配的問題,整個模式串即是主串又是模式串,
     而當前匹配的過程中,已有:

      Pj-k+1 = P1, Pj-k+2 = P2,… Pj-1 = Pk-1.
     則當Pk數據結構- 串的模式匹配算法:BF和 KMP算法Pj時應將模式向右滑動至以模式中的第next[k]個字符和主串中的第 j 個字符相比較。
     若next[k] = k`,且Pj= Pk`, 則說明在主串中的第j+1 個字符之前存在一個長度為k` (即next[k])的最長子串,和模式串
     從首字符其長度為看k`的子串箱等。即
       ‘P(1) ….. P(k`)’  =  ‘P(j – k` + 1) ….. P(j)’ 
     也就是說next[j+1] = k` +1 即
     next[j+1] = next[k] + 1
     同理,若Pj 數據結構- 串的模式匹配算法:BF和 KMP算法Pk` ,則將模式繼續向右滑動直至將模式串中的第next[k`]個字符和Pj對齊,
     … ,一次類推,直至Pj和模式中某個字符匹配成功或者不存在k`(1< k` < j)滿足,則:
     next[j+1] =1;

    1.jpg


  1. /************************************************************************/  
    /*      求子串next[i]值的算法 
    */  
    /************************************************************************/  
    void GetNext(SString T, int next[])  
    {   int j = 1, k = 0;  
        next[1] = 0;  
        while(j < T[0]){  
            if(k == 0 || T[j]==T[k]) {     
                ++j;  ++k;   next[j] = k;    
            } else {  
                k = next[k];   
            }  
        }  
    }

next 函數值究竟是什么含義,前面說過一些,這里總結。設在字符串S中查找模式串T,若S[m]!=T[n],那么,取T[n]的模式函數值next[n],1.       next[n] = 0 表示S[m]T[1]間接比較過了,不相等,下一次比較 S[m+1] T[1]2.       next[n] =1 表示比較過程中產生了不相等,下一次比較 S[m] T[1]。3.       next[n] = k >1 k<n, 表示,S[m]的前k個字符與T中的開始k個字符已經間接比較相等了,下一次比較S[m]T[k]相等嗎?4.       其他值,不可能。

注意:

(1)k值僅取決于模式串本身而與相匹配的主串無關。

(2)k值為模式串從頭向后及從j向前的兩部分的最大相同子串的長度。

(3)這里的兩部分子串可以有部分重疊的字符,但不可以全部重疊。

next[j]函數表征著模式P中最大相同前綴子串和后綴子串(真子串)的長度。

可見,模式中相似部分越多,則next[j]函數越大,它既表示模式T字符之間的相關度越高,也表示j位置以前與主串部分匹配的字符數越多。

即:next[j]越大,模式串向右滑動得越遠,與主串進行比較的次數越少,時間復雜度就越低(時間效率)。

轉自:http://blog.csdn.net/hguisu/article/details/7676786

原創文章,作者:s19930811,如若轉載,請注明出處:http://www.www58058.com/2800

(0)
s19930811s19930811
上一篇 2015-04-07 19:15
下一篇 2015-04-07 19:17

相關推薦

  • N_28文件類管理命令

    1.linux文件管理類命令有:mkdir ,rmdir,cp ,mv,rm,ls,vi,cat ,cut,sort,wc等 mkdir –make directories? (創建目錄) 用法 :mkdir [OPTION]… DIRECTORY… -P? 按需要創建目錄的父目錄; -v? 顯示創建的詳細過程; -m M…

    2017-12-09
  • free / buffer與cache

           前幾天看到有些伙伴傻傻分不清楚buffer與cache的用處,后來發現我也不能很清楚的說出來buffer與cache在不同的地方有什么不同之處,這里就總結了一些關于buffer于cache的區別,如有不完善的地方,請大家指出來。        說到buffer與ca…

    2017-07-17
  • Shell運算符

    Bash 支持很多運算符,包括算數運算符、關系運算符、布爾運算符、字符串運算符和文件測試運算符。 原生bash不支持簡單的數學運算,但是可以通過其他命令來實現,例如 awk 和 expr,expr 最常用。 expr 是一款表達式計算工具,使用它能完成表達式的求值操作。 例如,兩個數相加: #!/bin/bash val=`expr 2 + 2` echo …

    Linux干貨 2017-04-18
  • PHP中引用的詳解(引用計數、寫時拷貝)

    《PHP5中文手冊》內容中"引用的解釋"一文的摘要: 1. PHP中引用的特性 PHP中引用意味著用不同的名字訪問同一個變量內容,引用不是C的指針(C語言中的指針里面存儲的是變量的內容,在內存中存放的地址),是變量的另外一個別名或者映射。注意在 PHP 中,變量名和變量內容是不一樣的,因此同樣的內容可以有不同的名字。最接近的比喻是 Uni…

    Linux干貨 2015-04-10
  • 第一周 Linux基礎知識

    Linux基礎

    2018-03-18
  • N25-第十三周博客作業

    1、建立samba共享,共享目錄為/data,要求:(描述完整的過程) 1)共享名為shared,工作組為magedu;2)添加組develop,添加用戶gentoo,centos和ubuntu,其中gentoo和centos以develop為附加組,ubuntu不屬于develop組;密碼均為用戶名;3)添加samba用戶gentoo,centos和ubu…

    Linux干貨 2017-04-19
欧美性久久久久