淺談KMP演算法及其next[]數組

来源:http://www.cnblogs.com/Onlynagesha/archive/2016/04/10/5375333.html
-Advertisement-
Play Games

KMP演算法是眾多優秀的模式串匹配演算法中較早誕生的一個,也是相對最為人所知的一個。 演算法實現簡單,運行效率高,時間複雜度為O(n+m)(n和m分別為目標串和模式串的長度),比蠻力演算法的O(nm)快了許多。 理解KMP演算法,關鍵是理解其中的精髓——next[]數組。 (統一起見,下文將目標字元串記作ob ...


KMP演算法是眾多優秀的模式串匹配演算法中較早誕生的一個,也是相對最為人所知的一個。

演算法實現簡單,運行效率高,時間複雜度為O(n+m)(n和m分別為目標串和模式串的長度),比蠻力演算法的O(nm)快了許多。

理解KMP演算法,關鍵是理解其中的精髓——next[]數組。

 

(統一起見,下文將目標字元串記作obj,將模式字元串記作pattern,這與後面的程式代碼是一致的)

我們給一個字元串S定義一個next值,記作next(S),next(S)=n表示:

(1)S的前n個字元構成的首碼,和後n個字元的尾碼是相等的

(2)n是滿足(1)條件的極大值。

如果不存在一個滿足(1)條件的n,那麼記next(S)=0。

例如,字元串“GACC”的next值為0,“GACCGG”的next值為1(極大前(後)綴為“G"),

“GACCGGACC”的next值為4(極大前(後)綴為”GACC“),“GAGAG”的next值為5。(極大前(後)綴就是它本身)。

而next[]數組,記錄的則是pattern的所有首碼的next值。

 

next數組的作用,就是在模式匹配的過程中,儘可能減少模式串的偏移量。

下令obj=”GACGAACGACCGACGACGCCGACGAC“(O__O"…),pattern=”GACGCCG“。

模式匹配的流程如下:

設立兩個”游標“i和j,分別指向obj串和pattern串當前正在檢查的字元,初始時令i=j=0。

首先檢查第一個字元,若obj[i]==pattern[j],那麼i++,j++。直到遇到obj[i]!=pattern[j]的情形。

GACGAACGACCGACGACGCCGACGAC

GACGCCG

前4個字元檢查通過,但第5個字元(i=j=4)出了問題。

朴素的做法是讓i和j都回退,對於此例,回退至i=1,j=0,然而這樣無疑會做許多重覆的計算。

而KMP演算法的做法則是:只移動pattern。而移動的方法,則關係到演算法的效率甚至正確性。

我們的原則是:保證正確的前提下,使得重覆判斷的次數儘可能少。

最佳的做法是將j移動到next[j-1]的位置。

(1)由next的性質(首碼等於尾碼的極大值)可知,這樣可以省去儘可能多的判斷

(2)並且可以保證這樣做是正確的

此時j=4,而next[j-1]=next[3]=1,所以令j=1,再次進行比較。

GACGAACGACCGACGACGCCGACGAC

------GACGCCG

比對通過。令i=5,j=2。

GACGAACGACCGACGACGCCGACGAC

------GACGCCG

next[1]=0,所以令j=0。

GACGAACGACCGACGACGCCGACGAC

----------GACGCCG

即使回退到第一位也沒有出現字元相等的情形。而我們已經無法回退了。

此時只能令i++,表示前面的部分檢查無果,繼續向後檢查。

(你也可以理解成,讓obj相對於pattern運動)

GACGAACGACCGACGACGCCGACGAC

--------------GACGCCG

重覆上述的過程到了這裡(i=10,j=3),next[2]=0

GACGAACGACCGACGACGCCGACGAC

--------------------GACGCCG

pattern無法回退了,所以向前檢查。

GACGAACGACCGACGACGCCGACGAC

----------------------GACGCCG

i=15,j=4,next[3]=1

GACGAACGACCGACGACGCCGACGAC

----------------------------GACGCCG

回退之後比對成功,i++,j++,重覆這一過程,直到……

 

GACGAACGACCGACGACGCCGACGAC

 

----------------------------GACGCCG

至此我們便利用next數組完成了模式串匹配的過程。

可以看到,next數組使得匹配過程中少了很多不必要的計算,整個匹配過程顯得高效利落。

 

那麼問題來了,怎樣高效地求next數組?暴力是肯定行不通的。

事實上,next數組的求法和上述KMP演算法的步驟驚人地相似,甚至可以說,求next數組的過程就是一個自我匹配的過程。

也就是:求next[j],就是讓pattern[0..j]和pattern[0..x]進行上述的匹配過程。這裡x=next[j-1]。next[j]=能夠匹配成功的子串的最大長度。

下令pattern=”GACCGGACCGA“

首先令next[0]=0,易知next[1]=0。

之後,由於pattern[2]!=pattern[0],所以next[2]=0。同理next[3]=0。

由於pattern[4]==pattern[0],所以next[4]=0+1=1。

這裡的0是next[3]的值。表示字元pattern[4]可以接到next[3]對應的尾碼之後。

由於pattern[5]!=pattern[1],pattern[5]==pattern[next[0]]==pattern[0],所以next[5]=1。

詳情如下:

GACCGG

--------GA    【 用pattern[0..1]匹配pattern[0..5],這裡的1是next[4] 】

匹配不通過,所以令”模式串“的游標移至next[1]=0處(加了引號是因為這裡的”模式串“是對pattern本身進行匹配)

GACCGG

----------GA

類似地,可得:next[6]=next[5]+1=2,next[7]=next[6]+1=3,next[8]=next[7]+1=4,next[9]=next[8]+1=5。

next[10]的求法如下:

GACCGGACCGA

----------GACCGG    【 next[4]=1 】

 

GACCGGACCGA

------------------GACCGG

故next[10]=2。

 

代碼如下:

 1 #include <cstdio>
 2 #include <cstring>
 3 
 4 const int maxL=1000005;
 5 
 6 char obj[maxL];
 7 char pattern[maxL];
 8 
 9 void input()
10 {
11     scanf("%s%s",obj,pattern);
12 }
13 
14 int next[maxL]={0};
15 
16 int getNext()
17 {
18     next[0]=0;
19     int len=strlen(pattern);
20     for(int i=1;i<len;i++)
21     {
22         int k=next[i-1];
23         while(k>=0 && pattern[i] != pattern[k]) k=k?next[k-1]:-1;
24         next[i]=k+1;
25     }
26     return len;
27 }
28 
29 int kmp() //searching for the first place that pattern appears in obj
30 {
31     int lenObj=strlen(obj);
32     int lenPattern=getNext();
33     for(int i=0,j=0;i<lenObj;i++)
34     {
35         while(j>=0 && obj[i] != pattern[j]) j=j?next[j-1]:-1;
36         if(lenPattern==(++j)) return i-j+1;
37     }
38     return -1; //not found
39 }
40 
41 int main()
42 {
43     input();
44     printf("%d\n",kmp());
45     return 0;
46 }
View Code

(我們用-1作為迴圈終止的條件)

附一道簡單的練習題,求pattern在obj中出現的次數。

 1 #include <cstdio>
 2 #include <cstring>
 3 
 4 const int maxL=1000005;
 5 
 6 char obj[maxL];
 7 char pattern[maxL];
 8 
 9 void input()
10 {
11     scanf("%s%s",pattern,obj);
12 }
13 
14 int next[maxL]={0};
15 
16 int getNext()
17 {
18     next[0]=0;
19     int len=strlen(pattern);
20     for(int i=1;i<len;i++)
21     {
22         int k=next[i-1];
23         while(k>=0 && pattern[i] != pattern[k]) k=k?next[k-1]:-1;
24         next[i]=k+1;
25     }
26     return len;
27 }
28 
29 int kmp()
30 {
31     int ans=0;
32     int lenObj=strlen(obj);
33     int lenPattern=getNext();
34     for(int i=0,j=0;i<lenObj;i++)
35     {
36         while(j>=0 && obj[i] != pattern[j]) j=j?next[j-1]:-1;
37         if(lenPattern==(++j)) { ++ans; j=next[j-1]; }
38     }
39     return ans;
40 }
41 
42 int main()
43 {
44     int T; scanf("%d",&T);
45     while(T--)
46     {
47         input();
48         printf("%d\n",kmp());
49     }
50     return 0;
51 }
Problem:POJ P3461

 

演算法這東西,難者不會,會者不難。如果理解時遇到了瓶頸,不妨用筆實驗一下,總結規律,也許就能悟出演算法的思想。

ps.推薦另一篇博文:http://blog.csdn.net/OIljt12138/article/details/51107585,可以作為閱讀本文的對照參考


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 分類:Unity、C#、VS2015 創建日期:2016-04-10 一、簡介 Unity擁有功能完善的地形編輯器,支持以筆刷繪製的方式實時雕刻出山脈、峽谷、平原、高地等地形。Unity地形編輯器同時提供了實時繪製地表材質紋理、樹木種植、大面枳草地佈置等功能。值得—提的是,Unity中的地形編輯器支... ...
  • 軟體下載: http://hovertree.com/h/bjaf/hwqtjwjs.htm 截圖:使用方法:點擊按鈕,選擇文件夾,就可以顯示文件夾中包含的文件總數。這個項目包含在HoverTree解決方案中。源碼下載:http://hovertree.com/h/bjaf/cao15h74.htm ...
  • 一、引言 在前一篇文章中,我向大家介紹瞭如何實現實現端對端聊天的功能的,在這一篇文章中將像大家如何使用SignalR實現群聊這樣的功能。 二、實現思路 要想實現群聊的功能,首先我們需要創建一個房間,然後每個線上用戶可以加入這個房間裡面進行群聊,我們可以為房間設置一個唯一的名字來作為標識。那Signa ...
  • 1.前言 現在這個項目已經有階段性的模塊完成了,所以就想著對這些模塊進行單元測試,以保證項目的代碼的質量。首先雖然標題是mvc+webapi實質上我只是對mvc進行的測試。用的時候vs的unit test generator.至於它的安裝和介紹在這不做詳細介紹。好的現在開始總結我的單元測試總結。 2 ...
  • 前言當我們訪問某個網站的時候需要檢測用戶是否已經登錄(通過Session是否為null),我們知道在WebForm中可以定義一個BasePage類讓他繼承System.Web.UI.Page,重寫它的OnInit()方法,在OnInit()中判斷Session中是否有用戶登錄的信息。 在mvc下該怎 ...
  • 首先是IEnumerable與IEnumerator的定義: 1.IEnumerable介面允許使用foreach迴圈,包含GetEnumerator()方法,可以迭代集合中的項。 2.IEnumerator介面是一個真正的集合訪問器,它包含MoveNext()方法和Current屬性,在forea ...
  • HoverTree解決方案學習C#.NET,Sql Server,WinForm等的解決方案。本文鏈接http://hovertree.com/h/bjaf/0jteg8cv.htm使用的技術、框架、工具包括:.NET Framework 4.5.2Visual Studio 2015Sql Ser ...
  • 對於EF對資料庫的緩存,EF本身也有,但是不能靈活的控制,而且實體對象釋放了緩存就沒有了,總不能使用同一個實體對象(實體對象不支持多線程),基本上就是用完就釋放,而EF的一個擴展框架也提供了緩存操作(源碼:https://github.com/loresoft/EntityFramework.Ext ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...