乙個需求引發的演算法及優化 KMP演算法

2021-09-11 14:08:12 字數 2577 閱讀 8003

先分析下這個需求,這是乙個簡單的搜尋功能,在你輸入一段字元後會得到後端返回的搜尋結果,很常見.但是問題是需要將你輸入的字串在搜尋結果中變色,那就要得到子串在父串中的位置.

其實就是在乙個字串裡面匹配另乙個字串,然後如果匹配成功返回在主串中子串的 startindex.說完需求,再來看演算法,其實看完這個需求我就想到在 leetcode 上刷過的一道題 鏈結. 以下圖來分析:

首先遍歷父串,得到與子串中第乙個字元相等的 index,然後在遍歷子串,比較子串與父串 index 位之後字元是否相等.如果不相等,那麼繼續遍歷父串得到下乙個 index, 直到找到子串匹配父串或者沒找到退出迴圈. 這是個簡單的思路,就直接貼**:

/*

impstr()

* time complexity: o(nm), space complexity: o(n)

*/func impstr(haystack: string, needle: string) -> int

guard nchars.count != 0 else

for i in 0...(hchars.count - nchars.count)

// 找到子串匹配父串

if j + 1 == nchars.count }}

}return -1

}複製**

小需求搞定,easy! 但是...時間複雜度是o(nm)啊...再優化下?

再回頭看看上面的分析,在子串某一位匹配失敗後,父串開始下一次迴圈,然後之前已經對齊的子串錯開,那麼父串的匹配必然失敗,在上面的演算法中沒有處理這一塊資訊,如果有乙個很長的子串到最後幾位才匹配失敗的話,那這個演算法的效率就非常低下,畢竟是o(nm)的複雜度. 那有什麼辦法的?

kmp演算法

kmp 演算法會利用之前已經匹配成功的部分子串來減少父串的迴圈次數,當子串匹配失敗後,不去讓父串繼續遍歷,而且通過移動子串的 index 來重新開始下一次匹配.

kmp 演算法小解

從上圖來分析下 kmp 的流程,在第一次子串的最後一位d與父串e不相等,此時父串abcdab與子串的abcdab是匹配的,此時父串和子串擁有相同的字首ab,如果父串下次迴圈的其實位置就是ab時,就是父串的字尾和子串的字首對齊,那麼下一次匹配開始時已經成功匹配了兩個字元,然後繼續匹配. 不難看出,這個演算法充分利用了匹配好的字串,減少了父串迴圈的次數,但是問題是需要去計算匹配成功的字串的是否存在相同的字首與字尾,怎麼計算之後再說,先看子串移動的位數也就是父串迴圈的 index的起始位置的偏移量的計算.父串向右偏移的位數 = 匹配成功的字串長度 - 已匹配成功的字串的最大公共字首字尾長度上圖: 父串向右偏移的位數(4) = = 匹配成功的字串長度(6) - 已匹配成功的字串的最大公共字首字尾長度(2) 那就需要另乙個演算法來計算乙個字串的最大公共字首字尾長度,貼一下演算法:

func getnext(patternstr: string) -> [int] 

k += 1

j += 1

next[j] = k

}return next

}複製**

這個函式入參就是子串,得到的乙個等於子串 length的字串,每個 index 是除了當前 character 的最大前字尾長度.

字串匹配

計算完成 next陣列之後,接下來就可以用這個陣列去在父串中找到子串的出現位置,假設匹配到 i 位置時,父串匹配了子串的第乙個character, 接下來就要比較父串的 i+1和子串的1來匹配,直到出現第j 個位置不匹配,那麼就將子串中0.. int

guard needle.characters.count != 0 else

guard haystack.contains(needle) else

var indexi = haystack.startindex

var indexj = needle.startindex

var j = 0

let next = getnext(patternstr: needle)

while (indexi < haystack.endindex && indexj < needle.endindex)

indexi = haystack.index(indexi, offsetby: 1)

indexj = needle.index(indexj, offsetby: 1)

} else else }}

return -1

}複製**kmp 演算法的複雜度是 o(m+n),比之前的o(mn)好多了,基本上這個需求也就可以完美收工了.

其實對客戶端來說,演算法重要不重要呢?能不能用到呢? 其實我的答案是重要,當你遇到乙個類似我遇到的這種需求,不管你用了怎麼耗時的演算法完成,從 ui 的表現來說可能都一樣,但是會覺得不夠好,還可以去優化,我覺得這才是能讓自己去不斷進步的一種想法.

KMP演算法的正確性證明及乙個小優化

直接把作業帖上來是不是有點不太公道呀。無所謂啦反正各位看著開心就行kmp演算法對於模式串pp,建立其字首函式nn 其中n q n q 表示在pp中,以qq位置為結束的可以匹配到字首的最長字尾的長度 也可以理解為那個字首的結束位置 在匹配中,若p i p i 與s j s j 失配,則令i n i?1...

由乙個演算法引發的hash講解

請聽題 給n 1最直接反應的解決方法 法一 遍歷 法二 排序 二分 不符合本文標題,也不夠高階.更高階的做法 開闢乙個陣列,最大下標為n個數中最大的數,將n個數作為陣列的下標,陣列所有值預設0,若該數存在,則將該數下標對應得值改成1,判斷乙個k在不在n個樹中,直接判斷a k 是否為1即可 舉個栗子 ...

KMP演算法Next 函式的乙個應用

記乙個kmp演算法的應用,經典的kmp演算法詳解還是看這裡 問題 給乙個串,求這個串前i位構成的字首由多少個子串組成。比如aabaabaabaab,前2位是aa,a重複了2次,前6位是aabaab,aab重複了2次,前9位是aabaabaab,aab重複了3次,前12位是aabaabaabaab,a...