1. 程式人生 > >字串匹配演算法(二)窮舉與自動機

字串匹配演算法(二)窮舉與自動機


Rob Pike, 最偉大的C 語言大師之一, 在《Notes on C Programming》中闡述了一個原則:花哨的演算法比簡單演算法更容易出bug、更難實現,儘量使用簡單的演算法配合簡單的資料結構。而Ken Thompson——Unix 最初版本的設計者和實現者,禪宗偈語般地對Pike 的這一原則作了強調: 拿不準就窮舉(When in doubt , use brute force)。 而對於裝13愛好者來說,更是自豪的稱其使用的是BF演算法。窮舉法用在字串匹配上,簡單的描述就是,檢查文字從0到n-m的每一個位置,看看從這個位置開始是否與模式匹配。這種方法還是有一些優點的,如:不需要預處理過程,需要的額外空間為常數,每一趟比較時可以以任意順序進行。
儘管它的時間複雜度為O(mn),例如在文字"aaaaaaaaaaaaaaaaaaaaaaaaaaa"中尋找"aaaaab"時,就完全體現出來了。但是演算法的期望值卻是2n,這表明該演算法在實際應用中效率不低。C程式碼如下:
  1. void
     BF(char *x, int m, char *y, int n) {
  2. int i, j;
  3. /* Searching */
  4. for (j = 0; j <= n - m; ++j) {
  5. for (i = 0; i < m && x[i] == y[i + j]; ++i);
  6. if (i >= m)
  7.          OUTPUT(j);
  8.    }
  9. }
如果我們注意到C庫函式是彙編優化過的,並通常能提供比C程式碼更高的效能的話,我們可以用memcmp來完成每一趟比較過程,從而達到更好的效能:
  1. #define EOS '/0'
  2. void BF(char *x, int m, char *y, 
    int n) { 
  3. char *yb; 
  4. /* Searching */
  5. for (yb = y; *y != EOS; ++y) 
  6. if (memcmp(x, y, m) == 0) 
  7.       OUTPUT(y - yb);
  8. }
自動機的方法其實和窮舉法有點相似,都是用最簡單直白的方式來做事情。區別在於窮舉法是在計算,而自動機則是查表。儘管自動機的構造過程有一點點難解,要涉及到DFA的理論,但是自動機的比較過程那絕對是簡單到無語。簡單說來,根據模式串,畫好了一張大的表格,表格m+1行σ列,這裡σ表示字母表的大小。表格每一行表示一種狀態,狀態數比模式長度多1。一開始的狀態是0,也就是處在表格的第0行,這一行的每個元素指示了當遇到某字元時就跳轉到另一個狀態。每當跳轉到最終狀態時,表示找到了一個匹配。語言表述起來還是比較囉嗦,看程式碼就知道了:
  1. #define ASIZE 256
  2. int preAut(constchar *x, int m, int* aut) {
  3. int i, state, target, old;
  4. for (state = 0, i = 0; i < m; ++i) {
  5.                 target = i + 1;
  6.                 old = aut[state * ASIZE + x[i]];
  7.                 aut[state * ASIZE + x[i]] = target;
  8.                 memcpy(aut + target * ASIZE, aut + old * ASIZE, ASIZE*sizeof(int));
  9.                 state = target;
  10.         }
  11. return state;
  12. }
  13. void AUT(constchar *x, int m, constchar *y, int n) {
  14. int j, state;
  15. /* Preprocessing */
  16. int *aut = (int*)calloc((m+1)*ASIZE, sizeof(int));
  17. int Terminal = preAut(x, m, aut);
  18. /* Searching */
  19. for (state = 0, j = 0; j < n; ++j) {
  20.                 state = aut[state*ASIZE+y[j]];
  21. if (state == Terminal)
  22.                         OUTPUT(j - m + 1);
  23.         }
  24. }
注:原文的程式碼使用一個有向圖的資料結構,我遵循大師的指引,改用了更簡單一點的陣列
從程式碼上我們很容易看出,自動機的構造需要時間是O(mσ),空間也是O(mσ)(嚴格來說這份程式碼使用了O((m+1)σ)),但是一旦構造完畢,接下來匹配的時間則是O(n)。匹配的過程前面已經說了,太簡單了沒什麼好說的,這裡就解釋一下構造過程吧!我們構造的目標是對應模式長度,構造出同樣多的狀態,用0表示初始狀態,然後第一個字元用狀態1表示,第二個用狀態2表示,依次類推,直到最後一個字元,用m表示,也是最終狀態。一開始,陣列全都置0,,這個時候的自動機遇到任何字元都轉到初始狀態。然後給它看模式的第一個字元,假設這是'a'吧,告訴它,狀態0遇到'a'應該到一個新的狀態——狀態1,所以把第0行的第'a'列修改為1。而這個時候狀態1還是空白的,怎麼辦呢?這時候狀態0就想呀,在我被告知遇到'a'要去狀態1之前,我原本遇到'a'都要去狀態0的,也就是修改之前第'a'列所指的那個狀態,稱為old狀態吧;而現在我遇到'a'卻要去一個新的狀態,既然以前old狀態能處理遇到'a'之後的事情,那麼我讓新的狀態像old狀態一樣就好了。於是狀態0把old狀態拷貝到狀態1。現在輪到狀態1了,給它看第二個字元,它也如法炮製,指向了狀態2,又把old狀態拷貝給了狀態2。於是,狀態機就在這種代代傳承的過程中構造完畢了。雖然理論上自動機是最完美的匹配方式,但是由於預處理的消耗過大,實踐中,主要還是用於正則表示式。結語:窮舉法與自動機各自走了兩個極端,因此都沒能達到綜合性能的最佳,本文之後介紹的演算法,可以看成是在窮舉和自動機兩者之間取捨權衡的結果。

相關推薦

字串匹配演算法自動機

Rob Pike, 最偉大的C 語言大師之一, 在《Notes on C Programming》中闡述了一個原則:花哨的演算法比簡單演算法更容易出bug、更難實現,儘量使用簡單的演算法配合簡單的資料結構。而Ken Thompson——Unix 最初版本的設計者和實現者,禪宗偈語般地對Pike 的這一原則作了

【轉】字串匹配演算法位運算的魔法——KRSO

位運算經常能做出一些不可思議的事情來,例如不用臨時變數要交換兩個數該怎麼做呢?一個沒接觸過這類問題的人打死他也想不出來。如果拿圍棋來做比喻,那麼位運算可以喻為程式設計中的“手筋”。 按位的儲存方式能提供最大的儲存空間利用率,而隨著空間被壓縮的同時,由於CPU硬體的直接支援,速度竟然神奇般

[轉]字串匹配演算法簡介

文字資訊可以說是迄今為止最主要的一種資訊交換手段,而作為文字處理中的一個重要領域——字串匹配,就是我們今天要說的話題。(原文還特意提及文字資料數量每18個月翻一番,以此論證演算法必須要是高效的。不過我注意到摩爾定律也是18個月翻番,這正說明資料的增長是緊緊跟隨處理速度的,因此越是使用高效的演算

淺談單模式串字串匹配演算法KMP

字串演算法很有趣,尤其是KMP和AC自動機~~ 大綱 1.問題定義 字串匹配是電腦科學中最古老、研究最廣泛的問題之一。一個字串是一個定義在有限字母表∑上的字元序列。例如,ATCTAGAGA是字母表∑ = {A,C,G,T}上的一個字串。字串匹配問題就是在一個大的字串

[演算法]兩種字串匹配演算法索引法,KMP演算法對比,C語言實現

今天做了個一個簡單的字元對比程式,功能是實現從A串刪除包含B最多的字元的操作,比如A=“aaaaabbbbbbabababa” B=“aaccbaab”,應當刪除“aab”的,不是aa,相信知道搜尋引擎的朋友肯定是知道的吧,這種演算法主要用於去除頁面中無效的關鍵字,來減少收錄的計算消耗的一種方法,好了,具體演

字串匹配演算法BF KMP

BF演算法(樸素演算法): 思路: 子串的定位操作通常稱做模式匹配,其中子串稱做模式串,主串稱做目標串,樸素的模式匹配演算法即是模式匹配的一種演算法,其優點是簡單易懂,易於理解,某些應用場合

字串處理演算法求2個字串最長公共部分

基礎演算法連結快速通道,不斷更新中: 整型陣列處理演算法部分: 整型陣列處理演算法(一)按照正態分佈來排列整型陣列元素 整型陣列處理演算法(二)檔案中有一組整數,要求排序後輸出到另一個檔案中 整型陣列處理演算法(三)把一個數組裡的所有元素,插入到另一個數組的指定位置 整型陣列

資料結構演算法--遞迴

遞迴條件: 1.遞迴條件:每次調自己,然後記錄當時的狀態 2.基準條件:執行到什麼時候結束遞迴,不然遞迴就會無休止的呼叫自己, 遞迴的資料結構:棧(先進先出)和彈夾原理一樣,每一次呼叫自己都記錄了當時的一種狀態,然後把這種狀態的結果返回。 棧相對應的資料結構:佇列(先進後出

演算法之排序

排序演算法很多,常用的排序演算法有:氣泡排序、插入排序、選擇排序、歸併排序、快速排序、計數排序、基數排序、桶排序。 接下來一一介紹幾種排序的時間複雜度及優缺點。 插入排序與氣泡排序的時間複雜度相同O(n^2),開發中我們更傾向插入排序,而不是氣泡排序 排序演算法執行效率: 1.最好、最壞、平均情況時間

圖——基本的圖演算法圖的遍歷

圖——基本的圖演算法(二)圖的遍歷 1. 基本概念 圖的遍歷指的是從圖中的某個頂點出發訪問圖中其餘的頂點,且每個頂點只被訪問一次的這個過程。通常來說,圖的遍歷次序有兩種:深度優先遍歷(Depth first Search, DFS)和廣度優先遍歷(Breadth First Se

吳恩達老師機器學習筆記K-means聚類演算法

運用K-means聚類演算法進行影象壓縮 趁熱打鐵,修改之前的演算法來做第二個練習—影象壓縮 原始圖片如下: 程式碼如下: X =imread('bird.png'); % 讀取圖片 X =im2double(X); % unit8轉成double型別 [m,n,z]=size

深入理解線性迴歸演算法:正則項的詳細分析

前言 當模型的複雜度達到一定程度時,則模型處於過擬合狀態,類似這種意思相信大家看到個很多次了,本文首先討論了怎麼去理解複雜度這一概念,然後回顧貝葉斯思想(原諒我有點囉嗦),並從貝葉斯的角度去理解正則項的含義以及正則項降低模型複雜度的方法,最後總結全文。     &nb

Go語言字串高效拼接

在上一篇關於字串拼接的文章 Go語言字串高效拼接(一) 中,我們演示的多種字串拼接的方式,並且使用一個例子來測試了他們的效能,通過對比發現,我們覺得效能高的Builder並未發揮出其應該的效能,反而+號拼接,甚至strings.Join方法的效能更優越,那麼這到底是什麼原因呢?今天我們開始解開他們神祕的面紗,

spring boot 整合thyemleaf基本使用 字串的操作

thyemleaf基本使用 字串的操作的基本使用,其他的以後用到可以檢視文件或者百度。 <!--在輸入框中顯示msg的值--> <input type="text" name="username" th:value="${msg}"> <hr/> <!

Logistic迴歸之梯度上升優化演算法

Logistic迴歸之梯度上升優化演算法(二) 有了上一篇的知識儲備,這一篇部落格我們就開始Python3實戰 1、資料準備 資料集:資料集下載 資料集內容比較簡單,我們可以簡單理解為第一列X,第二列Y,第三列是分類標籤。根據標籤的不同,對這些資料點進行分類。  

「日常訓練&知識學習」莫隊演算法:樹上莫隊Count on a tree II,SPOJ COT2

題意與分析 題意是這樣的,給定一顆節點有權值的樹,然後給若干個詢問,每次詢問讓你找出一條鏈上有多少個不同權值。 寫這題之前要參看我的三個blog:CFR326D2E、CFR340D2E和HYSBZ-1086,然後再看這幾個Blog—— 參考A:https://blog.sengxian.com/algori

從零開始學演算法選擇排序

從零開始學演算法(二)選擇排序 選擇排序 演算法介紹 演算法原理 演算法簡單記憶說明 演算法複雜度和穩定性 程式碼實現 選擇排序 程式碼是Javascript語言寫的(幾乎是虛擬碼) 演算

決策樹-剪枝演算法

ID3演算法的的原理,它是以資訊熵為度量,用於決策樹節點的屬性選擇,每次優選資訊量最多 的屬性,以構造一顆熵值下降最快的決策樹,到葉子節點處的熵值為0,此時每個葉子節點對應的例項集中的例項屬於同一類。 理想的決策樹有三種: 1.葉子節點數最少 2.葉子加點深度最小 3.葉子節點數最少且葉子

程式設計演算法第八週測驗 3:棋盤問題

3:棋盤問題 檢視 提交 統計 提問 總時間限制:  1000ms   記憶體限制:  65536kB 描述 在一個給定形狀的棋盤(形狀可能是不規則的)上面擺放棋子,棋子沒有區別。要求擺放時任意的兩個棋子不能放

程式設計演算法第八週測驗 2:A Knight's Journey

2:A Knight's Journey 檢視 提交 統計 提問 總時間限制:  1000ms   記憶體限制:  65536kB 描述 Background The knight is getting bor