欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

KMP算法最淺顯理解(小白教程)

 更新時(shí)間:2019年11月29日 10:52:42   作者:路漫遠(yuǎn)吾求索  
這篇文章主要介紹了KMP算法最淺顯理解(小白教程),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

說(shuō)明

KMP算法看懂了覺(jué)得特別簡(jiǎn)單,思路很簡(jiǎn)單,看不懂之前,查各種資料,看的稀里糊涂,即使網(wǎng)上最簡(jiǎn)單的解釋?zhuān)廊豢吹南±锖俊?
我花了半天時(shí)間,爭(zhēng)取用最短的篇幅大致搞明白這玩意到底是啥。
這里不扯概念,只講算法過(guò)程和代碼理解:

KMP算法求解什么類(lèi)型問(wèn)題

字符串匹配。給你兩個(gè)字符串,尋找其中一個(gè)字符串是否包含另一個(gè)字符串,如果包含,返回包含的起始位置。
如下面兩個(gè)字符串:

char *str = "bacbababadababacambabacaddababacasdsd";
char *ptr = "ababaca";

str有兩處包含ptr
分別在str的下標(biāo)10,26處包含ptr。

“bacbababadababacambabacaddababacasdsd”;\

這里寫(xiě)圖片描述

問(wèn)題類(lèi)型很簡(jiǎn)單,下面直接介紹算法

算法說(shuō)明

一般匹配字符串時(shí),我們從目標(biāo)字符串str(假設(shè)長(zhǎng)度為n)的第一個(gè)下標(biāo)選取和ptr長(zhǎng)度(長(zhǎng)度為m)一樣的子字符串進(jìn)行比較,如果一樣,就返回開(kāi)始處的下標(biāo)值,不一樣,選取str下一個(gè)下標(biāo),同樣選取長(zhǎng)度為n的字符串進(jìn)行比較,直到str的末尾(實(shí)際比較時(shí),下標(biāo)移動(dòng)到n-m)。這樣的時(shí)間復(fù)雜度是O(n*m)

KMP算法:可以實(shí)現(xiàn)復(fù)雜度為O(m+n)

為何簡(jiǎn)化了時(shí)間復(fù)雜度:
充分利用了目標(biāo)字符串ptr的性質(zhì)(比如里面部分字符串的重復(fù)性,即使不存在重復(fù)字段,在比較時(shí),實(shí)現(xiàn)最大的移動(dòng)量)。
上面理不理解無(wú)所謂,我說(shuō)的其實(shí)也沒(méi)有深刻剖析里面的內(nèi)部原因。

考察目標(biāo)字符串ptr
ababaca
這里我們要計(jì)算一個(gè)長(zhǎng)度為m的轉(zhuǎn)移函數(shù)next。

next數(shù)組的含義就是一個(gè)固定字符串的最長(zhǎng)前綴和最長(zhǎng)后綴相同的長(zhǎng)度。

比如:abcjkdabc,那么這個(gè)數(shù)組的最長(zhǎng)前綴和最長(zhǎng)后綴相同必然是abc。
cbcbc,最長(zhǎng)前綴和最長(zhǎng)后綴相同是cbc。
abcbc,最長(zhǎng)前綴和最長(zhǎng)后綴相同是不存在的。

**注意最長(zhǎng)前綴:是說(shuō)以第一個(gè)字符開(kāi)始,但是不包含最后一個(gè)字符。
比如aaaa相同的最長(zhǎng)前綴和最長(zhǎng)后綴是aaa。**
對(duì)于目標(biāo)字符串ptr,ababaca,長(zhǎng)度是7,所以next[0],next[1],next[2],next[3],next[4],next[5],next[6]分別計(jì)算的是
a,ab,aba,abab,ababa,ababac,ababaca的相同的最長(zhǎng)前綴和最長(zhǎng)后綴的長(zhǎng)度。由于a,ab,aba,abab,ababa,ababac,ababaca的相同的最長(zhǎng)前綴和最長(zhǎng)后綴是“”,“”,“a”,“ab”,“aba”,“”,“a”,所以next數(shù)組的值是[-1,-1,0,1,2,-1,0],這里-1表示不存在,0表示存在長(zhǎng)度為1,2表示存在長(zhǎng)度為3。這是為了和代碼相對(duì)應(yīng)。

下圖中的1,2,3,4是一樣的。1-2之間的和3-4之間的也是一樣的,我們發(fā)現(xiàn)A和B不一樣;之前的算法是我把下面的字符串往前移動(dòng)一個(gè)距離,重新從頭開(kāi)始比較,那必然存在很多重復(fù)的比較?,F(xiàn)在的做法是,我把下面的字符串往前移動(dòng),使3和2對(duì)其,直接比較C和A是否一樣。

這里寫(xiě)圖片描述

這里寫(xiě)圖片描述

代碼解析

void cal_next(char *str, int *next, int len)
{
  next[0] = -1;//next[0]初始化為-1,-1表示不存在相同的最大前綴和最大后綴
  int k = -1;//k初始化為-1
  for (int q = 1; q <= len-1; q++)
  {
    while (k > -1 && str[k + 1] != str[q])//如果下一個(gè)不同,那么k就變成next[k],注意next[k]是小于k的,無(wú)論k取任何值。
    {
      k = next[k];//往前回溯
    }
    if (str[k + 1] == str[q])//如果相同,k++
    {
      k = k + 1;
    }
    next[q] = k;//這個(gè)是把算的k的值(就是相同的最大前綴和最大后綴長(zhǎng))賦給next[q]
  }
}

KMP

這個(gè)和next很像,具體就看代碼,其實(shí)上面已經(jīng)大概說(shuō)完了整個(gè)匹配過(guò)程。

int KMP(char *str, int slen, char *ptr, int plen)
{
  int *next = new int[plen];
  cal_next(ptr, next, plen);//計(jì)算next數(shù)組
  int k = -1;
  for (int i = 0; i < slen; i++)
  {
    while (k >-1&& ptr[k + 1] != str[i])//ptr和str不匹配,且k>-1(表示ptr和str有部分匹配)
      k = next[k];//往前回溯
    if (ptr[k + 1] == str[i])
      k = k + 1;
    if (k == plen-1)//說(shuō)明k移動(dòng)到ptr的最末端
    {
      //cout << "在位置" << i-plen+1<< endl;
      //k = -1;//重新初始化,尋找下一個(gè)
      //i = i - plen + 1;//i定位到該位置,外層for循環(huán)i++可以繼續(xù)找下一個(gè)(這里默認(rèn)存在兩個(gè)匹配字符串可以部分重疊),感謝評(píng)論中同學(xué)指出錯(cuò)誤。
      return i-plen+1;//返回相應(yīng)的位置
    }
  }
  return -1; 
}

測(cè)試

  char *str = "bacbababadababacambabacaddababacasdsd";
  char *ptr = "ababaca";
  int a = KMP(str, 36, ptr, 7);
  return 0;

注意如果str里有多個(gè)匹配ptr的字符串,要想求出所有的滿足要求的下標(biāo)位置,在KMP算法需要稍微修改一下。見(jiàn)上面注釋掉的代碼。

復(fù)雜度分析

next函數(shù)計(jì)算復(fù)雜度是(m),開(kāi)始以為是O(m^2),后來(lái)仔細(xì)想了想,cal__next里的while循環(huán),以及外層for循環(huán),利用均攤思想,其實(shí)是O(m),這個(gè)以后想好了再寫(xiě)上。

………………………………………..分割線……………………………………..
其實(shí)本文已經(jīng)結(jié)束,后面的只是針對(duì)評(píng)論里的疑問(wèn),我嘗試著進(jìn)行解答的。

進(jìn)一步說(shuō)明(2018-3-14)

看了評(píng)論,大家對(duì)cal_next(..)函數(shù)和KMP()函數(shù)里的

while (k > -1 && str[k + 1] != str[q])
    {
      k = next[k];
    }

while (k >-1&& ptr[k + 1] != str[i])
      k = next[k];

這個(gè)while循環(huán)和k=next[k]很疑惑!
確實(shí)啊,我開(kāi)始看這幾行代碼,相當(dāng)懵逼,這寫(xiě)的啥啊,為啥這樣寫(xiě);后來(lái)上機(jī)跑了一下,慢慢了解到為何這樣寫(xiě)了。這幾行代碼,可謂是對(duì)KMP算法本質(zhì)得了解非常清楚才能想到的。很牛逼!
直接看cal_next(..)函數(shù):
首先我們看第一個(gè)while循環(huán),它到底干了什么。

在此之前,我們先回到原程序。原程序里有一個(gè)大的for()循環(huán),那這個(gè)for()循環(huán)是干嘛的?

這個(gè)for循環(huán)就是計(jì)算next[0],next[1],…next[q]…的值。

里面最后一句next[q]=k就是說(shuō)明每次循環(huán)結(jié)束,我們已經(jīng)計(jì)算了ptr的前(q+1)個(gè)字母組成的子串的“相同的最長(zhǎng)前綴和最長(zhǎng)后綴的長(zhǎng)度”。(這句話前面已經(jīng)解釋了?。?這個(gè)“長(zhǎng)度”就是k。

好,到此為止,假設(shè)循環(huán)進(jìn)行到 第 q 次,即已經(jīng)計(jì)算了next[q],我們是怎么計(jì)算next[q+1]呢?

比如我們已經(jīng)知道ababab,q=4時(shí),next[4]=2(k=2,表示該字符串的前5個(gè)字母組成的子串ababa存在相同的最長(zhǎng)前綴和最長(zhǎng)后綴的長(zhǎng)度是3,所以k=2,next[4]=2。這個(gè)結(jié)果可以理解成我們自己觀察算的,也可以理解成程序自己算的,這不是重點(diǎn),重點(diǎn)是程序根據(jù)目前的結(jié)果怎么算next[5]的).,那么對(duì)于字符串ababab,我們計(jì)算next[5]的時(shí)候,此時(shí)q=5, k=2(上一步循環(huán)結(jié)束后的結(jié)果)。那么我們需要比較的是str[k+1]和str[q]是否相等,其實(shí)就是str[1]和str[5]是否相等!,為啥從k+1比較呢,因?yàn)樯弦淮窝h(huán)中,我們已經(jīng)保證了str[k]和str[q](注意這個(gè)q是上次循環(huán)的q)是相等的(這句話自己想想,很容易理解),所以到本次循環(huán),我們直接比較str[k+1]和str[q]是否相等(這個(gè)q是本次循環(huán)的q)。
如果相等,那么跳出while(),進(jìn)入if(),k=k+1,接著next[q]=k。即對(duì)于ababab,我們會(huì)得出next[5]=3。 這是程序自己算的,和我們觀察的是一樣的。
如果不等,我們可以用”ababac“描述這種情況。 不等,進(jìn)入while()里面,進(jìn)行k=next[k],這句話是說(shuō),在str[k + 1] != str[q]的情況下,我們往前找一個(gè)k,使str[k + 1]==str[q],是往前一個(gè)一個(gè)找呢,還是有更快的找法呢? (一個(gè)一個(gè)找必然可以,即你把 k = next[k] 換成k- -也是完全能運(yùn)行的(更正:這句話不對(duì)啊,把k=next[k]換成k–是不行的,評(píng)論25樓舉了個(gè)反例)。但是程序給出了一種更快的找法,那就是 k = next[k]。 程序的意思是說(shuō),一旦str[k + 1] != str[q],即在后綴里面找不到時(shí),我是可以直接跳過(guò)中間一段,跑到前綴里面找,next[k]就是相同的最長(zhǎng)前綴和最長(zhǎng)后綴的長(zhǎng)度。所以,k=next[k]就變成,k=next[2],即k=0。此時(shí)再比較str[0+1]和str[5]是否相等,不等,則k=next[0]=-1。跳出循環(huán)。
(這個(gè)解釋能懂不?)

以上就是這個(gè)cal_next()函數(shù)里的

while (k > -1 && str[k + 1] != str[q])
    {
      k = next[k];
    }

最難理解的地方的一個(gè)我的理解,有不對(duì)的歡迎指出。

復(fù)雜度分析:

分析KMP復(fù)雜度,那就直接看KMP函數(shù)。

int KMP(char *str, int slen, char *ptr, int plen)
{
  int *next = new int[plen];
  cal_next(ptr, next, plen);//計(jì)算next數(shù)組
  int k = -1;
  for (int i = 0; i < slen; i++)
  {
    while (k >-1&& ptr[k + 1] != str[i])//ptr和str不匹配,且k>-1(表示ptr和str有部分匹配)
      k = next[k];//往前回溯
    if (ptr[k + 1] == str[i])
      k = k + 1;
    if (k == plen-1)//說(shuō)明k移動(dòng)到ptr的最末端
    {
      //cout << "在位置" << i-plen+1<< endl;
      //k = -1;//重新初始化,尋找下一個(gè)
      //i = i - plen + 1;//i定位到該位置,外層for循環(huán)i++可以繼續(xù)找下一個(gè)(這里默認(rèn)存在兩個(gè)匹配字符串可以部分重疊),感謝評(píng)論中同學(xué)指出錯(cuò)誤。
      return i-plen+1;//返回相應(yīng)的位置
    }
  }
  return -1; 
}

這玩意真的不好解釋?zhuān)?jiǎn)單說(shuō)一下:

從代碼解釋復(fù)雜度是一件比較難的事情,我們從

這里寫(xiě)圖片描述

這個(gè)圖來(lái)解釋。

我們可以看到,匹配串每次往前移動(dòng),都是一大段一大段移動(dòng),假設(shè)匹配串里不存在重復(fù)的前綴和后綴,即next的值都是-1,那么每次移動(dòng)其實(shí)就是一整個(gè)匹配串往前移動(dòng)m個(gè)距離。然后重新一一比較,這樣就比較m次,概括為,移動(dòng)m距離,比較m次,移到末尾,就是比較n次,O(n)復(fù)雜度。 假設(shè)匹配串里存在重復(fù)的前綴和后綴,我們移動(dòng)的距離相對(duì)小了點(diǎn),但是比較的次數(shù)也小了,整體代價(jià)也是O(n)。
所以復(fù)雜度是一個(gè)線性的復(fù)雜度。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • 利用Matlab制作三子棋游戲的示例代碼

    利用Matlab制作三子棋游戲的示例代碼

    三子棋是一種民間傳統(tǒng)游戲,又叫九宮棋、圈圈叉叉、一條龍、井字棋等。將正方形對(duì)角線連起來(lái),相對(duì)兩邊依次擺上三個(gè)雙方棋子,只要將自己的三個(gè)棋子走成一條線,對(duì)方就算輸了。本文將用Matlab制作這一經(jīng)典游戲,感興趣的可以試一試
    2022-03-03
  • C語(yǔ)言實(shí)現(xiàn)紙牌游戲之小貓釣魚(yú)算法

    C語(yǔ)言實(shí)現(xiàn)紙牌游戲之小貓釣魚(yú)算法

    這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)紙牌游戲之小貓釣魚(yú)算法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-01-01
  • C++實(shí)現(xiàn)數(shù)獨(dú)快速求解

    C++實(shí)現(xiàn)數(shù)獨(dú)快速求解

    這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)數(shù)獨(dú)快速求解的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • C語(yǔ)言?typedef的用法示例詳解

    C語(yǔ)言?typedef的用法示例詳解

    typedef是在C和C++編程語(yǔ)言中的一個(gè)關(guān)鍵字,作用是為現(xiàn)有的數(shù)據(jù)類(lèi)型(int、float、char……)創(chuàng)建一個(gè)新的名字,目的是為了使代碼方便閱讀和理解,這篇文章主要介紹了C語(yǔ)言typedef的使用,需要的朋友可以參考下
    2023-06-06
  • 在C++中反射調(diào)用.NET的方法(二)

    在C++中反射調(diào)用.NET的方法(二)

    反射調(diào)用返回復(fù)雜對(duì)象的.NET方法怎么實(shí)現(xiàn)呢?今天小編通過(guò)本文給大家分享在C++中反射調(diào)用.NET的方法(二),需要的朋友參考下
    2017-02-02
  • C++多繼承(多重繼承)的實(shí)現(xiàn)

    C++多繼承(多重繼承)的實(shí)現(xiàn)

    多繼承容易讓代碼邏輯復(fù)雜、思路混亂,本文主要介紹了C++多繼承(多重繼承)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • 深入解析C++中的字符數(shù)組和處理字符串的方法

    深入解析C++中的字符數(shù)組和處理字符串的方法

    這篇文章主要介紹了深入解析C++中的字符數(shù)組和處理字符串的方法,需要的朋友可以參考下
    2015-09-09
  • 軟件構(gòu)建工具makefile基礎(chǔ)講解

    軟件構(gòu)建工具makefile基礎(chǔ)講解

    這篇文章介紹了軟件構(gòu)建工具makefile,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-12-12
  • OpenCV基于稠密光流實(shí)現(xiàn)視頻跟蹤詳解

    OpenCV基于稠密光流實(shí)現(xiàn)視頻跟蹤詳解

    這篇文章主要為大家詳細(xì)介紹了OpenCV如何基于稠密光流實(shí)現(xiàn)視頻跟蹤功能,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的可以參考一下
    2023-02-02
  • VC++植物大戰(zhàn)僵尸中文版修改器實(shí)現(xiàn)代碼

    VC++植物大戰(zhàn)僵尸中文版修改器實(shí)現(xiàn)代碼

    這篇文章主要介紹了VC++植物大戰(zhàn)僵尸中文版修改器實(shí)現(xiàn)代碼,可實(shí)現(xiàn)植物大戰(zhàn)僵尸中的無(wú)限陽(yáng)光與無(wú)冷卻時(shí)間功能,需要的朋友可以參考下
    2015-04-04

最新評(píng)論