考慮一個事件,它有兩種機率均等的結果。比如擲硬幣,出現正面和反面的機會是相等的。現在我們希望知道,如果我不斷拋擲硬幣,需要多長時間才能得到一個特定的序列。
序列一:反面、正面、反面
序列二:反面、正面、正面
首先,我反覆拋擲硬幣,直到最近的三次拋擲結果形成序列一,然後我記下這次我拋擲了多少次才得到了我要的序列。重複執行這個過程,我可以算出得到序列一平均需要的拋擲次數。同樣地,反覆拋擲硬幣直到序列二產生,它所需要的次數也有一個平均值。你認為這兩個平均值哪一個大哪一個小?換句話說,出現序列一平均所需的拋擲次數少還是出現序列二平均需要的次數少?
大多數人會認為,兩個序列會以同樣快的速度出現,因為在所有“正”和“反”的8種三元組合裡,“反正反”和“反正正”各佔1/8,其機率是均等的。而事實上,我們將會看到擲出序列二所需的次數更少一些。不妨考慮這樣一個問題:在由“正”和“反”構成的n位01序列中,有多少個序列以序列一結尾但之前不曾出現過序列一?有多少個序列以序列二結尾但之前不曾出現過序列二?當n比較小時,兩者答案是一樣的(例如n=3時符合要求的情況都是唯一的),但到後來n越大時,兩者的差距越明顯:後者的個數總比前者的個數要多一些。不妨看一看n=6的情況。對於序列一,只有以下5個序列是符合要求的:
- 反反反反正反
- 反正正反正反
- 正正正反正反
- 正反反反正反
- 正正反反正反
但對於序列二來說,合格序列就有7個:
- 反反反反正正
- 反正反反正正
- 反反正反正正
- 正反反反正正
- 正正反反正正
- 正正正反正正
- 正反正反正正
你可以通過電腦編程枚舉,計算一下n為其它值的情況。計算結果和剛才也一樣:在n位01序列中,以序列二結尾但之前不含序列二的情況不會少於以序列一結尾但之前不含序列一的情況。這說明,拋擲第n次硬幣後恰好出現了序列二,其機率不會小於恰好出現序列一的機率。顯然,當n漸漸增大時,這個機率應該呈下降趨勢;同時,隨著n的增長,兩個序列各自出現的機率由相等開始慢慢拉開差距,第n次拋擲產生序列二的機率下降得要緩慢一些,或者說更多的情況集中發生在n更小的時候。因此總的來說,出現序列二所需要的拋擲硬幣次數的期望值更小。
雖然我們通過一系列的觀察驗證了這個結論,並且我們也相信這個結論是正確的(雖然沒有嚴格的證明),但我們仍然不是很接受這個結論。這種情況是有悖於我們的直覺的,它與我們的生活經驗不相符合。此刻,我們迫切需要一個解釋,來說明這種出人意料的反常現象產生的原因。
如果不親自做幾次實驗的話,你很難體會到這種微妙的差距。考慮整個遊戲的實際過程,“反正正”序列顯然會出現得更早一些。假如某一次我們得到了序列“反正”。如果我們需要的是“反正反”序列,那麼下一次拋擲結果為反面將結束本輪的拋擲,而下一次是正面則前功盡棄,你必須再次從零開始。如果我們需要的是“反正正”序列,那麼下一次拋擲結果為正面將結束本輪的拋擲,而下一次是反面的話我至少不會慘到一切歸零,這相當於我已經有了一個反面作為新的開頭,只需再來兩個正面即可。這樣看的話,提前擲出“反正正”的可能性更大一些。
反覆體會上面的想法,瞭解KMP演算法的網友會恍然大悟:這就是KMP演算法的基本思路!考慮這樣一個問題:我們在當前字串中尋找子串“反正正”第一次出現的位置。假如當前已經能匹配模式串的前兩個字“反正”,主串中的下一個字是“正”則匹配成功,主串的下一個字是“反”則將使模式串的當前匹配位置退到第一個字。考慮一個更複雜的例子:我們希望在主串中尋找子串abbaba,現在已經在主串中找到了abbab。如果主串下一個字元是a,則成功匹配;如果主串下一個字元是b,則模式串最多能匹配到的位置退到了第三個字元,我只需要從abb開始繼續匹配,而不必一切從頭再來。
我們可以用KMP演算法完美地解決上面的問題。首先預先處理出一個數組c,c[i,0]表示模式串匹配到了第i個字元,主串下一個字元為0(反)時,模式串的匹配位置將退到哪裡;同樣地,c[i,1]表示模式串匹配到了第i個字元,主串下一個字元為1(正)時,新的模式串匹配位置在什麼地方。設f[i,j]表示第i次拋擲硬幣後恰好匹配到模式串第j位有多少種情況,則f[i,j]=Σf(i-1,k) + Σf(i-1,l),其中k滿足c[k,0]=j,l滿足c[l,1]=j。將f[i,j]除以2的i次方,我們就得到了相應的機率值。或者更直接地,設P[i,j]表示第i次拋擲硬幣後,最遠能匹配到的模式串位置是第j位的機率,則P[i,j]=Σ( P(i-1,k)/2 ) + Σ( P(i-1,l)/2 )。注意,我們還應該添加一種特殊的機率值P[i,*],它表示在主串第i個字元以前已經成功匹配過的機率,這樣的話下表中每一列的和才能為1。
來看一看程式的輸出結果:
Pattern 1: s[]="aba"
主串位置 1 2 3 4 5 6 7 8 9 10
匹配到s[0] 0.5000 0.2500 0.2500 0.2500 0.2188 0.1875 0.1641 0.1445 0.1270 0.1113
匹配到s[1] 0.5000 0.5000 0.3750 0.3125 0.2813 0.2500 0.2188 0.1914 0.1680 0.1475
匹配到s[2] 0.0000 0.2500 0.2500 0.1875 0.1563 0.1406 0.1250 0.1094 0.0957 0.0840
匹配到s[3] 0.0000 0.0000 0.1250 0.1250 0.0938 0.0781 0.0703 0.0625 0.0547 0.0479
已找到匹配 0.0000 0.0000 0.0000 0.1250 0.2500 0.3438 0.4219 0.4922 0.5547 0.6094
Pattern 2: s[]="abb"
主串位置 1 2 3 4 5 6 7 8 9 10
匹配到s[0] 0.5000 0.2500 0.1250 0.0625 0.0313 0.0156 0.0078 0.0039 0.0020 0.0010
匹配到s[1] 0.5000 0.5000 0.5000 0.4375 0.3750 0.3125 0.2578 0.2109 0.1719 0.1396
匹配到s[2] 0.0000 0.2500 0.2500 0.2500 0.2188 0.1875 0.1563 0.1289 0.1055 0.0859
匹配到s[3] 0.0000 0.0000 0.1250 0.1250 0.1250 0.1094 0.0938 0.0781 0.0645 0.0527
已找到匹配 0.0000 0.0000 0.0000 0.1250 0.2500 0.3750 0.4844 0.5781 0.6563 0.7207
這下我們可以清楚地看到,序列二提前出現的機率要大得多。注意到,根據我們的機率定義,表格中每一列的數字之和都是1。同時,倒數第二行的數字之和(有無窮多項)也應該為1,因為最後一行的機率就是倒數第二行的機率值累加的結果,而根據最後一行機率的定義,主串無窮長時已找到匹配的機率應該為1。因此,我們也可以把倒數第二行看作是模式串在主串第i個位置最初相符成功的機率。我們可以根據這一結果近似地計算出拋擲次數的期望值。
Matrix67原創
轉貼請註明出處