kmp算法超詳細

在計算機科學中,字符串匹配是一個常見的問題。給定一個文本串和一個模式串,我們需要在文本串中找到所有與模式串匹配的位置。傳統的字符串匹配算法如暴力匹配(Brute Force)方法在最壞情況下的時間復雜度為O(m*n),其中m和n分別是文本串(長的字符串)和模式串(短的字符串)的長度,kmp算法是一種高效的字符串匹配算法。

廢話不多說我們直接介紹重點,帶你理解kmp算法

1. kmp 算法原理?

為什么暴力匹配這么慢?

我們發現當每次匹配失敗后,bf算法都會讓 文本串(長的字符串)后退到匹配的第一個字符的下一個字符,讓模式串(短的字符串)后退到第一個字符,重新開始匹配,例如:

0 1 2 3 4 5 6 7 8 9 10
a b c a b a b c a b d
a b c a b d
0 1 2 3 4 5
當我們使用bf算法時,在 5 下標位置匹配失敗時,會讓 文本串 后退到 1 下標 ,讓模式串后退到 0 下標,重新開始匹配,但其實我們發現:文本串的 1 下標和 模式串的 0 下標其實并不匹配,其實大可跳過,文本串的 1 下標,如果重新匹配,我們發現,只有從文本串的 3 位置開始匹配才可能成功,kmp算法對于bf算法的優化就是在于,跳過了那些一定匹配不上的位置。

?kmp的算法核心在于,讓文本串不后退:

如上述例子,我們在5位置匹配失敗了,此處不讓文本串后退,只讓 模式串 后退,我們發現,文本串,的 3 4?下標是和模式串的 0 1 下標匹配的,所以我們可以讓,讓模式串后退到 2?下標位置,與 文本串 5 下標位置 進行比較:

0 1 2 3 4 5 6 7 8 9 10
a b c a b a b c a b d?
? ? ? ? ?a b c a b d
? ? ? ? ?0 1 2 3 4 5

即相當于,我們跳過了用 模式串 與 1 位置, 和 2 位置 的比較,因為這兩個位置匹配一定是失敗的,也跳過了 ,模式串 的 0 1 下標和,文本串的 3 4 下標的比較,因為我們知道一定是成功的,所以直接從模式串的 3 位置與文本串的 5 位置開始匹配

我們要如何知道 模式串 回退的位置?靠眼睛看肯定是不行的

如果上面的內容沒有看懂,沒關系,請重點理解下面的內容:

2. 最長前綴后綴

在模式串中,如果一個子串的前綴和后綴相同,則稱該子串為前綴后綴。例如,模式串"a b c a b "的前綴有"a"、"ab"、"abc"、"abca"、"abca",后綴有"b"、"ab"、"cab"、"bcab"、"abca"。?
那么"abcab"的最長前綴后綴不就是 "ab"嗎。

注意:最長前綴后綴不能是這個字串本身

練習一下:
"abcdbcabcd"的最長前后綴是?
沒錯,是 "abcd"。現在你已經會求最長前后綴了,現在我們可以解決,模式串回退的位置的問題了,這是最關鍵的一步。
以剛才的例子來說:

0 1 2 3 4 5 6 7 8 9 10
a b c a b a b c a b d
a b c a b d
0 1 2 3 4 5
現在我們發現了不匹配的地方,根據kmp算法,我們只回退模式串,要知道回退的位置,我們剛才的最長前綴后綴就有用處了。我們發現:前面綠色的代碼匹配成功的字串。
我們現在把這兩個字串的最長前綴后綴標出:

0 1 2 3 4 5 6 7 8 9 10
a b c a b a b c a b d
a b c a b d
0 1 2 3 4 5

現在你發現了什么。
沒錯,模式串中匹配成功的字串的 0 1 下標?和 3 4 下標互為最長前綴后綴,即 0 1 下標 的字符與 3 4 下標的字符相等,即模式串的的0 1 下標與 文本串中的 3 4 下標 相等,所以我們移動模式串,讓模式串已經匹配成功的字串的 前綴 與 文本串中的后綴對應:

0 1 2 3 4 5 6 7 8 9 10
a b c a b a b c a b d
? ? ? ? ?a b c a b d
? ? ? ? ?0 1 2 3 4 5
于是我們得出,模式串匹配失敗后的回退的位置 為 最長前后綴的 前綴 的后一個位置,也就是前綴/后綴的長度當前例子為 2?

如果理解了這一步,kmp 的關鍵你就已經掌握了。
接下來就是一個重復的過程了,模式串中每一個字符的前面的字串都有最長前綴后綴,而且最長相等前后綴的長度是我們移位的關鍵,所以我們用一個next數組記錄下每個字符前面的字串的最長前綴后綴的長度,即在該字符匹配失敗后模式串回退的位置。
例如:a b c a b d 的next數組為:
下標:? ? ? ? ? 0 1 2 3 4 5?
模式串:? ?? ? a b c a b d
next數組:? ?-1 0 0 0 1 2
注意:在第一個位置 為 -1 做特殊處理,下面會詳細講解。最長前后綴為空字符串即長度為0。

下面做一個練習:
abcababcabca的next數組為?

?

答案:

-1 0 0 0 1 2 1 2 3 4 5 3

3. 計算next 數組

?將next數組用代碼計算出來:

我們發現,next 的值一定是序數遞增的,不會由 0 直接到2,只能先到1 再到2。

舉個例子:
?0 1 2 3 4 5
?a b c a b d
-1 0 0 0?1 2

5 位置前的字串 最長前綴后綴為 a b 長度為2,那 4 前面的字串的最長前綴后綴一定為 a?
如果4 前面 的最長前綴后綴為 0 即 0 位置的字符,與 3 位置的字符沒有匹配上,? 5 前面的字串的 最長前綴后綴 長度要為2 的話只能是 0 1小標和3 4 下標,所以0 3下標必須是匹配的 長度才可能為2。

那么,我們求 next數組,是不是就可以只判斷當前的前一個字符是不是和他的最長前綴后綴的長度的對應位置的字符是否相等,如果相等,則當前的next值即為前一個值+1.

舉個例子:

?0? 1? 2? 3? 4? 5? 6? 7? 8? 9?10 11
?a??b? c? a? b? a? b? c? a? b? ?c? ?a
-1? 0? 0? 0

現在要求4下標的next值,我們判斷 4 下標的前一個字符 即 3下標的 a?是否和 它的?最長前綴后綴的長度 即對應的next值 即 0 位置的元素 即 a 是否相等 ,顯然 a 與 a 相等,所以 4 下標的值應為?
0 + 1 = 1
?0? 1? 2? 3? 4? 5? 6? 7? 8? 9?10 11
?a??b? c? a? b? a? b? c? a? b? ?c? ?a
-1? 0? 0? 0? 1? ??

同理,5下標 只需判斷 4 下標 下的字符 是否等于 1 下標的字符,顯然相等,所以 5 下標下的值應該為 1 + 1 = 2。

?0? 1? 2? 3? 4? 5? 6? 7? 8? 9?10 11
?a??b? c? a? b? a? b? c? a? b? ?c? ?a
-1? 0? 0? 0? 1? 2??

那如果比較的字符不相等呢?

?0? 1? 2? 3? 4? 5? 6? 7? 8? 9?10 11
?a??b? c? a? b? a? b? c? a? b? ?c? ?a
-1? 0? 0? 0? 1? 2??
1? 2? 3? 4? ?5? ??
如現在求11下標的next值,我們比較 10?下標的 字符和 5?下標的字符 發現并不相等。
注意!!此時我們繼續 判斷 5?下標下的 next 的值 即 2?對應位置的字符是否和 10?下標下的字符相等,顯然 c?== c?,所以 10?下標下的 next 值即為 2?+ 1 = 3.

以下為證明過程,理解不了直接記下結論即可

解釋:因為我們嘗試在 11?的前面找比 5?更長的最長前后綴,顯然 匹配失敗了,代表找不到,所以我們,繼續向前,找“最長前后綴的前后綴的長度”,即在 abcab中找最長前綴后綴,即 5 下標下的next 值 2,由于 10 下標前的 最長前綴后綴的長度是知道的 ,即 5 ,說明 0 到 4 下標與 5 到 9 下標是匹配的,先找到 0 到 4 下標字串的最長前綴后綴 的長度為 ,2 那說明 0 1 下標 與 2 4 下標匹配,又因為 0 4 下標與 5 9 下標匹配 ,所以 ,0 1 下標 與 8 9 下標匹配,所以我們直接判斷,2 下標與10 下標下的字符是否相等,相等則該下標對應的next 值應為 2 + 1 = 3. 如果不相等 則 繼續 用 2 對應的 next 值往下找直到 為 -1 時做特殊處理

代碼實現:

c語言版:

int* getNext(char* s, int len)
{//申請一塊內存用于返回next數組int* next = (int*)malloc(4 * len);next[0] = -1; //處理特殊值,以便代碼實現next[1] = 0; //第二個字符匹配失敗一定是回到 0 位置開始比較int k = 0; // 代表前一個下標的 next 值,我們從2開始,所以k初始為next[1] = 0int i = 2;while (i < len){//判斷當前位置前一個字符是否等于 k 位置的 字符if (s[i - 1] == s[k]){//相等則當前位置的next值為 k + 1next[i] = k + 1;//讓 i++i++;//讓k的值更新k++;}else{//匹配失敗,讓當前字符與,next[k] 下標下的字符進行比較//我們這里直接更新k的值,在下一次循環比較,注意不需要 i++k = next[k];}}return next;
}

注意如果一直匹配失敗會導致 k 的值 為 -1 導致 上面 數組越界
所以我們在上方的if中做特殊處理

int* getNext(char* s, int len)
{int* next = (int*)malloc(4 * len);next[0] = -1; //處理特殊值,以便代碼實現next[1] = 0; //第二個字符匹配失敗一定是回到 0 位置開始比較int k = 0; // 代表前一個下標的 next 值,我們從2開始,所以k初始為next[1] = 0int i = 2;while (i < len){//判斷當前位置前一個字符是否等于 k 位置的 字符,注意處理 k == -1if (k == -1 || s[i - 1] == s[k]){//相等則當前位置的next值為 k + 1next[i] = k + 1;//讓 i++ 進入下次循環繼續獲取next數組i++;//讓k的值更新k++;}else{//匹配失敗,讓當前字符與,next[k] 下標下的字符進行比較//我們這里直接更新k的值,在下一次循環比較,注意不需要 i++k = next[k];}}return next;
}

我們讓k等于-1的時候也進入if內部,當k等于-1說明,當前i位置的字符前面最長前綴后綴的長度為0,此時我們給 next[i] 賦的值為 -1 + 1 = 0,所以這就是我們為什么要把next[0] 設置為 -1?

現在我們的 kmp 算法以及完成大半了,只需再寫一個 函數用來比較兩個字符串,在匹配失敗的時候用next數組找到 模式串回退的位置,然后繼續比較即可。

代碼實現:

int KMP(char* s1, char* s2, int len1, int len2)
{int* next = getNext(s2, len2);int i = 0;int j = 0;while (i < len1 && j < len2){//注意處理j為-1的情況if (j == -1 || s1[i] == s2[j]) {i++;j++;}else{j = next[j];}}if (j == len2){//j == len2 說明匹配成功了,返回s1中匹配成功時,匹配的第一個字符的位置return i - j;}//匹配失敗返回-1return -1;
}

至此我們的 kmp 算法就完成了,如果覺得對你有幫助的話,請給一個免費的贊,有什么問題也可以在下方討論。

Java代碼:
?

    //求next數組public static int[] getNext(String s) {int n = s.length();int[] next = new int[n];next[0] = -1;//處理特殊值,以便代碼實現int i = 2;//第二個字符匹配失敗一定是回到 0 位置開始比較所以直接從 2 位置開始int k = 0; // 代表前一個下標的 next 值,我們從2開始,所以k初始為next[1] = 0while (i < n) {//判斷當前位置前一個字符是否等于 k 位置的 字符,注意處理 k == -1if(k == -1 || s.charAt(i-1) == s.charAt(k)) {//相等則當前位置的next值為 k + 1next[i] = k + 1;//讓k的值更新k++;//讓 i++ 進入下次循環繼續獲取next數組i++;}else {//匹配失敗,讓當前字符與,next[k] 下標下的字符進行比較//我們這里直接更新k的值,在下一次循環比較,注意不需要 i++k = next[k];}}return next;}public static int KMP(String s1, String s2) {int n = s1.length();int m = s2.length();int i = 0;int j = 0;int[] next = getNext(s2);while(i < n && j < m) {//注意處理 j == -1if(j == -1 || s1.charAt(i) == s2.charAt(j)) {i++;j++;}else{j = next[j];}}if(j == m) {return i - j;}return -1;}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/213394.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/213394.shtml
英文地址,請注明出處:http://en.pswp.cn/news/213394.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Java實現minio

配置Dapplication.yml minio:access-key: minioadminsecret-key: minioadminbucket-name: file #指定桶名稱endpoint: http://localhost:9000 實現代碼minioContriller.java package com.setsail.setsailcusserver.controller;import com.alibaba.fastjson.JSONObject; impo…

萬界星空科技五金家具行業MES解決方案

MES系統如何與家具企業生產相匹配&#xff1f;相較于其它大多數工業軟件&#xff0c;MES系統無疑是受企業歡迎的軟件之一。MES系統處于制造生產企業信息化的核心領域&#xff0c;有著承上啟下的作用。那MES系統如何與家具企業生產相匹配&#xff1f; 五金家具行業的工藝特點&am…

最簡單的pixel刷機和安裝面具、lsposed

一 下載手機對應的系統 1&#xff0c;手機usb連接然后重啟進入Fastboot模式&#xff1a;adb reboot bootloader2&#xff0c;找到你下載的系統&#xff0c;Windows 系統 直接運行 flash-all.bat上圖 &#xff1a;左邊就是安卓11和12的系統&#xff0c;右邊是對應的手機型號 下…

mysql:修改整數字段的顯式長度不生效

例如&#xff0c;我使用mysql 8.2.0版本&#xff0c;想修改整數字段的顯式長度&#xff0c;不會生效&#xff0c;提醒整數的顯示長度已經廢棄&#xff0c;會在將來某個版本去掉&#xff1a; mysql官網中也有說明&#xff1a; https://dev.mysql.com/doc/refman/8.2/en/numeric…

帶阻濾波器:原理、應用及性能分析?|深圳比創達電子EMC

在現代電子技術和通信領域中&#xff0c;濾波器是一種常見的電路元件&#xff0c;用于處理信號&#xff0c;去除不需要的頻率成分或者增強感興趣的頻率成分。本文將重點探討帶阻濾波器&#xff0c;它是一種特殊類型的濾波器&#xff0c;具有在特定頻率范圍內抑制信號的功能。我…

SSD自己也能復制粘貼?淺談NVMe 2.0 Copy Command命令

復制粘貼&#xff08;CtrlC/V&#xff09;作為現代打工人日常辦公的必備生存技能&#xff0c;想必大家都非常熟悉。但你知道嗎&#xff0c;其實SSD自身也能進行這個非常實用的操作。可能有的讀者要說了&#xff1a;這有什么稀奇&#xff0c;復制粘貼這么簡單的功能&#xff0c;…

騰訊字節常考的linux命令

1 ps 1.1 ps -ef 有哪些字段 ps -ef 命令在Unix/Linux系統中用于顯示當前運行的進程。輸出的字段通常包括&#xff1a; UID&#xff1a;啟動進程的用戶ID。PID&#xff1a;進程ID。PPID&#xff1a;父進程ID。C&#xff1a;CPU利用率。STIME&#xff1a;進程啟動時間。TTY&a…

安卓上比iOS快捷指令更強大的工具——MacroDroid

使用 MacroDroid (Android) 自動化您的日常生活——一個簡單的自動化應用程序&#xff0c;用于在 Android 上自動執行任務以及如何在其上自動執行任務。 iOS 和 Android 之間的區別? iOS和Android是兩種不同的移動操作系統&#xff0c;iOS由蘋果公司開發&#xff0c;于2007年…

conda配環境問題合集

&#xff08;CtrlF&#xff0c;請&#xff09; 問題&#xff1a; File "F:\Anaconda3\envs\YOLOv5\lib\ssl.py", line 773, in __init__ raise ValueError("check_hostname requires server_hostname") ValueError: check_hostname requires server_h…

Vue2解決pinia刷新后數據丟失的問題

Pinia&#xff1a;官網 Pinia 是一個 Vue.js 狀態管理庫&#xff0c;如果你在組件中修改了 store 中的數據并刷新了界面&#xff0c;Pinia 會將 store 中的數據重置為初始值&#xff0c;從而導致數據丟失的問題。 這里給出vue2的解決方案&#xff1a; 可以使用 Pinia 的 Per…

當接口要加入新方法時,我后悔沒有早點學設計模式了

&#x1f4e2;?聲明&#xff1a; &#x1f344; 大家好&#xff0c;我是風箏 &#x1f30d; 作者主頁&#xff1a;【古時的風箏CSDN主頁】。 ?? 本文目的為個人學習記錄及知識分享。如果有什么不正確、不嚴謹的地方請及時指正&#xff0c;不勝感激。 直達博主&#xff1a;「…

PP材料粘接ABS材料使用UV膠的好處?

跟隨著現階段材料的不斷發展更迭&#xff0c;PP材料應用越來越廣&#xff0c;生產效率要求越來越高&#xff0c;為了加快生產&#xff0c;提高效率&#xff0c;PP材料的粘接上使用UV膠粘接PP&#xff08;聚丙烯&#xff09;和ABS&#xff08;丙烯腈-丁二烯-苯乙烯共聚物&#x…

python Open3D加載obj

pip安裝Open3D python -m pip install open3d示例代碼 import numpy as np import open3d as o3dpath_obj test/assimp-5.2.5/test/models/OBJ/box.objmesh o3d.io.read_triangle_mesh(path_obj, enable_post_processingTrue)print(np.asarray(mesh.vertices))mesh.compute…

Jenkins:持續集成與持續交付的自動化利器

隨著軟件開發行業的快速發展&#xff0c;持續集成&#xff08;Continuous Integration&#xff0c;簡稱CI&#xff09;和持續交付&#xff08;Continuous Delivery&#xff0c;簡稱CD&#xff09;已經成為了現代軟件開發的重要理念。Jenkins作為一款開源的持續集成和持續交付工…

企業可以利用SD-WAN打破網絡限制,實現高效穩定的應用訪問

在當今數字化時代&#xff0c;我們面臨著越來越多復雜應用和各種類型的數據傳輸。企業需要實時訪問云應用、視頻會議等關鍵應用&#xff0c;不斷增長的訪問流量&#xff0c;導致應用訪問速度變得越來越慢&#xff0c;給工作效率和用戶體驗帶來了很大困擾。 SD-WAN是否能夠解決這…

javaSwing酒店管理

一、介紹 在這篇博客中&#xff0c;我們將介紹一個基于MySQL數據庫、Java編程語言和Swing圖形用戶界面的簡單酒店管理系統。該系統包括了查詢房客信息、查詢房客狀態、修改房客信息、添加房間信息、添加住戶、退房管理、預定管理、退訂管理、入賬管理、出賬管理、修改資料等多…

0009Java程序設計-ssm微信小程序在慢性疾病管理中的應用

文章目錄 **摘要**目錄系統實現開發環境 編程技術交流、源碼分享、模板分享、網課分享 企鵝&#x1f427;裙&#xff1a;776871563 摘要 首先,論文一開始便是清楚的論述了小程序的研究內容。其次,剖析系統需求分析,弄明白“做什么”,分析包括業務分析和業務流程的分析以及用例…

極坐標曲線@典型的4種曲線

文章目錄 abstract典型曲線心形線玫瑰線阿基米德螺線伯努利雙扭線 abstract 除了圓和圓錐曲線外,還有許多曲線用極坐標描述會簡單得多 典型曲線 分析下列曲線時,線分析是否含有三角函數(周期性) 利用描點法做出單個周期內的圖形 作圖:可以打開geogebra https://www.geogebr…

記:vite3+vue3+axios前端項目跨域問題解決【前端和服務器nginx配置】

前言&#xff1a;什么是跨域&#xff0c;網上一搜一大把&#xff0c;所以這里直接跳過&#xff0c;直入主題。 處理方式&#xff1a;不通過后端處理跨域&#xff0c;通過前端服務器nginx處理。 1.前端涉及處理跨域的必要配置&#xff08;開發環境、生產環境&#xff09;&…

銀行插件導致的Outlook客戶端無法連接服務器問題

問題現象 最近遇到好些同事出現outlook客戶端無法連接服務器的情況&#xff0c;具體現象就是右下角一直顯示【正在嘗試連接…】或者【需要密碼】&#xff0c;點擊【需要密碼】按鈕&#xff0c;輸密碼的彈窗是一個完全空白的頁面。 此時打開word&#xff0c;右上角那里去登錄o…