1. 程式人生 > >KMP演算法真的很簡單1

KMP演算法真的很簡單1

KMP 演算法真的很簡單

KMP 是字串匹配的經典演算法,曾經一度對其敬而遠之,感覺很難寫出來正確的 KMP 演算法,這都是拜那些“教科書”所賜,在它們的教授下,不禁感覺 KMP 很難!

其實理解 KMP 演算法很簡單,今天就來看個究竟,我的目標就是從幾個簡單的數學等式推匯出 KMP 演算法,簡單但嚴謹。

串匹配

先來回憶一下串匹配場景,不外乎是給定兩個字串 S T ,然後在 S 串中查詢 T 串,如果查詢成功就是匹配,否則就是不匹配。比如:

S = “avantar-titanic”; T = “avantar”; C = “tistan” ;那麼結果就是 T 匹配 S ,而 C

不能匹配 S

樸素的串匹配演算法

再來回顧一下樸素的匹配思想,從 S[i] 開始,逐個檢查 S[i+pos] 是否與 T[pos] 相等,如果 S[i+pos] = T[pos] ,那麼 pos++ ;如果 S[i+pos] != T[pos] ,那麼 T 就要回退到 T[0] ,然後再從 S[i+1] 開始嘗試匹配 T ,這就是下面的虛擬碼。

上面演算法的問題就在於,如果出現了不匹配的情況, T 就要回退到 T[0] ,演算法複雜度為 O(len[S]*len[T])

需要從頭再來嗎?

讓我們來重新審視上面的問題,在 S 串中查詢 T 串,我們使用表示法 C [i, j] 表示 C 的字串 S i S i+1 …S j 假設當前時刻 S 從位置 i 開始,已經匹配了 pos 個長度,也就是 S[i, i+pos-1] = T[0, pos-1] ,繼續向下進行比較;

如果 S[i+pos] = T[pos] ,那麼依然有 S[i, i+pos] = T[0, pos]

如果 S[i+pos] != T[pos] ,那麼將有

S[i, i+pos] != T[0, pos]

這時候 樸素的串匹配思想就是回退 T T[0] S[i+1] T[0] 開始再次逐 字元的比較。然而仔細觀察這個時刻,假設我們不讓 T 回退到 T[0] ,而是回退到 T[j] 的位置,並讓 S 停留在 S[i+pos-1] 的位置,並且滿足

S[i+pos-j-1, i+pos-1] = T[0, j] -------------------------(1)

那麼必然 j<pos-1 ,然後再從 S[i+pos] T[j+1] 開始 字元的比較,這樣不就更加高效了嗎?下面就來看看能不能找到這個 j

採用假設法,假設 j 已經找到了,那麼等式 (1) 成立。

當匹配進行到 S[i+pos] != T[pos] 時,我們已經有 S[i, i+pos-1] = T[0, pos-1] ,取後面的 j 個字串就有:

S[i+pos-j, i+pos-1] = T[pos-j, pos-1] ------------------(2)

根據等式 (1) 和等式 (2) ,我們便可得到

T[pos-j, pos-1] = T[0, j] ----------------------------------(3)

也就是說,如果找到了這樣的 j ,那麼必然滿足等式 (3) 。於是我們可以採用一個數組 P ,記住每個 pos 對應的 j ;也就是 P[pos] = j ,當 T[pos] 不能匹配時,就讓 T 回退到 j ,繼續和 S[i+pos] 匹配; pos 的範圍是 0~len[T]

如果 j = 0 ,意味著 S[i+pos-1] = T[0] = T[pos-1]

如果這個 j 不存在呢,這是很可能發生的,世界不是總是這麼美好的嘛,這個時候意味著 S[i+pos-1] != T[0] ,那麼我們只能從 S[i+pos] T[0] 開始匹配了。

如果對應於每個 pos 我們都找到對應的 j ,那麼不就可以改進上面樸素的匹配演算法了嗎?姑且把這個結果儲存在陣列 next 中,於是 next 的定義就是:

next[pos] 表明如果在 pos T[pos] S[m] 不能匹配,但是 T[0, next[pos]] = S[m-next[pos]-1, m-1] 成立,於是應該回退到 T[next[pos]+1] 處和 S[m] 進行匹配。

這時候匹配演算法的邏輯就像下面那樣:

If S[i] = T[j] then // 如果匹配就繼續向後

i++

j++

if j = Len[T] then

find a match position

endif

else

loop // 找到使 S[i] = T[j’] j’

j = next[j]

until S[i] = T[j] or j < 0

if j < 0 then // j < 0 表明 S[i] != T[0] ,從 i+1 位置匹配 T[0]

j = -1

endif

i++

endif

改進後的匹配演算法

根據上面的思想,那麼我們可以很容易的寫出匹配演算法的程式碼。

 

計算next陣列

現在另外一個問題出現了,如何求next陣列呢?根據前面的分析可以知道next陣列只和T串有關,再說一下next的定義:

next[pos]表明如果在posT[pos]S[m]不能匹配,但是T[0, next[pos]] = S[m-next[pos]-1, m-1]成立,於是應該回退到T[next[pos]+1]處和S[m]進行匹配。

最簡單的情況開始,next[0]=-1,因為如果T[0] != S[m]那麼只能嘗試T[0] ? S[m+1];

不難理解,如果T[0] = T[1],那麼next[1] = 0,否則next[1] = -1

對於next[pos] = j 滿足T[pos-j, pos-1] = T[0, j],因此可以通過蠻力方法找到這個next陣列,只是效率太低,還有更好的方法嗎?

如果當前已知next[0],...,next[pos-1],能否通過它們的值來計算next[pos]呢,假設next[pos-1] = j

1 如果T[j+1] = T[pos],那麼結合T[pos-j-1, pos-1] = T[0, j]可知

T[pos-j-1, pos] = T[0, j+1]

於是next[pos] = j+1

2 如果T[j+1] != T[pos],那麼T[pos-j-1, pos] != T[0, j+1],這時候如何求next[pos]呢?假設next[pos] = j’,如果j’ > -1,那麼有T[pos-j’, pos] = T[0, j’]

再考慮對於任意i < pos-1, next[i] = p’ T[i-p’, i] = T[0, p’],那麼p’肯定<j

現在已經有T[pos-j-1, pos-1] = T[0, j],於是T[pos-p’-1, pos-1] = T[0, p’]

如果T[pos] = T[p’+1]的話,就意味著T[pos-p’-1, pos] = T[0,p’+1]

於是next[pos] = next[i] +1

看來我們找到了求next陣列的方法,看看是不是和匹配演算法很相似呢,虛擬碼如下所示:

next[0] = -1

i = 1

j = -1 // j next[0]開始

// 迴圈直到i=Len[T]

If T[i] = T[j+1] then // 如果匹配就繼續向後

next[i] = j+1 // 計算next[i]

i++

j++

else

loop // 找到使T[i] = T[j’+1]j’

j = next[j]

until T[i] = T[j] or j < 0

if j < 0 then // j < 0表明T[i] = -1

next[i] = j = -1

endif

i++

endif

c++程式碼如下:

KMP匹配演算法

KMP相對於樸素的匹配思想就是當不匹配時T不必回退到T[0]從而提高了匹配效率。

上面的程式碼似曾相似啊,其實它就是KMP匹配演算法,簡單調整一下程式碼邏輯就和常見的KMP演算法相似了。

KMP演算法其實不難,幾個簡單的數學等式就能推出來了嘛,其餘的複雜性證明就略過了。

相關推薦

KMP演算法真的簡單1

KMP 演算法真的很簡單 KMP 是字串匹配的經典演算法,曾經一度對其敬而遠之,感覺很難寫出來正確的 KMP 演算法,這都是拜那些“教科書”所賜,在它們的教授下,不禁感覺 KMP 很難! 其實理解 KMP 演算法很簡單,今天就來看個究竟,我的目標就是從幾個簡單的數學等式

kmp演算法清晰)

#include <iostream> #include <cstring> #include <cstdio> using namespace std; int main() { char t[10050],s[100000

爬蟲簡單麽?直到我抓取了一千億個網頁後我懂!爬蟲簡單

服務 字體 每日 還需要 道理 但是 電子商務 發表 硬件 現在爬蟲技術似乎是很容易的事情,但這種看法是很有迷惑性的。開源的庫/框架、可視化的爬蟲工具以及數據析取工具有很多,從網站抓取數據似乎易如反掌。然而,當你成規模地在網站上抓東西時,事情很快就會變得非常

KMP 演算法1):如何理解 KMP

http://www.61mon.com/index.php/archives/183/ 系列文章目錄 KMP 演算法(1):如何理解 KMPKMP 演算法(2):其細微之處 一:背景TOC 給定一個主字串(以 S 代替)和模式串(以 P 代替),要

【leetcode】python演算法題庫——簡單難度【1

1. 兩數之和 給定一個整數陣列和一個目標值,找出陣列中和為目標值的兩個數。 你可以假設每個輸入只對應一種答案,且同樣的元素不能被重複利用。 示例: 給定 nums = [2, 7, 11, 15], target = 9 因為 nums[0] + nums[1] = 2

dubbo其實簡單,就是一個遠端服務呼叫的框架(1

dubbo專題」dubbo其實很簡單,就是一個遠端服務呼叫的框架(1) 一、dubbo是什麼? 1)本質:一個Jar包,一個分散式框架,,一個遠端服務呼叫的分散式框架。 既然是新手教學,肯定很多同學不明白什麼是分散式和遠端服務呼叫,為什麼要分散式,為什麼要遠端呼叫。我簡單畫個對比圖說明(

負載均衡演算法---Java的簡單實現(1

最近,大夥常在談論什麼負載均衡,什麼伺服器的,而自己對於這一塊也是不太理解深入模糊,然後就去看書學習,印證自己的想法。下面是自己的一些總結吧:   比較常用的負載均衡演算法,有下面的這一些: (1)輪詢(其實就是迴圈) (2)隨機 (3)hash (4)加權輪詢

KMP演算法簡單理解 【筆記】

//本文除實現程式碼外全部為原創內容 轉載請註明出處  程式碼來自這裡 kmp演算法是一種改進的字串匹配演算法,由D.E.Knuth與V.R.Pratt和J.H.Morris同時發現,故稱KMP演算法 字串匹配:從字串T中尋找字串P出現的位置(P遠小於T)。其中P稱為“模式”

從壹開始前後端分離 [ Vue2.0+.NET Core2.1] 二十三║Vue實戰:Vuex 其實簡單

前言 哈嘍大家週五好,馬上又是一個週末了,下週就是中秋了,下下週就是國慶啦,這裡先祝福大家一個比一個假日嗨皮啦~~轉眼我們的專題已經寫了第 23 篇了,好幾次都堅持不下去想要中斷,不過每當看到群裡的交流,看到部落格下邊好多小夥伴提出問題,我又燃起了鬥志,不過這兩天感冒了,所以更新的比較晚,這裡也提醒大家,節

演算法筆記》3.1小節——入門模擬->簡單模擬 問題 I: 錘子剪刀布

把握今天,才能擁有明天! 開始輸入資料總是有問題,咋改都不對,最討厭帶空格得字元或字串處理了。 看了一下這位小哥寫的輸入,終於對了····難為我這種菜鳥嘛。 #include<stdio.h> #include<math.h> int mai

「資料結構與演算法 2」| 單鏈表其實真的簡單

寫在之前 在程式設計裡,我們經常需要將同為某個型別的一組資料元素作為一個整體來使用,需要建立這種元素組,用變數來記錄它們或者傳入函式等等等等,「線性表」就是這樣一組元素的抽象,它是某類元素的集合並且記錄著元素之間一種順序關係,是最基本的資料結構之一,在實際程式中

敏捷其實簡單1)重讀敏捷宣言

本人在軟體開發行業中混跡多年,接觸敏捷開發也有3年多的時間了,期間SM, Agile Coach 都曾經從事過,在工作過程中也有過一些對軟體開發流程,敏捷開發的一些思考和總結。這裡想記錄下來,和大家分享一下,也作為自己對個人多年來工作的一個記錄和總結吧。 本篇是這個敏捷系列

KMP演算法簡單求next陣列

前言 1.next陣列的計算只與模式串有關,與主串無關 2.next可能有不同的表示方法,但意義不變 3.字首:除最後一個字母外,前面字母的從前往後組合情況。串abaaba的字首={a,ab,aba,abaa,abaab} 4.字尾:除第一個字母外,後面字母的從前往後組合情況。串

運用kmp演算法解決的一些問題的簡單題解

學習kmp演算法我最後是看的資料結構書上的一本教材學會的。。我覺得kmp相對於普通的BF演算法就是避免了很多不必要的匹配,而kmp演算法的精髓自然就在於next陣列的運用。。。而next陣列簡而言之就

KMP之一:從頭到尾徹底理解KMP演算法(2014年8月1日版)

作者:July 時間:最初寫於2011年12月,2014年7月21日晚10點 全部刪除重寫成此文。 1. 引言     本KMP原文最初寫於2年多前的2011年12月,因當時初次接觸KMP,思路混亂導致寫也寫得非常混亂,如此,留言也是“罵聲”一片。所以一直想找機會重新寫下KMP,但苦於一直以來對KMP的理

影象處理演算法其實都簡單

要學習高斯模糊我們首先要知道一些基本概念: 線性濾波與卷積的基本概念       線性濾波可以說是影象處理最基本的方法,它可以允許我們對影象進行處理,產生很多不同的效果。做法很簡單。首先,我們有一個二維的濾波器矩陣(有個高大上的名字叫卷積核)和一個要處理的二維影象。然

簡單理解KMP演算法

KMP演算法是迄今為止最為高效的字串匹配演算法。當然,在KMP演算法出現之前,有關字串的匹配問題當然經過了一個漫長的探索過程。從一開始最簡單的樸素字串匹配演算法,到Rabin-Karp演算法,再到有限自動機演算法等等,可以說任何一個偉大演算法的誕生都不可能是一朝

指令碼其實簡單-愛心列印指令碼(1

教程簡介 本系列教程(指令碼其實很簡單)的目的是熟悉Windows下的bat指令碼和Linux下的bash shell指令碼。 指令碼的特性就在於不依賴於特定環境,直接呼叫作業系統,不需要“中間人”就可以執行。 文章簡介 作為系列的第一篇文章,思來想去,最終兼顧程式設計師和屌絲單身貴族的身份,打算完成

1.C++實現KMP演算法模板

其他經典演算法合集 https://blog.csdn.net/qq_41562704/article/details/86441022 txt為待查詢字串,pat為子字串 #include <iostream> #include <string> #includ

簡單KMP演算法

雖然題目聲稱KMP簡單,但只是對於理解了的人而言的,但是對於還沒有理解的人來說,KMP演算法確實是非常難的,但是不要緊,我相信通過我的介紹你會理解的,但是個人認為,不論什麼比較難理解的演算法,如果直接給你講,即使講的方法再簡單,但是你沒有去自己思考,那也是理解不