我想說一句“我日,我讨厭KMP!”。
KMP雖然經典,但是了解起來極其複雜,好不容易了解好了,便起碼來巨麻煩!
老子就是今天圖書館在寫了幾個小時才勉強寫了一個有bug的、效率不高的KMP,特别是計算next數組的部分。
其實,比KMP算法速度快的算法大把大把,而且了解起來更簡單,為何非要抓住KMP呢?筆試出現字元串模式比對時直接上sunday算法,既簡單又高效,何樂而不為?
說實話,想到sunday算法的那個人,絕對是發散思維,絕對牛。當我在被KMP折磨的夠嗆的時候,我就琢磨,有沒有别的好算法呢??琢磨了半天也沒想出個是以然來。笨啊,腦子不夠發散。
下面貼上一位兄弟寫的算法總結,很簡單(建議KMP部分就不用看了,看了費腦子)。
參見:http://hi.baidu.com/willamette/blog/item/02bd0b5599c8b4c0b645ae06.html
趁着做Presentation的功夫,順便做一個總結
字元串比對:
---willamette
在比對串中尋找模式串是否出現,注意和最長公共子序列相差別(LCS: Longest Common Substring)
-:Brute Force(BF或蠻力搜尋)算法:
這是世界上最簡單的算法了。
首先将比對串和模式串左對齊,然後從左向右一個一個進行比較,如果不成功則模式串向右移動一個機關。
速度最慢。
那麼,怎麼改進呢?
我們注意到Brute Force算法是每次移動一個機關,一個一個機關移動顯然太慢,是不是可以找到一些辦法,讓每次能夠讓模式串多移動一些位置呢?
當然是可以的。
我們也注意到,Brute Force是很不intelligent的,每次比對不成功的時候,前面比對成功的資訊都被當廢棄物丢棄了,當然,就如現在的變廢為寶一樣,我們也同樣可以将前面比對成功的資訊利用起來,極大地減少計算機的處理時間,節省成本。^_^
注意,蠻力搜尋算法雖然速度慢,但其很通用,文章最後會有一些更多的關于蠻力搜尋的資訊。
-: KMP算法
首先介紹的就是KMP算法。
原始論文:Knuth D.E., Morris J.H., and Pratt V.R., Fast pattern matching in strings, SIAM Journal on Computing, 6(2), 323-350, 1977.
這個算法實在是太有名了,大學上的算法課程除了最笨的Brute Force算法,然後就介紹了KMP算法。也難怪,呵呵。誰讓Knuth D.E.這麼world famous呢,不僅拿了圖靈獎,而且還寫出了計算機界的Bible (業内人士一般簡稱TAOCP).稍稍提一下,有個叫H.A.Simon的家夥,不僅拿了Turing Award,順手拿了個Nobel Economics Award,做了AI的爸爸,還是Chicago Univ的Politics PhD,可謂全才。
KMP的具體描述略去,教科書一大把。
-:Horspool算法
Horspool算法。
當然,有市場就有競争,字元串比對這麼大一個市場,不可能讓BF和KMP全部占了,于是又出現了幾個強勁的對手。
第一個登場的是
論文:Horspool R.N., 1980, Practical fast searching in strings, Software - Practice & Experience, 10(6):501-506
Horspool算法的思想很簡單的。不過有個創新之處就是模式串是從右向左進行比較的。很好很強大,為後來的算法影響很大。
比對串:abcbcsdxzcxx
模式串:cbcac
這個時候我們從右向左進行對暗号,c-c,恩對上了,第二個b-a,不對啊,我們應該怎麼辦?難道就這麼放棄麼。于是,模式串從不比對的那個字元開始從右向左尋找比對串中不比對的字元b的位置,結果發現居然有,趕快對上趕快對上,别耽誤了。
比對串:abcbcsdxzcxx
模式串:cbcac
然後繼續從最右邊的字元從右向左進行比較。這時候,我們發現了,d-c不比對啊,而且模式穿裡面沒有噢,沒辦法,隻好移動一個模式串長度的機關了。
比對串:abcbcsdxzcxx
模式串:cbcac
-:Boyer-Moore算法
對于BM算法,下面推薦一個講解非常優秀的文章,可謂圖文并茂啊,而且還是個MM寫的。
最後一個是Sunday算法,實際上比Boyer-Moore還快,呵呵。長江後浪推前浪。
原始論文:Daniel M. Sunday, A very fast substring search algorithm, Communications of the ACM, v.33 n.8, p.132-142, Aug. 1990
看原始論文的題目,D.M. Sunday貌似是故意想氣氣Boyer-Moore兩位大牛似的。呵呵。不過實際上的确Sunday算法的确比BM算法要快,而且更簡單。
Sunday的算法思想和Horspool有些相似,但是。當出現不比對的時候,卻不是去找比對串中不比對的字元在模式串的位置,而是直接找最右邊對齊的右一位的那個字元在模式串的位置。
比如:
比對串:abcbczdxzc
模式串:zbcac
恩,這裡我們看到b-a沒有對上,我們就看比對串中的z在模式串的位置,然後,嘿嘿。
比對串:abcbczdxzc
模式串:zbcac
如果模式串中的沒有那個字元怎麼辦呢?很簡單,跳過去呗。
比對串:abcbcedxzcs
模式串:zbcac
e不在模式串中出現
那麼我們就
比對串:abcbcedxzcs
模式串:zbcac
(2009/10/20補充)
RK算法
某一天在圖書館的一本算法分析設計書上翻到的。思路很新穎!和大家分享下。
在串比對的簡單算法中,把文本每m個字元構成的字元段作為一個字段,和模式進行比對檢查。如果能對一個長度為m的字元
串賦以一個Hash函數。那麼顯然隻有那些與模式具有相同hash函數值的文本中的字元串才有可能與模式比對,這是必要條件
,而沒有必要去考慮文本中所有長度為m的字段,因而大大提高了串比對的速度。是以RK算法的思想和KMP,BM,Sunday等思
路迥然不同!
(事實上,之前的串比對方法,是将模式串的一個一個字元作為小的特征去分别進行比對,而RK算法則是将串整體作為一個
特征!難就難在單個字元的特征很容易想得到,整體作為一個特征就沒那麼容易想得到了)
如果把整體作為一個特征,那麼如何快速的求出這個整體特征的特征值??
模式串的特征值僅需求一次即可。對于文本中的任意m個字元構成的字串如何快速的求特征就是個難點了。
抛磚引玉,這裡給出一個簡單的特征計算。 将字元串的每一個字元看做一個數,那麼這個字元串的就是一個數字數組,通
過積分向量可以快速任意一個長度子字元串的向量和。可以把字元串的對應的字元數組的元素和看做這個字元串整體特征。
這個特征是可以再O(1)的時間内求出的。其實原始的RK算法裡面是把字元串看做一個26進制數在計算特征的。這裡就不啰
嗦了,有興趣的可以深入查找
aabseesds 模式串 ees
ees
發現 see向量和 == ees的向量和
然後就對see和ees做逐個字元的比較。發現不比對繼續往下走
aabseesds 模式串 ees
ees
發現 ees向量和 == ees的向量和
然後就對ees和ees做逐個字元的比較。發現比對OK。
另外還有 字元串比對自動機 字尾樹算法(分線上和非線上兩種)等 見如下文章。不能說那個比那個更好,各個算法都有自己的優勢及最佳應用場合。參考:
http://blog.csdn.net/yifan403/archive/2009/06/16/4272793.aspx
另外,關于多模式字元串比對 有AC算法(字元串比對自動機思想) WM算法(BM在多模式的推廣應用)
參考:
http://blog.csdn.net/ijuliet/category/498465.aspx 該女子的blog有很多好文章。
===============================================================================
一個sunday算法的實作
http://hi.baidu.com/azuryy/blog/item/10d3d3460b97af0e6b63e5cd.html
頭檔案定義:
class Sunday
{
public:
Sunday();
~Sunday();
public:
int find(const char* pattern, const char* text);
private:
void preCompute(const char* pattern);
private:
//Let's assume all characters are all ASCII
static const int ASSIZE = 128;
int _td[ASSIZE] ;
int _patLength;
int _textLength;
};
源檔案
Sunday::Sunday()
{
}
Sunday::~Sunday()
{
}
void Sunday::preCompute(const char* pattern)
{
for(int i = 0; i < ASSIZE; i++ )
_td[i] = _patLength + 1;
const char* p;
for ( p = pattern; *p; p++)
_td[*p] = _patLength - (p - pattern);
}
int Sunday::find(const char* pattern, const char* text)
{
_patLength = strlen( pattern );
_textLength = strlen( text );
if ( _patLength <= 0 || _textLength <= 0)
return -1;
preCompute( pattern );
const char *t, *p, *tx = text;
while (tx + _patLength <= text + _textLength)
{
for (p = pattern, t = tx; *p; ++p, ++t)
{
if (*p != *t)
break;
}
if (*p == 0)
return tx-text;
tx += _td[tx[_patLength]];
}
return -1;
}
簡單測試下:
int main()
{
char* text = "blog.csdn,blog.net";
char* pattern = "csdn,blog" ;
Sunday sunday;
printf("The First Occurence at: %d/n",sunday.find(pattern,text));
return 1;
}
strstr的實作。
需要說明的是strstr是c語言提供的使用Brute Force實作的字元串比對,簡單、通用是其最大的優點。時間複雜度是O(mn)
// 下面是Microsoft的實作
//經典算法
//比KMP算法簡單,沒有KMP算法高效
char * __cdecl strstr (
const char * str1,
const char * str2
)
{
char *cp = (char *) str1;
char *s1, *s2;
if ( !*str2 )
return((char *)str1);
while (*cp)
{
s1 = cp;
s2 = (char *) str2;
while ( *s1 && *s2 && !(*s1-*s2) )
s1++, s2++;
if (!*s2)
return(cp);
cp++;
}
return(NULL);
}
strstr
glibc裡的strstr函數用的是brute-force(naive)算法,它與其它算法的差別是strstr不對pattern(needle)進行預處理,是以用起來很友善。理論複雜度O
(mn), 實際上,平均複雜度為O(n), 大部分情況下高度優化的算法性能要優于基于自動機的比對算法,關于串比對算法可參考http://www-igm.univ-mlv.fr/~lecroq/string/。 glibc中使用了(1)Stephen R. van den Berg的實作,在他的基礎上,(2)Tor Myklebusthttp://sources.redhat.com/ml/libc-alpha/2006-07/msg00028.html給出了更複雜的實作,當然也更高效。
BF有一個重要性質是事先不用知道串的長度,而基于跳躍的算法是需要用字元串長度來判斷結束位置的。如何快速的确定字元串結束位置,可參考http://www.cppblog.com/ant/archive/2007/10/12/32886.html,寫的很仔細。
将兩種思想結合起來,可以做出更快的strstr(3)。約定(1) 為strstrBerg; (2) 為strstrBergo,(3)為lstrstr,(4)為glibc中的strstr,簡單測試了一下:
從長度為2k的文本中查找長度為1、2、9的模式串,結果如下
1 2 9
(1)0.000006 0.000006 0.000012
(2)0.000007 0.000004 0.000008
(3)0.000002 0.000002 0.000005
(4)0.000005 0.000005 0.000011
下載下傳strstr和測試程式,
下載下傳後執行 :
unzip testStrstr.zip
cd testStrstr
make test
基于sse2的strstr函數 是用sse2指令集對strstr的優化