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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

KMP算法详细讲解(看完不会请打我)

發布時間:2025/3/15 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 KMP算法详细讲解(看完不会请打我) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 前言
  • 一:情景導入-如何快速在一個主串找到目標字符串
  • 二:詳解KMP
    • (1)暴力匹配的缺點
    • (2)最長相同前綴和后綴
    • (3)究竟怎么回溯
    • (3)next數組
    • (4)求解next數組
      • A:next[0]=-1
      • B:next[j]=k
      • C:k=next[k]
    • (5)KMP算法代碼

前言

KMP算法可以說是知名度非常高的算法了,不管是數據結構課本還是各大論壇都對這個算法進行過深入講解,但遺憾的是,還是把大部分人給勸退了。

一:情景導入-如何快速在一個主串找到目標字符串

如下有兩個字符串,問你:如何快速在主串中找到目標串

當然我們說的是算法,請你不要回答“肉眼搜索之類的話題”(當然如果這個主串有一億個字符我相信你這輩子也不可能找出來)

回歸正題,相信大家想到的第一個解決方法就是暴力破解——用兩個指針,開始時分別指向兩個串的開頭,然后比較,如果相同繼續掃描下一個字符,一旦出現不相同,兩個指針進行回溯,主串的指針回溯到下一位,目標串的指針晦朔至第一位,然后繼續按照上述步驟比較

直接用圖片描述如下,需要注意的是,回溯的步驟在圖中表示為了目標串向后移動的,但是實際物理內存中字符串是不可能移動的,這里只是為了形象解釋

由此,你可以寫出對應的暴力匹配代碼

int index(string main_str,string aim_str) {int i=0;int j=0;//i和j分別用來掃描主串和目標串while(i<=main_str.size()-1 && j<=aim_str.size()-1){if(main_str[i]==main_str[j]){++i;++j;}else{j=1;i=++k;}}if(j>aim_str.size()-1)return k;elsereturn 0; }

暴力匹配是可行的,但是時間復雜度一看就很高,所以KMP算法正是為了解決如何快速在主串中找到目標串而生的,而且它真的很快。

二:詳解KMP

為了便于介紹,我將主串和目標串設置為這樣(當然,下面的例子中主串中是沒有目標串的,但是不要緊,我只是為了說明更好的說明,有沒有都一樣)

(1)暴力匹配的缺點

通過上面的那么長的一張圖,,大家可能已經發現了暴力匹配的缺陷所在——對,就是回溯太頻繁了,只要找不到就回溯到下一位

這里如果讓你進行回溯的話,你肯定不會呆呆的直接回溯的下一位,起碼得下面這樣吧

為什么?因為主串只有第一個位置是A,而這種匹配的嘗試已經失敗了,但是它第2,3位都不是A,既然都是A,那我還干嘛回溯到這里?直接從第4位開始不好嗎?

所以KMP的算法的核心點就在這里:不要回溯到無效的地方,讓其回溯到有效的位置

(2)最長相同前綴和后綴

在講下面前,必須引入一個概念——一個字符串的最長相同前綴和后綴。
關于這個問題,其實很多數據結構的書解釋的都太過復雜了,甚至總是愛拿出一套公式把你勸退

要想知道什么是最長相同前綴和后綴,首先得明白什么是字符串的前綴和后綴,看完下面這個圖相信你就不難理解了

那么,最長相同前綴和后綴你也應該能找出來了吧

什么?竟然不是最下面的那個嗎?這里就要給大家說明一點,最長相同前后綴不能是字符串本身,因為如果你認為最長相同前后綴可以是字符串本身的話,那么這就是一個恒成立的命題了,那么KMP算法還玩什么?

(3)究竟怎么回溯

前面講過,KMP算法的核心問題就是處理如何有效的回溯的問題。
再把之前那個應該這樣回溯的圖拿來,注意觀察

你有沒有發現它回溯的位置有些特點?沒錯!就是最長公共前后綴重合的地方,也就是說讓最長相同前綴對齊至后綴

下面的話一定要認真理解,這是你是否能理解KMP算法以及明白為什么要有next數組的關鍵所在。首先請問目標串發生不匹配時的索引是多少?,是5對吧,然后咋們回溯的時候請問目標串的哪個位置(或者說索引)與主串的不匹配位置(也就是5)“對齊了”?很明顯是目標串的2號位置,你有沒有發現——這個2恰好就是目標串發生不匹配位置前的字符串的最長前綴和后綴的長度!

好的如何你能理解上面的部分,那么KMP算法的思想算是已經明白了。

(3)next數組

進過上面的敘述,我們可以意識到,這種回溯是有規律的,并不是憑空臆想的。
再舉幾個例子
如下目標串的索引為3號的位置發生不匹配

其3號位置前的字符串的最長相同前后綴的長度是1,所以就應該讓目標串索引為1的位置與該不匹配處“對齊”

再來,如下目標串的索引為5號的位置發生不匹配

其5號位置前的字符串的最長相同前后綴的長度是2,所以就應該讓目標串索引為2的位置與該不匹配處“對齊”

我講到這里,大家應該可以明白了,也就是匹配時根本就“不需要”主串,因為每個位置發生不匹配時,總有一個值能確定其應該回溯的位置。這個值就是其前面的字符串的最長相同前后綴的長度

我們把目標串每個位置發生不匹配時,目標串應該回溯的位置給記錄下來,形成一個數組,這個數組就是大名鼎鼎的next數組,next[i]=j,就表示索引為i的位置發生不匹配,就讓其回溯的j的位置繼續匹配

next數組的計算方法我其實前面已經說到過了,如下目標的串next數組計算如下,需要注意是由于第一個位置也就是next[0]前面沒有串,所以記為-1

ABCABCMN
next[0]next[1]next[2]next[3]next[4]next[5]next[6]next[7]
-10001230

好,依據next數組我們就能確定目標串的回溯位置了,比如下面假設目標串和主串在索引為4的位置發生不匹配,所做的操作如下

(4)求解next數組

KMP算法的思想大部分人是能很輕松的明白的,但是這個算法為什么勸退了很多人呢?就是無法準確,深刻的理解求解next數組的代碼

void getnext(string target_str,int next[]) {int j=0,k=-1;next[0]=-1;while(j<target_str.size()){if(k==-1 || str[j]==str[k]){j++;k++;next[j]=k;}else{k=next[k];}} }

你說它長也就算了,畢竟是一個具有難度的算法,但是它偏偏這么短,這就讓人很尷尬。

上述代碼中總共有三個地方如果能理解清楚這個代碼也就沒問題了。
next[0]=-1,next[j]=l,k=next[k],其中k=next[k]最讓人摸不著頭腦

A:next[0]=-1

首先是next[0]=-1,如下當目標串的第一個位置,也就是索引為0的位置,發生不匹配時,應該讓目標串的索引為-1的位置與當前不匹配的地方對齊比較

好的,我知道你肯定回文,怎么可能有-1的位置啊,這明顯越界了啊?你先不要管這些,我就問你,這種情況下,是不是應該是目標串的第一個位置與主串的下一個位置進行比較? 是的,的確是這樣,所以我這里先截取,KMP算法的一小段(不要擔心你肯定看得懂)

上面的j=next[j]不用不說在干什么了,好的回到上面的問題,此時就是執行結果就是-1=next[0],也就是j=-1了,然后重新回到循環,繼續判斷,此時j=-1,進入if內,注意i++,j++,請問i++是在干什么?,而j++會等于0,這不就是讓目標串的第一位與主串的下一位比較嗎

所以你現在能理解為什么next[0]=-1了吧

B:next[j]=k

首先next[j]=k我認為大家肯定能明白的一點就是在為next數組進行位置,但是不明白的是為什么下標為j的元素的值是k

那么我想問哪種情況下會有next[j]=k?,根據上面的代碼自然是k=-1或str[j]=str[k]的時候。

好的現在,我們先考慮k=-1,當k=-1時,總會有k++,使得k變為0,這樣的話next[j]=0,表明前面沒有相同的前后綴

那么一旦k不是-1,滿足這個if的唯一條件就是str[j]=str[k]了。如果此刻str[j]=str[k],這就表示此時的位置可以算作相同的前后綴了,我們所做的就是判斷下一對了。
比如下面,可以看出,此時k來到了C的位置,j也來到了C的位置,他們之前都各自在B的位置,由于相同,所以來到了現在位置,現在他們要判斷此刻對應的位置是否可以也將其算入相同的前后綴

非常幸運,他們相同,于是j++,k++,next[j]=3

C:k=next[k]

我認為kmp算法的高潮部分就在這里!(為了方便說明,我換了一張圖)
如下,j和k準備比較下一個

很遺憾這個位置,無法使其成為長度為4的相同前后綴,那么根據代碼只能執行k=next[k]了
這樣畫比較難受,我把前綴直接拿到下面來

有沒有似曾相識的感覺?沒錯,好像是主串和目標串,但是這不是原來的那個兩個串。而是后綴作為了主串,前綴作為了目標串,好的,現在前綴(目標串)的下表為3的位置發生了不匹配,該怎么做?這不用問我吧,當然是讓前綴(目標串)下標為1的位置(next[3]=1)與該不匹配處重新比較

好的,重新組裝回去,畢竟這不是真的主串和目標串

好的,現在if的判斷是否滿足,當然滿足,對應位置相同,那么繼續i++,j++,于是next[9]=2,此時next數組全部賦值完成。

這就是為什么這樣回溯的原因!
仔細想一想,下面的這種情況中已經不可能有ABAB,ABAC相等的情況存在了,因為一旦相同,那么next[9]=4了,也即是C之前不可能有長度為4的相同的前后綴,但是沒有這么長的或許也有短的呀,你看咋們上面的那個AB=AB不就是一個短的情況嗎,所以這也就是為什么有k=next[k]這樣的回溯的情況。其實就是在前綴和后綴在比較,只不過一旦遇到不相等,前人栽樹后人乘涼罷了!

如果你能理解最后的那句諺語,那么這個算法你應該掌握的八九不離十了,這里也可以看出這個算法有多么的牛逼,簡直即使巧妙之際,不得不佩服發明KMP算法的這三位大牛的聰明才智啊

(5)KMP算法代碼

我覺得不用我再解釋了吧

int KMP(string main_str,string target_str) {int next[MaxSize],i=0,j=0;getnext(target_str,next);while(i<main_str.size() && j<target_str.size()){if(j==-1 || main_str[i]==target_str[j]){i++;j++;}else{j=next[j];}}if(j>=target_str.size())return i-target_str.size();//返回目標串在主串的第一個字符的位置elsereturn -1;//找不到返回-1}

總結

以上是生活随笔為你收集整理的KMP算法详细讲解(看完不会请打我)的全部內容,希望文章能夠幫你解決所遇到的問題。

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