漫畫:什麼是字符串匹配算法?

2021-01-20 CSDN

————— 第二天 —————

什麼意思呢?讓我們來舉一個例子:

在上圖中,字符串B是A的子串,B第一次在A中出現的位置下標是2(字符串的首位下標是0),所以返回 2。

我們再看另一個例子:

在上圖中,字符串B在A中並不存在,所以返回 -1。

為了統一概念,在後文中,我們把字符串A稱為主串,把字符串B稱為模式串。

小灰的想法簡單粗暴,讓我們用下面的例子來演示一下:

第一輪,我們從主串的首位開始,把主串和模式串的字符逐個比較:

顯然,主串的首位字符是a,模式串的首位字符是b,兩者並不匹配。

第二輪,我們把模式串後移一位,從主串的第二位開始,把主串和模式串的字符逐個比較:

主串的第二位字符是b,模式串的第二位字符也是b,兩者匹配,繼續比較:

主串的第三位字符是b,模式串的第三位字符也是c,兩者並不匹配。

第三輪,我們把模式串再次後移一位,從主串的第三位開始,把主串和模式串的字符逐個比較:

主串的第三位字符是b,模式串的第三位字符也是b,兩者匹配,繼續比較:

主串的第四位字符是c,模式串的第四位字符也是c,兩者匹配,繼續比較:

主串的第五位字符是e,模式串的第五位字符也是e,兩者匹配,比較完成!

由此得到結果,模式串 bce 是主串 abbcefgh 的子串,在主串第一次出現的位置下標是 2:

以上就是小灰想出的解決方案,這個算法有一個名字,叫做BF算法,是Brute Force(暴力算法)的縮寫。

上圖的情況,在每一輪進行字符匹配時,模式串的前三個字符a都和主串中的字符相匹配,一直檢查到模式串最後一個字符b,才發現不匹配:

這樣一來,兩個字符串在每一輪都需要白白比較4次,顯然非常浪費。

假設主串的長度是m,模式串的長度是n,那麼在這種極端情況下,BF算法的最壞時間複雜度是O(mn)。

————————————

比較哈希值是什麼意思呢?

用過哈希表的朋友們都知道,每一個字符串都可以通過某種哈希算法,轉換成一個整型數,這個整型數就是hashcode:

hashcode = hash(string)

顯然,相對於逐個字符比較兩個字符串,僅比較兩個字符串的hashcode要容易得多。

給定主串和模式串如下(假定字符串只包含26個小寫字母):

第一步,我們需要生成模式串的hashcode。

生成hashcode的算法多種多樣,比如:

按位相加

這是最簡單的方法,我們可以把a當做1,b當做2,c當做3......然後把字符串的所有字符相加,相加結果就是它的hashcode。

bce = 2 + 3 + 5 = 10

但是,這個算法雖然簡單,卻很可能產生hash衝突,比如bce、bec、cbe的hashcode是一樣的。

轉換成26進位數

既然字符串只包含26個小寫字母,那麼我們可以把每一個字符串當成一個26進位數來計算。

bce = 2*(26^2) + 3*26 + 5 = 1435

這樣做的好處是大幅減少了hash衝突,缺點是計算量較大,而且有可能出現超出整型範圍的情況,需要對計算結果進行取模。

為了方便演示,後續我們採用的是按位相加的hash算法,所以bce的hashcode是10:

第二步,生成主串當中第一個等長子串的hashcode。

由於主串通常要長於模式串,把整個主串轉化成hashcode是沒有意義的,只有比較主串當中和模式串等長的子串才有意義。

因此,我們首先生成主串中第一個和模式串等長的子串hashcode,

即abb = 1 + 2 + 2 = 5:

第三步,比較兩個hashcode。

顯然,5!=10,說明模式串和第一個子串不匹配,我們繼續下一輪比較。

第四步,生成主串當中第二個等長子串的hashcode。

bbc = 2 + 2 + 3 = 7:

第五步,比較兩個hashcode。

顯然,7!=10,說明模式串和第二個子串不匹配,我們繼續下一輪比較。

第六步,生成主串當中第三個等長子串的hashcode。

bce= 2 + 3 + 5 = 10:

第七步,比較兩個hashcode。

顯然,10 ==10,兩個hash值相等!這是否說明兩個字符串也相等呢?

別高興的太早,由於存在hash衝突的可能,我們還需要進一步驗證。

第八步,逐個字符比較兩字符串。

hashcode的比較只是初步驗證,之後我們還需要像BF算法那樣,對兩個字符串逐個字符比較,最終判斷出兩個字符串匹配。

最後得出結論,模式串bce是主串abbcefgh的子串,第一次出現的下標是2。

什麼意思呢?讓我們再來看一個例子:

上圖中,我已知子串abbcefg的hashcode是26,那麼如何計算下一個子串,也就是bbcefgd的hashcode呢?

我們沒有必要把子串的字符重新進行累加運算,而是可以採用一個更簡單的方法。由於新子串的前面少了一個a,後面多了一個d,所以:

新hashcode = 舊hashcode - 1 + 4 = 26-1+4 = 29

再下一個子串bcefgde的計算也是同理:

新hashcode = 舊hashcode - 2 + 5 = 29-2+5 = 32

publicstaticintrabinKarp(String str, String pattern){//主串長度int m = str.length();//模式串的長度int n = pattern.length();//計算模式串的hash值int patternCode = hash(pattern);//計算主串當中第一個和模式串等長的子串hash值int strCode = hash(str.substring(0, n));//用模式串的hash值和主串的局部hash值比較。//如果匹配,則進行精確比較;如果不匹配,計算主串中相鄰子串的hash值。for (int i=0; i<m-n+1; i++) {if(strCode == patternCode && compareString(i, str, pattern)){return i; }//如果不是最後一輪,更新主串從i到i+n的hash值if(i<m-n){ strCode = nextHash(str, strCode, i, n); } }return-1;}privatestaticinthash(String str){int hashcode = 0;//這裡採用最簡單的hashcode計算方式://把a當做1,把b當中2,把c當中3.....然後按位相加for(int i = 0; i < str.length(); i++) { hashcode += str.charAt(i)-'a'; }return hashcode;}privatestaticintnextHash(String str, int hash, int index, int n){ hash -= str.charAt(index)-'a'; hash += str.charAt(index+n)-'a';return hash;}privatestatic boolean compareString(int i, String str, String pattern){ String strSub = str.substring(i, i+pattern.length());return strSub.equals(pattern);}publicstaticvoidmain(String[] args){ String str = "aacdesadsdfer"; String pattern = "adsd"; System.out.println("第一次出現的位置:" + rabinKarp(str, pattern));}

本文為程式設計師小灰(ID:chengxuyuanxiaohui)投稿。

相關焦點

  • 在JavaScript字符串的search()方法中,如何匹配正則表達式?
    正則表達式的核心功能是建立一種匹配模式,這個匹配模式可以理解為模板,模子。然後再拿具體的字符串來與這個模式進行匹配,如果匹配上,則表示符合要求,則進一步採用措施。第二節:正則表達式特點正則表達式是由字符串組成的。正則表達式只是一種搜索模式或匹配模式。對於具體的字符串,需要經過正則表達式的計算後,形成一個值來判斷是否匹配上。
  • JavaScript字符串 - 查找方法
    字符串查找的方法子字符串代表的就是要查找的字符串1.indexOf();格式:字符串.indexOf( 子字符串串,開始查找的位置 );返回值:如果在字符串中查找到了子字符串第一次出現的位置,返回子字符串出現的位置,否則沒有查找到返回 -
  • 正則表達式:如何匹配一個或多個字符?
    (英文句號) 可以匹配任何一個單個的字符。如果你曾經使用過DOS的文件搜索功能,你將發現正則表達式裡的,字符相當於DOS的?字符,SQL用戶將注意到正則表達式裡的、字符相當於SQL中的_(下劃線)字符。於是,用正則表達式c.t進行的搜索將匹配到cat和cot (還能匹配到一些毫無意義的單詞)。正則表達式sales將把由字符串sales和另外一個字符構成的文件名查找出來。
  • php刪除字符串兩邊的空白符:trim()、ltrim()、rtrim()
    基本概念在現實中的很多情況下,我們都需要先清除一個字符串左右兩邊的空白字符,然後再使用它。比如我們要求用戶在網頁中的一個輸入框中輸入他的手機號,當用戶輸入的字符串被提交到服務端後,我們需要驗證它是否符合正確的手機號格式。
  • JavaScript字符串-概念
    字符串的概念 概念: 在JavaScript中將所有單引號或雙引號括起來的都叫做字符串 object對象有屬性和函數,對象具體的概念後面會再跟大家講 2.省略new運算符創建 運行效果如下,同樣是字符串類型
  • 深蘭科學院基礎研究厚積薄發,「長序列比對算法」助攻戰「疫」
    比如,就拿上面提到的那兩個字符串AABABA和AACAABA來說,我們可以先試著用AA匹配AA,最後看看這樣匹配的結果是什麼。比如說這樣匹配的得分是100分,那麼接著我們再考慮用AABA匹配AABA,假設得分是120,那麼我們最終就採用後一個方案。即使不考慮如何記錄回溯點以及如何在回溯點恢復現場這樣複雜的技術問題,回溯法仍然不是我們的最優選擇。
  • MySQL字符串截取 和 截取字符進行查詢
    通過mysql自帶的一些字符串截取函數,對數據進行處理,下面是我整理的字符串截取 和 截取字符進行查詢。一、MySQL中字符串的截取MySQL中有專門的字符串截取函數:其中常用的有兩種:substring_index(str,delim,count) 和concat 1.substring_index(str,delim,count) 函數的使用較為普遍。
  • 10個很棒的 JavaScript 字符串技巧
    我們稱一個字符序列為字符串。這幾乎是所有程式語言中都有的基本類型之一。這裡跟大家展示關於 JS 字符串的10個很棒的技巧,你可能還不知道哦?1.如何多次複製一個字符串JS 字符串允許簡單的重複,與純手工複製字符串不同,我們可以使用字符串的repeat方法。2. 如何填充一個字符串到指定的長度有時,我們希望字符串具有特定長度。
  • 按任意符號間隔拆分字符串的函數
    大家好,我們今日講解「VBA信息獲取與處理」教程中第十四個專題「Split函數提取數據信息的深入講解」的第二節「按任意符號間隔拆分字符串的函數」,這個專題是非常實用的知識點,希望大家能掌握利用。第二節 按任意符號間隔拆分字符串的函數在上一講中,我們講解了Split函數的基本應用,但我們很快會發現,這個函數在利用起來有一定的局限性,只能按某個字符串進行拆分,在實際的應用中,如果我們要按多個字符串進行拆分,這個函數就無能為力了,怎麼辦?我們可以擴展一下這個函數的功能。
  • Python中去除字符串首尾空格、特殊字符和指定子字符串的方法
    ;使用print()函數輸出字符串時,其中的特殊字符「\n、\r、\t」則被默認為命令執行了;使用strip()方法,只能去除字符串首尾的空格和特殊字符,存在於字符串中間的空格和特殊字符是無法去除的。>去除字符串首尾指定的子字符串從strip()方法中,又延伸出了去除字符串開頭和結尾位置空格、特殊字符和指定子字符串的方法。
  • pandas向量化字符串操作方法!
    作者:小伍哥 來源:AI入門學習python內置一系列強大的字符串處理方法,但這些方法只能處理單個字符串,處理一個序列的字符串時,需要用到循環。那麼,有沒有辦法,不用循環就能同時處理多個字符串呢,pandas的向量化操作就提供了這樣的方法。
  • 並行算法庫清單: 附各算法代碼實例!
    【IT168 資訊】並行算法採用並行程式語言NESL實現,該語言是卡內基梅隆大學Scandal項目中開發的一種程式語言。對於每個算法,團隊給出了一個簡短的描述以及複雜性評估(就工作和深度而言)。  在很多情況下,NESL代碼已經設置完畢,可以使用FORMs基本接口來運行算法。隨意更改數據或算法,並提交修改後的版本。
  • Go語言學習筆記之字符串一
    \ fmt.Println("\nContains函數判斷字符串包含關係:") str4 := "Ajian loves python and goland" fmt.Println(str4) fmt.Println(strings.Contains(str4,"jian")) //Index 函數是返回某字符在字符串的下標,在字符串裡面返回下標,否則-1(註:返回的是首次出現的下標)
  • 深入剖析go中字符串的編碼問題——特殊字符的string怎麼轉byte?
    沒有經過字節級別的轉義,那麼字符串是一個標準的utf8序列。有了前面的基礎知識和字符串是一個標準的utf8序列這一結論後我們接下來對字符串「」(如果無法展示,記住該特殊字符的unicode是\u0081)手動編碼。
  • Python基礎教程(一) - 序列:字符串、列表和元組
    對於字符串來說就是判斷一個字符是否屬於一個字符串;對於列表和元組,就代表一個對象是否屬於該對象。返回值一般來講是True/False,語法為:對象 [not] in 序列連結操作符(+):這個操作符允許我們把一個序列和另一個相同類型的序列做連接。
  • 在C語言中如何高效地複製和連接字符串?
    儘管這些函數可以同樣很容易地定義為返回一個指針來指向最後一個複製的字符(或它的後一位),而且事實證明這種做法也非常有用。兩個或多個字符串的連接操作的最佳複雜度和字符數量成線性關係。但是,如上所述,讓函數返回指向目標字符串的指針會導致操作的效率明顯低於最佳效率。該函數遍歷源字符串序列和目標字符串序列,並獲取指向這兩個序列末尾的指針。
  • 附代碼|OpenCV實現銀行卡號識別,字符識別算法你知多少?
    字符識別是模式識別的一個重要應用,首先提取待識別字符的特徵;然後對提取出來的特徵跟字符模板的特徵匹配;最後根據準則判定該字符所屬的類別。不同的訓練方法,不同的特徵提取, 不同的匹配規則,就相應的有不同的字符識別方法,基本上很多就是在這些地方做改進,或者是採用新的規則。但是萬變不離其宗。1、模板匹配字符識別算法。
  • 正則表達式 – 匹配規則
    正則表達式 - 匹配規則基本模式匹配一切從最基本的開始。模式,是正規表達式最基本的元素,它們是一組描述字符串特徵的字符。模式可以很簡單,由普通的字符串組成,也可以非常複雜,往往用特殊的字符表示一個範圍內的字符、重複出現,或表示上下文。
  • Python正則表達式:特殊符號和字符
    正表達式為高級的文本模式匹配,抽取,與/或文本形式的搜索和替換功能提供了基礎。簡而言之,正則表達式(簡稱regex)是由一些字符和特殊符號組成的字符串,它描述了模式的重複或者表達多個字符。python通過標準庫中的re模塊來支持正則表達式。
  • Excel小技巧|三種方法計算算式字符串
    Excel中針對一列算式字符串的問題,如果才能計算得出正確結果?如下圖所示,A列是一列算式字符串,如何計算其正確的結果,即如何在算式字符串前面加個"="並使之正常計算,這裡我們用三種方法處理,總有一種適合你哦!