要完善乙個string字串類,那麼實現查詢子串的功能是必不可少的,實現子串查詢可以使用樸素演算法,每次匹配乙個字元後向右移動乙個位置,這樣執行下來效率是比較低的,所以就有了kmp演算法,它能夠準確的知道當前字元不匹配後字串應該向右移動多少位,由於剛接觸kmp演算法,所以很多還明白的不是很透徹,在此記錄在學習kmp演算法過程中的一些理解。
先看一張利用kmp演算法得出的一張字元匹配圖:
我們稍微對圖中的流程進行分析:
0、題目是要在一長串字元bbc abcdab abcdabcdabde中匹配乙個子串abcdabd.
1、第一步是子串的第乙個字元和母串的第乙個字元匹配,匹配失敗,子串整體右移1位。
2、重複移動操作後,進入到第二步,能夠匹配前六個字元,但是第七個卻匹配失敗,這是子串整體向右移動4位(為什麼是4?)。
3、移動四位後再次挨個匹配字元,發現在第三個字元處匹配失敗,此時右移2位(為什麼是2?)
4、移動兩位後在第乙個字元處就匹配失敗,然後子串右移1位。
這裡為什麼移動那麼多位數源自前人的一些偉大的發現:匹配失敗時右移位數與子串本身相關,與母串無關;移動位數 = 已匹配字元數 - 對應的部分匹配值;對應部分匹配值來自子串的唯一存在的乙個部分匹配表。
現在我們先給出上面子串的部分匹配表,驗證一下移動位數的計算方法是否正確:
現在根據這個表來計算一下:
圖中可以看出前六位是匹配成功的,在第七個字元匹配時失敗,根據查表可以知道匹配成功6個字元對應的部分匹配值為2,所以移動位數就是6-2 = 4.
那麼問題來了,部分匹配表是怎麼獲得的?
在講解部分匹配表之前我們需要知道它是由匹配成功字元個數對應的部分匹配值構成的。
在解釋部分匹配值的概念前介紹一下子串中字首和字尾的概念:
字首:除開最後乙個字元以外,其餘字串的全部含頭部字元的組合。如:abcd的字首是:a、ab、abc。在所有組合中,a需為起始字元。
字尾:除開第乙個字元以外 ,其餘字串的全部含尾部字元的組合。如:abcd的字尾是:d、cd、bcd。在所有組合中,d需為結尾字元。
知道了字首和字尾,那麼應該知道部分匹配值其實是由子串的字首和字尾的共有組合的最長組合的元素個數確定。
通過列舉我們可以比較容易的找出部分匹配值,但是在程式設計中我們應該如何編碼?
下面介紹實現關鍵:
1、我們將部分匹配表記做pmt,通過觀察我們可以發現乙個字元匹配成功的部分匹配值始終為0,即pmt[1] = 0,因為字首和字尾都為空,所以共有組合最長為0,也即是部分匹配表中下標為0的部分匹配值為0.
2、從2個字元匹配成功開始遞推:假設pmt[n] = pmy[n-1]+1(再次強調pmt[n]的值就是最長共有元素組合的長度)
當假設不成立時,pmt[n]在pmt[n-1]的基礎上減小。
現在進行乙個遞推例項:
建議先理解各個概念再看遞推分析。
在講解之前給出兩條規則:
規則1:當前需要求出的ll值由歷史ll值推導出來。
規則2:當可選的ll值為0時,直接比對首尾字元,若相等則 ll = 0+1 = 1;不相等則 ll = 0.
使用部分匹配表(kmp演算法)
設母串的下標為i,子串的下標為j
程式設計實現部分匹配表和kmp演算法
屬於string類成員函式。
int* string::make_pmt(const char* p)//建立部分匹配表
if(p[ll] == p[i])//新加進來的字元不能作為種子,找到種子並擴充套件一位後直接比對擴充套件的字元
ret[i] = ll;//在部分匹配表中儲存部分匹配值}}
return ret;
}int string::kmp(const char* s, const char* p)
if(p[j] == s[i] )//理想情況,匹配成功子串下標就後移乙個。
if(j == pl)//匹配完最後乙個字元後表示匹配完成
}free(pmt);
}return ret;
}
程式過程在語句注釋中比較詳細,若是將分析放在外面可能還難於理解。
kmp函式返回的 是子串在母串中的位置,若查詢不到返回-1.
通過分析樸素演算法查詢子串我們知道他的演算法複雜度為o(mn),而經過kmp演算法優化後子串查詢演算法為o(m+n)
所以得出部分匹配表是提高子串查詢演算法的關鍵
最後再說明一遍部分匹配值定義為字首和字尾最長共有元素的長度,即ll值。
在分析原理中我們是通過遞推的方法產生的部分匹配表。
通過確定子串移動位數來提高查詢效率。
多看幾遍。
感謝狄泰唐老師
資料結構 KMP演算法
求串的模式值next n 定義 1 next 0 1 意義 任何串的第乙個字元的模式值規定為 1。2 next j 1 意義 模式串t中下標為j的字元,如果與首字元相同,且 j的前面的 1 k個字元與開頭的 1 k個字元不等 或者相等但t k t j 1 k 如 t abcabcad 則next 6...
資料結構 KMP演算法
在我的理解,kmp演算法最核心的同時最難理解的是這個next 函式。但是,next 的值是挺好求的,難在哪呢?這個函式難在邏輯。理解起來很費勁,但真的很好用,並且這個函式的結果很好求。例如求模式串t ababaaa 的next j 的函式值 是這樣的,當j 0,next 0 1,對於任何子串,第乙個...
資料結構 KMP演算法
課本知識和競賽用的還是略微有些差別 比如 abcabaa 按照最長公共前字尾的求法 先得到 0001211 之後去掉最後一位,得到000121,再把第一位賦值為 1 即 1000121,這個就是我們一般做oj題用的所謂next陣列 但是按照課本的求法 你會發現結果卻是0111232,include ...