字串匹配演算法(三)位運算的魔法 KR與SO

2021-04-21 10:05:28 字數 2107 閱讀 1326

位運算經常能做出一些不可思議的事情來,例如不用臨時變數要交換兩個數該怎麼做呢?乙個沒接觸過這類問題的人打死他也想不出來。如果拿圍棋來做比喻,那麼位運算可以喻為程式設計中的「手筋」。

按位的儲存方式能提供最大的儲存空間利用率,而隨著空間被壓縮的同時,由於cpu硬體的直接支援,速度竟然神奇般的提公升了。舉個例子,普通的陣列要實現移位操作,那是o(n)的時間複雜度,而如果用位運算中的移位,就是乙個指令搞定了。

kr演算法之前第一章介紹中說是利用雜湊,原文這麼介紹的。而我的看法是,雜湊只是乙個幌子。這個演算法的基本步驟同窮舉法一樣,不同在於每趟比較前先比較一下雜湊值,hash值不同就不必比較了。而如果hash值無法高效計算,這樣的改進甚至還不如不改進呢。你想想,比較之前還要先計算一遍hash值,有計算的功夫,直接比都比完了。

kr演算法為了把挨個字元的比較轉化為兩個整數的比較,它把乙個m長度的字串直接當成乙個整數來對待,以2為基數的整數。這樣呢,在第一次算出這個整數後,以後每次移動視窗,只需要移去最高位,再加上最低位,就得出乙個新的hash值。但是m太大,導致超出計算機所能處理的最大整數怎麼辦?不用擔心,對整數最大值取模,借助模運算的特性,一切可以完美的進行。而且由於是對整數最大值取模,所以取模這一步都可以忽略掉。

這是kr演算法的**:

#define rehash(a, b, h) ((((h) - (a)*d) << 1) + (b))

void kr(char *x, int m, char *y, int n) 

/* searching */

j = 0;

while (j <= n-m) 

}我們可以看到,kr演算法有o(m)複雜度的預處理的過程,總感覺它的預處理沒有反映出模式本身的特點來,導致它的搜尋過程依然是o(mn)複雜度的,只不過一般情況**現不出來,在"aaaaaaaaaaaaaaaaaaaaaaaaa"中搜"aaaaa"就知道kr多慢了。

總的來說,kr演算法比窮舉強一點,比較次數的期望值是o(m+n)。

為了最大限度的發揮出位運算的能力,shift or演算法就有了乙個最大缺陷:模式不能超過機器字長。按現在普遍的32位機,機器字長就是32,也就是只能用來匹配不大於32個字元的模式。而帶來的好處就是匹配過程是o(n)時間複雜度的,達到自動機的速度了。而預處理所花費的時間與空間都為o(m+σ),比自動機少多了。

我們來看看它怎麼巧妙的實現「只看一遍」的:

假設我們有乙個公升級系統,總共有m個級別。每一關都會放乙個新人到第0級上,然後對於系統中所有的人,如果通過考驗,公升一級,否則,咔嚓掉。而對於公升到最高端的人,那說明他連續通過了m次考驗,這就是我們要選拔的人。

kr演算法的思路就是上面的公升級規則,給出的考驗就是你的位置上的字元與給出的文字字元是否一致。公升滿級了,說明在連續m個位置上與不斷給出的文字字元一致,這也就是匹配成功了。

明白了這個思路後,疑問就開始出來了:檢查哪些位置與文字字元一致,需要m次吧?那麼整個演算法就是o(mn)了?

現在就該位運算出場了,對,這個演算法的思路是很笨,但是我位運算的效率高呀,事先我算出字母表中每個字元在模式中出現的位置,用位的方式存在整數裡,出現的地方標為0,不出現的地方標為1,這樣總共使用σ個整數;同樣,我用乙個整數來表示公升級狀態,某個級別有人就標為0,沒人就標為1,整個系統公升級就恰好可以用「移位」來進行,當檢查位置的時候只需要與表示狀態的整數「或」1次,所以整個演算法就成o(n)了。shift-or演算法名字就是這樣來的。

有乙個地方很奇怪,0和1的設定和通常的習慣相反呀,習慣上,喜歡把存在設為1,不存在設為0的。不過這裡沒有辦法,因為移位新移出來的是0。

這時我們來看**就容易理解多了:

#define wordsize sizeof(int)*8

int preso(const

char *x, int m, unsigned int s) 

lim = ~(lim>>1);

return(lim);

}void so(const

char *x, int m, const

char *y, int n) }

**中lim變數其實就是乙個標尺,例如出現最高端的狀態是01111111,那麼lim就成了10000000,因此只要小於lim,就表示最高端上的0出現了。

原文中對shift-or演算法的描述還是很難懂的,如果對著那段說明去看**,有點不知所云的感覺。我還是直接對著**才想出這個公升級的比喻來。

字串的匹配演算法

簡單的說,就是對主串的每乙個字元作為字串的開頭,與要匹配的字串進行匹配。對主串做大迴圈,每個字元開頭做字串的長度的小迴圈,直到匹配成功或者全部遍歷為止。讓我們來分析一下,最好的情況是什麼?那就是第一開始就成功,時間時間複雜度為o 1 最壞呢,就是每次迴圈到子串最後一位才發現不對,時間複雜度為o n ...

字串的匹配演算法

串模式的匹配演算法 1.使用最基礎 的bp演算法,也就是從主串開始乙個乙個遍歷字串看主串是否有包含子串,如果有返回其標記的位置。如下 int bf char findstr,int sizefindstr,char str,int sizestr if j sizestr return 1 2.使用...

字串匹配的三種演算法

下面將介紹三種有關字串匹配的演算法,一種是樸素的匹配演算法,時間複雜度為o mn 也就是暴力求解。這種方法比較簡單,容易實現。一種是kmp演算法,時間複雜度為o m n 該演算法的主要任務是求模式串的next陣列。另外還有一種對kmp演算法的改進,主要是求nextval陣列。第一種樸素的匹配演算法 ...