日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

字符串匹配算法(二):BM(BoyerMoore)算法、坏字符规则,好后缀规则

發布時間:2024/4/11 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 字符串匹配算法(二):BM(BoyerMoore)算法、坏字符规则,好后缀规则 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • BM算法
  • 壞字符規則
  • 好后綴規則
  • 完整代碼


BM算法

BM算法的全程叫做Boyer-Moore,是工程上最常用且最高效的字符串匹配算法,有實驗統計,它的性能是著名的KMP 算法的 3 到 4 倍。那么它是如何將性能提升的呢?

在上一篇博客中我介紹了BF算法和RK算法,其中也提到過如果想要優化字符串匹配的效率,就必須要減少不必要的比較,例如RK算法就是通過預匹配哈希值來完成了這一功能,但是我們也提到了,由于哈希沖突等原因,RK在最壞的情況下就會退化成BF算法。

基于上述問題的缺陷,BM、KMP(下一篇博客會寫)等算法采用了大量滑動的機制來解決這一問題

在RK和BF算法中,在字符串不匹配的時候,我們通常會將模式串滑動到主串的下一個位置繼續進行匹配,這種方法存在一定的缺陷,就是即使我們滑動到的位置不可能完成匹配,我們還是會一個一個去嘗試進行配對,這也就是它們效率低下的原因。

就例如上圖,我們可以發現a只存在于主串的第一個位置,第四個位置,第六個位置,而其他的位置下模式串是不可能匹配成功的,所以我們滑動的時候就應該直接滑動到上述的位置,如下圖

BM算法的核心就是找到這種大量滑動的規律,減少無意義的匹配。而它正是通過壞字符規則好后綴規則來實現。


壞字符規則

因為我們的壞字符和好后綴規則都需要保證偏移量最大,所以其并非像傳統的字符串比較一樣從前往后,而是從后往前比較,并且我們將第一個遇見的不匹配的字符稱為壞字符

當檢測到壞字符后,我們就沒有必要再一個一個的進行判斷了,因為只有模式串與壞字符T對齊的位置也是字符T的情況下,兩者才有匹配的可能。并且為了保證滑動的范圍最大,我們對字符T的選擇是在模式串中最后一次出現的那個

壞字符規則主要有以下三種情況,下面一一對其進行分析

  • 模式串中存在與壞字符相同的字符
  • 模式串中不存在與壞字符相同的字符
  • 模式串中存在的其他壞字符為第一個字符
  • 情況一:模式串中存在與壞字符相同的字符

    此時的處理方法就是將壞字符與匹配字符對其,接著進行判斷

    此時匹配成功

    情況二:模式串中不存在與壞字符相同的字符

    此時模式串中不存在可以與壞字符匹配的字符,這也就代表著在壞字符這個位置之前,不可能匹配成功,所以我們直接滑動到壞字符的下一個位置

    此時,匹配成功。

    情況三:倒退或者不移動
    例如以下情景
    后面的全部匹配,不匹配的只有b,而壞字符又在最后面出現過,此時就會倒退。
    所以我們還需要加上判斷,如果滑動值小于等于0時,就直接向后滑動一步。

    為了保存每個字符的最后一次出現的下標,我們使用一個數組來模擬哈希,采用ascii碼來進行直接定址

    下面是基于壞字符規則實現的BM算法

    //構建壞字符規則的下標數組 void generateBC(const string& pattern, int* indexArr, int len) {//初始化for(int i = 0; i < len; i++){indexArr[i] = -1;}//記錄模式串中每個下標最后出現的位置for(int i = 0; i < pattern.size(); i++){indexArr[pattern[i]] = i; } }int boyerMoore(const string& str, const string& pattern) {//不滿足條件則直接返回falseif(str.empty() || pattern.empty() || str.size() < pattern.size()){return -1;}int len1 = str.size(), len2 = pattern.size();int indexArr[128] = {0}; //壞字符規則記錄數組,記錄了每一個字符最后一次出現的下標generateBC(pattern, indexArr, 128);int i = 0;while(len1 - i >= len2){int j;//模式串從后往前匹配for(j = len2 - 1; j >= 0; j--){//如果當前字符不匹配,則說明該位置是壞字符if(str[i + j] != pattern[j]){break;}}//如果全部匹配,則返回主串起始位置if(j < 0){return i;}/*如果該字符沒出現過,則直接將模式串滑動到壞字符的下一個位置如果出現過,則將模式串中對應字符滑動到壞字符處*/int badMove = (j - indexArr[str[i + j]]);badMove = (badMove == 0) ? 1 : badMove; //防止倒退i += badMove;}return -1; }

    從上面也可以看出,在最后一種情況下BM算法的效率就又會退化到BF算法的級別,所以為了防止這種問題,BM還有一種好前綴規則


    好后綴規則

    在我們進行匹配的時候,我們將第一次碰到的不匹配的字符稱為壞字符,而將碰到壞字符之前所匹配到的字符串稱為好后綴

    與壞字符規則一樣,如果我們想要使得字符串匹配,只有模式串中存在相同子串,并與主串中好后綴對齊的情況下,兩者才有匹配的可能。所以直接將對應子串滑動到好后綴的位置,如下圖

    如果不存在這個子串,那我們能否按照壞字符規則,則直接跳過好后綴后呢?

    答案是否定的,如果我們因為過度滑動導致我們跳過了本身可匹配的一些字符串,如下圖

    這是為什么呢?雖然我們的模式串中并不存在能夠與好后綴匹配的子串,但是卻存在能夠與好后綴部分重合的子串,而我們的滑動就導致了跳過了這些子串

    為了防止上述情況,我們此時就會尋找能夠與部分好后綴子串匹配的前綴,并以它為滑動的標準,如下圖

    為了方便計算,我們需要保存模式串中所有前綴和后綴的匹配情況以及子串的位置,所以需要引入兩個數組,一個是整型數組suffix,其用于標記能夠與好后綴匹配的子串的下標。另一個是布爾數組prefix,其用于標記前綴[0, i - 1]是否能夠與好后綴進行匹配

    //構建好后綴規則的前綴和后綴數組 void generateGS(const string& pattern, vector<int>& suffix, vector<bool>& prefix) {int len = suffix.size();//匹配區間[0 ~ len - 1],len時即為整個模式串,不可能存在前綴for(int i = 0; i < len - 1; i++){int j = i;int size = 0; //匹配子串的下標while(j >= 0 && pattern[j] == pattern[len - 1 - size]){//繼續匹配下一個位置j--; size++; suffix[size] = j + 1; //記錄匹配后綴的子串的位置}//如果子串一直匹配到開頭,則說明該子串為前綴,此時前綴與后綴匹配if(j == -1){prefix[size] = true;}} }//計算出好后綴規則的偏移量 int moveByGS(int index, const vector<int>& suffix, const vector<bool>& prefix) {int len = suffix.size(); //模式串長度int size = len - 1 - index; //后綴長度//如果存在與后綴匹配的子串,則直接返回它們的偏移量if(suffix[size] != -1){return index - suffix[size] + 1;}//如果沒有匹配的后綴,那么判斷后綴中是否有部分與前綴匹配for(int i = index + 2; i <= len - 1; i++){if(prefix[len - i] == true){return i;}}//如果也不存在,則說明沒有任何匹配,直接偏移整個模式串的長度return len; }

    完整代碼

    好前綴和壞字符都實現了,因為我們希望的是盡量減少不必要的匹配,所以我們選取兩者中較大的那一個作為偏移量

    將上面的好前綴規則加入前面寫的壞字符規則的框架中,就是完整的BM算法,代碼如下

    //構建壞字符規則的下標數組 void generateBC(const string& pattern, vector<int>& indexArr) {//記錄模式串中每個下標最后出現的位置for(int i = 0; i < pattern.size(); i++){indexArr[pattern[i]] = i; } }//構建好后綴規則的前綴和后綴數組 void generateGS(const string& pattern, vector<int>& suffix, vector<bool>& prefix) {int len = suffix.size();//匹配區間[0 ~ len - 1],len時即為整個模式串,不可能存在前綴for(int i = 0; i < len - 1; i++){int j = i;int size = 0;while(j >= 0 && pattern[j] == pattern[len - 1 - size]){//繼續匹配下一個位置j--; size++; suffix[size] = j + 1; //記錄匹配后綴的子串的位置}//如果子串一直匹配到開頭,則說明該子串為前綴,此時前綴與后綴匹配if(j == -1){prefix[size] = true;}} }int moveByGS(int index, const vector<int>& suffix, const vector<bool>& prefix) {int len = suffix.size(); //模式串長度int size = len - 1 - index; //后綴長度//如果存在與后綴匹配的子串,則直接返回它們的偏移量if(suffix[size] != -1){return index - suffix[size] + 1;}//如果沒有匹配的后綴,那么判斷后綴中是否有部分與前綴匹配for(int i = index + 2; i <= len - 1; i++){if(prefix[len - i] == true){return i;}}//如果也不存在,則說明沒有任何匹配,直接偏移整個模式串的長度return len; }int boyerMoore(const string& str, const string& pattern) {//不滿足條件則直接返回falseif(str.empty() || pattern.empty() || str.size() < pattern.size()){return -1;}int len1 = str.size(), len2 = pattern.size();vector<int> indexArr(128, -1); //標記匹配壞字符的字符下標vector<int> suffix(len2, -1); //標記匹配后綴的子串下標vector<bool> prefix(len2, false); //標記是否匹配前綴generateBC(pattern, indexArr);generateGS(pattern, suffix, prefix);int i = 0;while(len1 - i >= len2){int j;//模式串從后往前匹配for(j = len2 - 1; j >= 0; j--){//如果當前字符不匹配if(str[i + j] != pattern[j]){break;}}//如果全部匹配,則返回主串起始位置if(j < 0){return i;}int badMove = (j - indexArr[str[i + j]]); //壞字符規則偏移量badMove = (badMove == 0) ? 1 : badMove; //防止倒退int goodMove = 0; //好后綴規則偏移量//如果一個都不匹配,則不存在后綴if(j < len2 - 1){goodMove = moveByGS(j, suffix, prefix); //計算出好后綴的偏移量}i += max(goodMove, badMove); //加上最大的那個}return -1; }

    總結

    以上是生活随笔為你收集整理的字符串匹配算法(二):BM(BoyerMoore)算法、坏字符规则,好后缀规则的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。