主頁 > 軟體設計 > kmp演算法詳解

kmp演算法詳解

2021-09-01 14:59:41 軟體設計

文章目錄

  • 前言
  • 例題引入
  • 簡單演算法BF
  • 經典演算法KMP
  • kmp理解難點1
  • kmp理解難點2
  • kmp最難理解點3
  • kmp代碼

前言

對于kmp的鼎鼎大名,不只是博主自己,想必還有更多小伙子們聽說過,也相信都去了解過,博主亦是這樣,但是真正去理解這個程序,確是例外的折磨人,在kmp演算法里面搗鼓了整整3天,博主終于找到了用更好理解的話語進行解釋了,便迫不及待的進行分享.


例題引入


假如有一個文本串T,內容為cadefgdefghp,有一個模式串P,其內容為defgh,請問P是否在T內?如果在,請回傳P在T中的索引位置,如果不在,請回傳-1.


簡單演算法BF

對于此題,我想大部分人都有一個簡單思路,那就是T和P一一匹配,當匹配一個字符后,就挨個匹配后面;如果在中間部分不匹配,那么T講回到最開始匹配字符的下一個字符處,P回到索引為0處.如下圖:

影片

而這種演算法我們稱為樸素演算法(BF),代碼博主會貼在下面:

int BF(char T[], char P[])
{
    int i = 0, j = 0;
    while (i < strlen(T) && j < strlen(P))
    {
        if (T[i] == P[j])  //如果相等,就繼續從這往后匹配
        {
            i++, j++;
        }
        else
        {
            i = i - j + 1;     //如果不等,i就回傳最開始匹配位置下一個位置(見上圖)
            j = 0;             //p回傳到索引為0重新開始
        }
    }
    if (j >= strlen(P))   //當匹配成功時,j一定移動到P字串末尾,所以j必須有此條件
        return i - j;
    else 
        return -1;
}

經典演算法KMP

為什么有KMP,他的目的就是解決我們重復匹配的問題,比如下圖:

image-20210823155233574

我們發現g和e不匹配了,按照BF演算法,文本串(上方字串)位置會回到b位置,模式串(下方)會重新回到索引為0處然后繼續比對.

image-20210823155647071

而如果我們按照人的思維會怎樣繼續對比呢?沒錯,我們并不會像BF那樣笨,如果是人,會直接讓文本串不動,直接讓模式串開始重新匹配:

image-20210823160011513

也就是文本串此時索引不動,而模式串索引回到了0,并從此處開始繼續匹配.記住這句話!!!


我們再舉一個例子,假設在文本串efabcabpabcabe中匹配abcabe,如下圖:

image-20210823160634394

我們先是一個一個的匹配,但是當匹配到p和e時,發現不匹配了,按照BF演算法,文本串會回到索引為3位置,模式串會回到索引為0位置.

image-20210823161300023

但是如果是人呢?會怎樣做?我們會讓模式串索引0位置和文本串索引5位置對齊,因為對齊后都有ab,所以模式串就從索引為2位置繼續進行匹配.

image-20210823161424049

也就是文本串此時不動,而模式串索引回到2,并從此開始繼續匹配.記住這句話!!!


我們最后再舉一個例子:假設有文本串efabcdabcabcabe,模式串abcdabc,然后開始正常匹配

image-20210823161704440

在這里,我們發生了失配,如果按照BF演算法,我們會讓文本串回到索引為3位置處,而模式串回到索引0處:

image-20210823162122640

但是如果是人類呢? 會怎樣做? 沒錯,我們會讓模式串與文本串的索引6位置處對齊,而文本串不動.然后發現對齊以后,文本串和模式串都有abc,所以我們不管abc,而是讓模式串從索引為3處開始和文本串對比:

image-20210823162441496

也就是文本串此時不動,而模式串索引回到3,并從此開始繼續匹配.記住這句話!!!


大家分別縱觀上面三個例子,我們都做了什么相同操作?

  • 當失配時:
    • 文本串位置不動
    • 模式串的索引直接從某個位置開始繼續和文本串適配位置進行匹配

我們再來看看規律,

第一個例子: 文本串此時不動,而模式串索引回到0,并從此開始繼續匹配.

  • 原因:模式串失配位置前面有0個重復的元素

第二個例子:文本串此時不動,而模式串索引回到2,并從此開始繼續匹配.

  • 原因: 模式串失配位置前面有2個重復的元素,即ab

第三個例子:文本串此時不動,而模式串索引回到3,并從此開始繼續匹配.

  • 原因: 模式串失配位置前面有3個重復的元素,即abc

也就是說!!!,當在中途匹配程序中發生失敗,我可以不再讓文本串回走很長一段舉例,而是讓模式串進行繼續匹配,至于模式串下一步應該會到哪個位置,取決于匹配失敗字符前的相同前后綴字符(后面會介紹前后綴)長度


我們再試試一個例子,看看是這個規律嗎?比如文本串efacadmp,模式串acabef

image-20210823165420095

此時失配了,模式串失配字符b前面有1個重復的元素a,所以模式串直接從索引為1開始進行繼續匹配,看看是否對:

image-20210823165604273

完全沒問題,因為在模式索引0前有一個字符a,而文本串對應位置也有一個字符a,所以模式串直接從索引為1開始匹配

也就是說一旦發生失配,人類執行時,會按照如下步驟:

  • 不動文本串
  • 模式串位置直接回到索引為k處(k是失配位置前面所有字串中最大重復元素數量)

而這些步驟用代碼寫出來就是kmp

即kmp演算法不像BF演算法那樣,避免了文本串的索引回傳,而是直接定位模式串下一個匹配位置.


kmp理解難點1

我們已經清楚了kmp的演算法步驟,而難點1就在于求k

而k就是 字串的最大相同前后綴長度

**前后綴概念: **

  • 前綴: 字串中除了最后一個字符外的所有順序集合

    • 比如有字串abcdef,那么他的前綴有a,ab,abc,abcd,abcde
  • 后綴:字串中除了第一個字符外的所有順序集合

    • 比如有字串abcdef,那么他的后綴有f,ef,def,cdef,bcdef
  • 最大相同前后綴: 前綴集合和后綴集合的交集中最大長度者

    • 比如有字串ababab,他的前后綴交集有ab,abab,所以最長相同前后綴就是abab

kmp理解難點2

我們知道,當模式串與主串不匹配時,而主串不動,模式串的索引跳到k處,k值是b當前不匹配字符前的所有字符中最大相同前后綴長度

所以我們為了方便處理,便用一個陣列進行儲存一段字串的最大前后綴最長度

假設有字串ababaaaba,則:

字串ababaaaba
索引012345678
k值001231121

解釋:

  • 在索引為3下,k值是2,代表著[0,3]中的字串中相同前后綴的最大長度為2

  • 在索引為6下,k值是1,代表著[0,6]中的字串中相同前后綴的最大長度為1

  • 在索引為0下,k值0,代表著[0,0]中的字串,即一個字符是沒有前后綴的,最大長度為0


我們給儲存k值的陣列起名叫做next.這也就是我們kmp演算法中的精華所在


kmp最難理解點3

博主在學習kmp演算法中時候,發現很多文章與視頻都是只給了手動算next陣列方法,對于代碼部分便是一筆跳過,而這才是KMP中最最最難理解的一部分代碼,下面博主用自己的理解給大家分享下自己的拙見

我們還是像介紹kmp演算法一樣,我們還是先用自己的第一思路來處理,然后寫代碼,最后進階精華kmp陣列求法:

  • 我們的第一思路是什么? 沒錯,那就是前綴和后綴一對一對的比較,比如有下面一段字串str:

abbabba

我們用變數i進行指示比的是第幾對,從1開始.

我們用變數count進行計數,當出現一對相同前后綴,則count = i,最后count值就是最大長度

  • 當i = 1時候,第一對前綴是"a",后綴是"a",后綴的位置是strlen(str)-1,前后綴相同,count等于1
  • 當i = 2時候,第一對前綴是"ab",后綴是"ba",后綴的位置是strlen(str)-2,前后綴不同,count不管
  • 當i = 4時候,第一對前綴是"abba",后綴是"abba",后綴的位置是strlen(str)-4,前后綴相同,count等于4

所以大概代碼如下:

int count = 0;
for(int i = 1;i < strlen(str);i++)               //i不能等于strlen(str),因為前后綴分別不包含尾字符和首字符
{
    if(strncmp(str,str+strlen(str)-i,i) == 0)    //如果第幾對前后綴相同,則count等于幾
    {                                            //str+strlen(str)-i 是指標加減整數的意義
        count = i;
    }
}

而我們是需要求一個陣列,所以我們就把字串拆成更多個小字串,然后又分別求每個小字串的最大前后綴長度,思路和上面一樣

void next(char str[],int next[])
{
    int i = 1,j = 1,count = 0;
    //i代表第幾對,j代表索引[0,j]的字串,count代表最大長度   
    
    next[0] = 0; //首字符一定為0,因為之后一個字符,沒有前后綴
    
    for(j = 1;j<strlen(str);j++)
    {
        count = 0;
        for(int i = 1;i < strlen(str);i++)               //i不能等于strlen(str),因為前后綴分別不包含尾字符和首字符
        {
            if(strncmp(str,str+strlen(str)-i,i) == 0)    //如果第幾對前后綴相同,則count等于幾
            {                                            //str+strlen(str)-i 是指標加減整數的意義
                count = i;
            }
        }
        next[j] = count;
    }
}

我們已經成功的用自己的方法求出來了next陣列,但是大家有沒有發現這樣求解有一個很大的缺陷?

  • 那就是我們每次在求解[0,j]中的字串最大相同前后綴時,我們都是從第一對的一個字符,到第j對的j個字符進行對比.

  • 而在求解[0,j+1]中的字串最大相同前后綴時,我們又要從第一對的第一個字符,到第j+1對的j+1個字符進行對比.

在比較的程序中我們比較了很多不相同前后綴的字串,那么我們想一想,可不可以換一種思路,不再像上面那樣每次都要從一個字符開始進行匹配,避免多次匹配不相同前后綴字串? 答案是完全可以!!!

下面,請大家一定記住這句話,不要覺得這些話很簡單,后面正是這些話才能明白某些代碼:

相同前后綴 == 相同前綴 == 相同后綴 !!!— --- — --- — --- — --- — --- — --- — --- — --- — --- — --- — --- ---- — --- — ---標號①

如果一段字串存在相同前后綴,那么相同前綴一定可以在后綴(注意,沒有相同兩字)中找到,并且相同前綴一定是最長后綴末尾,相同前綴的長度一定小于等于最長后綴長度— --- — --- — --- — --- — --- — --- — --- — --- — --- — --- — --- —標號②

舉個例子:

有一段字串ababa.

  • 相同前后綴有aaba,而后綴有a,ba,aba,baba,這就是這句話— --- —相同前綴一定可以在后綴中找到的意思
  • 該段字串最長后綴為baba,相同前綴為a,是最長后綴的末尾,相同前綴為aba,是最長后綴的末尾.
    • 所以說— --- — 相同前綴一定是最長后綴末尾
  • 相同前綴的長度分別是1和3都小于最長后綴的長度4

既然我們目的是為了減少不必要的不同前后綴字串比對,那么我們的方法是什么呢?

答曰: 給最長相同前綴新增一個字符,然后判斷是否在最長后綴中.(請看標號①陳述句)

  • 如果不在,我們就判斷原最長相同前綴是否在最長后綴中…

  • 如果在,說明此時的字串中最長相同前后綴長度是原最長相同前后綴長度加1(步驟重復上面)

我們現在要開始清楚一些概念了:

  1. 我們用j表示一段字串([0,j]的字串)最長后綴的尾巴的索引,j的初始值設為1

  2. 用k表示字串中最長相同前綴的長度(這句話本身還表達了另一個意思,是最長相同前綴尾巴后一個索引,這個很重要),k初始為0

    • 比如有字串ababa,假設此時k等于3,說明此時最長相同前后值長度是3,而索引為3字符卻是最長相同前綴尾末,
  3. next陣列存盤的是最長相同前后綴字符長度,則next[j]表示索引在[0,j]的字串中最長相同前后綴的長度

所以他們一定有下圖關系:

image-20210824102151162


現在大家再看看上面我們說的新的比較方法的步驟,然后下面的代碼結合上面這張圖分析


(下面這些話大家一定要靜下心來想,博主會貼圖再詳細解釋)

如果不存在,怎么判斷?

  • 答曰:

  • while(k>0 && p[k] != p[j])  //k為什么大于0,后面會解釋
    {
        k = next[k-1];         //說明給原最長相同前綴加一個字符后,判斷出不等,即[0,k]的字串不在后綴中
    }
    
    其實k = next[k-1]大家可能還是不理解,但是大家想想我們的目的,避免比較不相同前后綴,而next陣列裝的就只有相同前后綴長度.
    既然[0,k]字串的最長前綴不在后綴中,我們就需要比較[0,k-1]中的最長前綴是否在后綴.
        而next[k-1]的意義不就是[0,k-1]中最長相同前后綴字串長度嗎?
    

給最長相同前綴新增一個字符,如何判斷在最長后綴中?

  • **答曰: **

  • if(p[k] == p[j])    //因為p[0,k-1]等于p[j-k,j-1],而p[0,k-1]就是目前為止最長相同前綴,所以只需要判斷p[k]和p[j]了
    {
        next[j] = k+1; //如果相等,[0,j]中的最長相同前后綴長度等于原最長前后綴長度加一.
        k++;           //更新現在的最長相同前后綴長度
    }
    else               //else就是處理當k等于0,且p[k]不等于p[j]時候,說明next[j]應該為0
    {
        next[j] = 0;
    }
    

所以最后求next陣列的代碼就是

void get_next(char p[100],int next[100]) 
{

    next[0] = 0;
    int j = 1;
    int k = 0;
    //如果有效前綴增加一個值和新增后綴不等,
    for(j = 1; j < strlen(p); j++)
    {
        while (k>0 && p[k] != p[j])
        {
            k = next[k - 1];   
        }
        if (p[k] == p[j])
        {
            next[j] = ++k;
        }
        else   //這一步是索引為1時候,k等于0,且不等的條件
        {
            next[j] = 0;
        }
    }
}

kmp代碼

int kmp(char s[100],char p[100])
{
    int i = 0, j = 0;
    int next[100];
    get_next(p,next);
    while (i < strlen(s) && j < strlen(p))
    {
        if (s[i] == p[j])
        {
            i++, j++;
        }
        else if (j > 0)
        {
            j = next[j - 1];
        }
        else
        {
            i++;
        }
    }

    if (j >= strlen(p)) return i - j;
    else
        return -1;
}

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/296653.html

標籤:其他

上一篇:qsort庫函式排序

下一篇:通俗易懂,值得收藏的 java 設計模式實戰,裝飾者模式 之 你不用改變,就讓你的能力變強了

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more