因為在網上搜尋hash演算法的知識,無意中又找到一些字串搜尋演算法。 由於之前已經學習過一些搜尋演算法,覺得應該可以歸為一類。因此就寫一篇文章來記錄下學習的過程。
問題:在一長字串中找出其是否包含某子字串。
首先當然還是簡單演算法,通過遍歷來檢索所有的可能:
public static int *****search(string content, string sub)
}if(found) return i;
}return -1;
}
時間複雜度為 θ((n-m+1) m)
rabin–karp,即hash檢索法:
public static int rabinkarp(string content, string sub)
}hcontent = newhash(content, hcontent, i + 1, sub.length());
}return -1;
}private static long rshash(string str)
return hash;
}private static long newhash(string str, long previous, int i, int length)
這個演算法的核心思想是,通過hash值,我們可以一次匹配一整條字串,速度上要快很多。
關鍵: 選擇這樣一種hash演算法,使得從前乙個hash值到後乙個hash值僅需要常量的步驟。
我這裡實現的hash演算法可以做到這點,但是有效性並不高,應該還有其他的hash演算法可以更好了減少衝突的發生。
kmp演算法
kmp演算法說簡單也不簡單,說複雜也不複雜。只要你理解了它的核心思想,**量其實非常少。可是想要解釋它的思想,卻也不是一件容易的事情。
kmp algorithm
例項為了解釋該演算法的細節,我們首先利用乙個例項來把演算法的步驟過一遍。在這個過程中的任意時間點,該演算法的狀態都由兩個變數來決定, m和i。 m代表在s中,某個對於w(pattern)匹配的起始位置。 i代表在w中的當前正在進行匹配工作的位置。我們來描述一下當演算法開始時的狀態:
1 2
m: 01234567890123456789012
s: abc abcdab abcdabcdabde
w: abcdabd
i: 0123456
我們首先從0位開始匹配w和s中平行的字串,如果匹配,則前進到下一位。然而當我們到第四步的時候,我們發現s[3]是空格而w[3]=『d』,出現了第乙個不匹配。在傳統的模式匹配演算法中,接下來我們應該從s[1]的位置重新開始匹配。然而,如果我們仔細觀察,在s的0到3位中(也就是我們剛剛進行過匹配成功的位),除了0位,其它都沒有『a『出現過。[b]而假設某字串中有和w匹配的子串,那麼這個子串必須是以』a『開頭的。[/b]因此,我們可以確定,s的0到3位中,都不可能存在這樣的子串,於是我們決定從s的4位中重新找起。也就是說,m=4, i=0.
1 2
m: 01234567890123456789012
s: abc abcdab abcdabcdabde
w: abcdabd
i: 0123456
此時,我們獲得了乙個幾乎匹配的「abcdab」,然後在w[6](s[10])這個位置上,我們又出現了乙個不匹配。於是,我們應該繼續擴大m的值去尋找下乙個可能的匹配。那麼m的下乙個值應該設定為多少呢?
[b]整個演算法的核心就在於此,我們可以從不匹配的位置開始,即m=10,然後這並不是乙個正確的選擇,我們發先在s的4到10位中,第8位也是『a』。因此,如果我們從第十位開始繼續找起的話,有可能就錯過了某個匹配。
s中第八位的『a』和第九位的『b』,分別跟w的第0第1位相匹配。kmp演算法對此的處理是,新的m=8(即首位匹配的值),新的i=2(因為前兩位根據統計,已經是匹配好的了)。然後我們從s的m+i開始匹配w的i位。[/b]
1 2
m: 01234567890123456789012
s: abc abcdab abcdabcdabde
w: abcdabd
i: 0123456
1 2
m: 01234567890123456789012
s: abc abcdab abcdabcdabde
w: abcdabd
i: 0123456
此時,我們又在m=17的位置上出錯了,根據第二步的解釋,我們這次跳到m=15而i=2:
1 2
m: 01234567890123456789012
s: abc abcdab abcdabcdabde
w: abcdabd
i: 0123456
找到該模式,演算法結束,返回m的值15.
部分匹配表
如果我們剛才的分析那樣,整個匹配演算法的核心就在於,當某次匹配過程出現不匹配的值時,如何尋找下乙個做匹配的位置(這裡的位置包括兩個概念,即起始位置和我們應該從哪個值開始做匹配)。這樣的值當然是越大越好,因為選擇的下乙個匹配位置越大,我們跳過的值就越多,整個演算法就越快。
[b]如果單看我們之前的分析,好像這個確定下乙個匹配位置的工作關係到s和w兩張表。其實不是這樣的,我們只需要對w進行乙個預處理,就可以做到這點[/b]。
還是來看,當w是「abcdabd」這樣乙個字串時。我們會發現除了起始位置是『a』以外,4位的值也是『a』,那麼如果我們在某次匹配時,匹配到了w的第4位,那麼下一次做匹配查詢時,就應當從w第4位對應的那個字元開始:
m 123456
s ... abcdax...
w abcdabd
i 0123456
因為我們已經知道s[5]和w[4]是匹配的了,那麼其實就不需要再匹配一次了。因此匹配的起始位置是m=5,但是應當從i=1那裡開始進行匹配。
如果是這樣的乙個情況呢?
m 1234567
s ... abcdabx..
w abcdabd
i 0123456
同樣的道理,匹配的起始位置依然是m=5,但是應當從i=2開始匹配。
下面給出求出當從任一位出現不匹配是,應該從**開始從新匹配的演算法:
private static void next(char input, int table) else if(*** > 0) else }}
既然已經得到了這樣乙個表,那麼寫出整個kmp演算法也不是什麼難事了:
private static void next(char input, int table) else if(*** > 0) else }}
最後兩個演算法分別是bm演算法和有限自動機演算法。昨天我花了一天的時間研究bm演算法,對演算法的本質有了一定的了解,但是對於如何編碼還是有點困惑。
字串搜尋演算法
參考文獻 google boyer moore 1.前言 字串搜尋是乙個基本的操作,c庫函式中也提供了strstr 函式進行字串搜尋,應該是屬於線性搜尋。此外,為提高搜尋速度,人們又發展 出一些快速搜尋演算法,如boyer moore演算法等,其與線性搜尋的區別是當發現模式不匹配時,不是象線性搜尋那...
字串匹配 暴力搜尋演算法
主要特徵 1 沒有預處理階段 2 需要常量額外空間 3 通常需要模式串視窗向右移動乙個位置 4 可以按照任意順序進行比較 5 搜尋的時間複雜度為 o mn 6 文字字元期望比較次數 2n演算法描述 暴力搜尋演算法由文字串中從0到 n m所有位置的比較組成,無論是否從模式串的起始位置開始,每次匹配過後...
字串匹配 暴力搜尋演算法
1 沒有預處理階段 2 需要常量額外空間 3 通常需要模式串視窗向右移動乙個位置 4 可以按照任意順序進行比較 5 搜尋的時間複雜度為o mn 6 文字字元期望比較次數 2n 暴力搜尋演算法由文字串中從0到n m所有位置的比較組成,無論是否從模式串的起始位置開始,每次匹配過後,模式串向右移動一位。暴...