C語(yǔ)言字符串的模式匹配之BF與KMP
確定一個(gè)子串(模式串)在主串中第一次出現(xiàn)的位置。
BF算法(Brute-Force算法)
BF算法即樸素的簡(jiǎn)單匹配法,采用的是窮舉的思路。從主串的每一個(gè)字符開(kāi)始依次與模式串的字符進(jìn)行比較。
int index_BF(SeqString S, SeqString T, int begin)//從S的第begin位(下標(biāo))開(kāi)始進(jìn)行匹配判斷 { int i = begin, j = 0; while (i < S.length && j < T.length) { if (S.ch[i] == T.ch[j]) { i ++; j ++;//比較下一個(gè)字符 } else { i = i - j + 1; j = 0;//模式串回溯到起點(diǎn) } } if (j == T.length) return i - T.length; //匹配成功,則返回該模式串在主串中第一次出現(xiàn)的位置下標(biāo) else return -1; }
int index_BF(char S[], char T[], int beg) { int i = beg, j = 0; while (i < strlen(S) && j < strlen(T)) { if (S[i] == T[j]) { i ++; j ++; } else { i = i - j + 1; j = 0; } } if (i == strlen(S)) return i - strlen(T); else return -1; } int main() { char str1[10] = "abcde"; char str2[10] = "cde"; printf("%d", index_BF(str1, str2, 0)); return 0; }
KMP算法(快速的)
基本思想為:主串的指針 i i i不必回溯,利用已經(jīng)得到前面“部分匹配”的結(jié)果,將模式串向右滑動(dòng)若干個(gè)字符,繼續(xù)與主串中的當(dāng)前字符進(jìn)行比較,減少了一些不必要的比較。
時(shí)間復(fù)雜度為 O ( n + m )
KMP算法的核心,是一個(gè)被稱(chēng)為部分匹配表(Partial Match Table)的數(shù)組。
首先要明白什么是字符串的前綴和后綴。
如果字符串A和B,存在A=BS,其中S是任意的非空字符串,那就稱(chēng)B為A的前綴。例如,”Harry”的前綴包括{”H”, ”Ha”,”Har”, ”Harr”},我們把所有前綴組成的集合,稱(chēng)為字符串的前綴集合。
同樣可以定義后綴A=SB,其中S是任意的非空字符串,那就稱(chēng)B為A的后綴,例如,”P(pán)otter”的后綴包括{”otter”, ”tter”, ”ter”, ”er”, ”r”},然后把所有后綴組成的集合,稱(chēng)為字符串的后綴集合。
要注意的是,字符串本身并不是自己的前綴或后綴。
PMT中的值是字符串的前綴集合與后綴集合的交集中最長(zhǎng)元素的長(zhǎng)度。
比如,對(duì)于字符串”ababa”,它的前綴集合為{”a”, ”ab”, ”aba”, ”abab”},它的后綴集合為{”baba”, ”aba”, ”ba”, ”a”}, 兩個(gè)集合的交集為{”a”, ”aba”},其中最長(zhǎng)的元素為”aba”,長(zhǎng)度為3,即該字符串在PMT表中的值為3。性質(zhì)為:該字符串前3個(gè)字符與后三個(gè)字符相同。
如果模式串有 j個(gè)字符,則PMT表中就有 j 個(gè)數(shù)值。其中第一個(gè)數(shù)值總為0。
int index_KMP(SeqString S, SeqString T, int begin)//從S的第begin位(下標(biāo))開(kāi)始進(jìn)行匹配判斷 { int i = begin, j = 0; while (i < S.length && j < T.length) { if (j == -1 || S.ch[i] == T.ch[j]) { i ++; j ++; } else j = next[j];//即PMT[j-1] } if (j == T.length) return i - T.length; //匹配成功,則返回該模式串在主串中第一次出現(xiàn)的位置下標(biāo) else return -1; }
那么該如何求出next數(shù)組呢?
其實(shí),求next數(shù)組的過(guò)程完全可以看成字符串匹配的過(guò)程,即以模式字符串為主字符串,以模式字符串的前綴為目標(biāo)字符串,一旦字符串匹配成功,那么當(dāng)前的next值就是匹配成功的字符串的長(zhǎng)度。
具體來(lái)說(shuō),就是從模式字符串的第一位(注意,不包括第0位)開(kāi)始對(duì)自身進(jìn)行匹配運(yùn)算。 在任一位置,能匹配的最長(zhǎng)長(zhǎng)度就是當(dāng)前i位置的next值。如下圖所示。
void GetNext(SeqString T, int next[]) { next[0] = -1; int j = 0, k = -1;//起始時(shí)k落后j一位 while (j < T.length)//j遍歷一遍模式串,對(duì)于每個(gè)字符得到該位置的next數(shù)組的值 { if (k == -1 || T.ch[j] == T.ch[k]) { j ++; next[j] = k + 1;//將j視為指向一個(gè)子串(后綴)結(jié)束后的下一個(gè)字符,k指向一個(gè)子串(前綴)的最后一個(gè)字符,則這兩個(gè)子串的重疊部分的長(zhǎng)度(k下標(biāo)從0開(kāi)始)即PMT[j-1]的值 k ++; /*也可以簡(jiǎn)便地寫(xiě)為(易記): j ++; k ++; next[j] = k; 最簡(jiǎn)單的形式為: next[++ j] = ++ k; */ } else k = next[k];//k回溯,即將第二個(gè)子串(右滑)(減小匹配的前綴長(zhǎng)度) } }
即:
#include <stdio.h> #include <string.h> int next[10];//全局?jǐn)?shù)組 void GetNext(char T[]) { int j = 0, k = -1; next[0] = -1; while (j < strlen(T)) { if (k == -1 || T[j] == T[k]) { j ++; next[j] = k + 1; k ++; } else k = next[k]; } } int index_KMP(char S[], char T[], int begin)//從S的第begin位(下標(biāo))開(kāi)始進(jìn)行匹配判斷 { int i = begin, j = 0; while (i < strlen(S) && strlen(T)) { if (j == -1 || S[i] == T[j]) { i ++; j ++; } else j = next[j];//即PMT[j-1] } if (j == strlen(T)) return i - strlen(T); //匹配成功,則返回該模式串在主串中第一次出現(xiàn)的位置下標(biāo) else return -1; } int main() { char str1[10] = "abcde"; char str2[10] = "cde"; GetNext(str2); printf("%d", index_KMP(str1, str2, 0)); return 0; }
求next數(shù)組的方法也可進(jìn)行優(yōu)化:
void GetNextVal(SeqString T, int nextval[]) { nextval[0] = -1; int j = 0, k = -1; while (j < T.length) { if (k == -1 || T.ch[j] == T.ch[k]) { j ++; k ++; if (T.ch[j] != T.ch[k]) nextval[j] = k; else nextval[j] = nextval[k]; } else k = nextval[k]; } }
即:
int nextval[10];//全局?jǐn)?shù)組 void GetNextVal(char T[]) { int j = 0, k = -1; nextval[0] = -1; while (j < strlen(T)) { if (k == -1 || T[j] == T[k]) { j ++; k ++; if (T[j] != T[k]) nextval[j] = k; else nextval[j] = nextval[k]; } else k = nextval[k]; } } int index_KMP(char S[], char T[], int begin)//從S的第begin位(下標(biāo))開(kāi)始進(jìn)行匹配判斷 { int i = begin, j = 0; while (i < strlen(S) && strlen(T)) { if (j == -1 || S[i] == T[j]) { i ++; j ++; } else j = nextval[j]; } if (j == strlen(T)) return i - strlen(T); //匹配成功,則返回該模式串在主串中第一次出現(xiàn)的位置下標(biāo) else return -1; } int main() { char str1[10] = "abcde"; char str2[10] = "bcde"; GetNextVal(str2); printf("%d", index_KMP(str1, str2, 0)); return 0; }
KMP—yxc模板
字符串從數(shù)組下標(biāo)1開(kāi)始存
#include <iostream> using namespace std; const int M = 1000010, N = 100010; char S[M], p[N]; int ne[N]; //全局變量數(shù)組,初始化全為0 int main() { int m, n; cin >> m; for (int i = 1; i <= m; i ++) cin >> S[i]; cin >> n; for (int i = 1; i <= n; i ++) cin >> p[i];//主串與模式串均由數(shù)組下標(biāo)1開(kāi)始存儲(chǔ) // 也可以簡(jiǎn)寫(xiě)為 cin >> m >> S + 1 >> n >> p + 1; for (int i = 2, j = 0; i <= n; i ++)//求模式串各字符處的next值,即求串p[1~i]的前后綴最大交集的長(zhǎng)度 { //由于字符串由下標(biāo)1開(kāi)始存儲(chǔ),next[i]+1也是模式串下次比較的起始下標(biāo) while (j && p[i] != p[j + 1]) j = ne[j];//記錄的最大交集的長(zhǎng)度減小,直到為0,表示p[1~i]前后綴無(wú)交集 if (p[i] == p[j + 1]) j ++;//該位匹配成功 ne[i] = j;//j即該位的ne值 } for (int i = 1, j = 0; i <= m; i ++)//遍歷一遍主串 { while (j && S[i] != p[j + 1]) j = ne[j];//不匹配且并非無(wú)路可退,則j后滑。j==0意味著當(dāng)前i所指的字符與模式串的第一個(gè)字符都不一樣,只能等該輪循環(huán)結(jié)束i++,之后再比較 if (S[i] == p[j + 1]) j ++;//該位匹配成功 if (j == n)//主串與模式串匹配成功 { cout << i - n << ' ';//匹配時(shí),輸出 模式串首元素在主串中的下標(biāo) j = ne[j];//j后滑,準(zhǔn)備繼續(xù)尋找下一個(gè)匹配處 } } return 0; }
字符串從數(shù)組下標(biāo)為開(kāi)始存
const int N = 1000010; char s[N], p[N]; int ne[N]; int main() { int n, m; cin >> m >> p >> n >> s; ne[0] = -1;//ne[0]初始化為-1 for (int i = 1, j = -1; i < m; i ++ )//從模式串的第2位2開(kāi)始求next值 { while (j != -1 && p[j + 1] != p[i]) j = ne[j]; if (p[j + 1] == p[i]) j ++ ; ne[i] = j; } for (int i = 0, j = -1; i < n; i ++ )//遍歷一遍主串 { while (j != -1 && s[i] != p[j + 1]) j = ne[j]; if (s[i] == p[j + 1]) j ++ ; if (j == m - 1)//掃描到模式串結(jié)尾,說(shuō)明匹配完成 { cout << i - j << ' '; j = ne[j]; } } return 0; }
總結(jié)
本篇文章就到這里了,希望能夠給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
- C語(yǔ)言遞歸之漢諾塔和青蛙跳臺(tái)階問(wèn)題
- C 語(yǔ)言基礎(chǔ)之C 語(yǔ)言三大語(yǔ)句注意事項(xiàng)
- C 語(yǔ)言基礎(chǔ)之C語(yǔ)言的常見(jiàn)關(guān)鍵字
- C 語(yǔ)言基礎(chǔ)之初識(shí) C 語(yǔ)言常量
- C語(yǔ)言中的常量詳解
- C語(yǔ)言指針筆試題全面解析
- C語(yǔ)言之陷阱與缺陷詳解
- C語(yǔ)言位運(yùn)算符的具體使用
- 關(guān)于C語(yǔ)言 const 和 define 區(qū)別
- C語(yǔ)言 函數(shù)缺省參數(shù)詳情
- C 語(yǔ)言基礎(chǔ)實(shí)現(xiàn)青蛙跳臺(tái)階和漢諾塔問(wèn)題
相關(guān)文章
C++中實(shí)現(xiàn)隊(duì)列類(lèi)鏈?zhǔn)酱鎯?chǔ)與棧類(lèi)鏈?zhǔn)酱鎯?chǔ)的代碼示例
這篇文章主要介紹了C++中實(shí)現(xiàn)隊(duì)列類(lèi)鏈?zhǔn)酱鎯?chǔ)與棧類(lèi)鏈?zhǔn)酱鎯?chǔ)的代碼示例,通過(guò)注釋來(lái)說(shuō)明,直接上代碼,簡(jiǎn)單粗暴XD 需要的朋友可以參考下2016-03-03c/c++ 標(biāo)準(zhǔn)庫(kù) bind 函數(shù)詳解
bind是一組用于函數(shù)綁定的模板。在對(duì)某個(gè)函數(shù)進(jìn)行綁定時(shí),可以指定部分參數(shù)或全部參數(shù),也可以不指定任何參數(shù),還可以調(diào)整各個(gè)參數(shù)間的順序。這篇文章主要介紹了c/c++ 標(biāo)準(zhǔn)庫(kù) bind 函數(shù) ,需要的朋友可以參考下2018-09-09C語(yǔ)言實(shí)現(xiàn)矩陣翻轉(zhuǎn)(上下翻轉(zhuǎn)、左右翻轉(zhuǎn))
這篇文章主要介紹了C語(yǔ)言實(shí)現(xiàn)矩陣翻轉(zhuǎn)(上下翻轉(zhuǎn)、左右翻轉(zhuǎn))的相關(guān)資料,需要的朋友可以參考下2017-05-05C++實(shí)現(xiàn)LeetCode(107.二叉樹(shù)層序遍歷之二)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(107.二叉樹(shù)層序遍歷之二),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07Matlab實(shí)現(xiàn)同步子圖視角的方法詳解
這篇文章主要和大家分享三個(gè)可以Matlab中更簡(jiǎn)便實(shí)現(xiàn)同步子圖視角的技巧,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以學(xué)習(xí)一下2022-06-06用C++編寫(xiě)擴(kuò)展node.js(node-ffi版)
今天小編就為大家分享一篇關(guān)于用C++編寫(xiě)擴(kuò)展node.js(node-ffi版),小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12