最简单上手Go语言常用数据结构与算法代码, 你可以通过这篇文章来学习如何使用KMP字符串匹配,来应用于实际算法问题中。
KMP算法是一种常用于字符串匹配的算法,其名称来自算法发明者的名字Knuth、Morris和Pratt。它的核心思想是(注意本专栏不讲述具体过程,学习这种细节的最好办法我认为就是跟着视频手写一下),在匹配过程中当出现不匹配的情况时,不必每次从头开始重新匹配,而是通过已知信息来跳过一部分无需匹配的字符,从而提高匹配效率。
具体来说,KMP算法通过构建一个模式串(待匹配的字符串)的前缀表(即部分匹配表),然后在匹配过程中根据前缀表来移动模式串,从而减少比较次数。在KMP算法中,匹配过程中不匹配的情况只会出现在模式串中,而前缀表可以帮助我们跳过不必要的比较。
KMP算法的时间复杂度为O(m+n),其中m是模式串的长度,n是文本串(待匹配的字符串)的长度。相对于暴力匹配算法,KMP算法的时间复杂度更低,特别是在文本串较长、模式串较短的情况下,KMP算法的优势更为明显。
KMP算法是一种非常经典的字符串匹配算法,广泛应用于各种文本处理系统、编译器、字符串搜索、数据压缩等领域。
package main
import (
"fmt"
)
// 构建前缀表
func buildPrefixTable(pattern string) []int {
// 初始化前缀表为0
prefixTable := make([]int, len(pattern))
// 设置指针i和j,分别指向前缀表和模式串中的字符
i, j := 0, 1
for j < len(pattern) {
if pattern[i] == pattern[j] {
// 如果两个字符相等,则前缀表值为i+1
prefixTable[j] = i + 1
i++
j++
} else if i > 0 {
// 如果两个字符不等,且i不为0,则回溯到上一个相等的字符
i = prefixTable[i-1]
} else {
// 如果两个字符不等,且i为0,则前缀表值为0
j++
}
}
return prefixTable
}
// KMP算法
func kmp(text, pattern string) int {
// 构建前缀表
prefixTable := buildPrefixTable(pattern)
// 设置指针i和j,分别指向文本串和模式串中的字符
i, j := 0, 0
for i < len(text) && j < len(pattern) {
if text[i] == pattern[j] {
// 如果两个字符相等,则继续比较下一个字符
i++
j++
} else if j > 0 {
// 如果两个字符不等,且j不为0,则回溯到上一个相等的字符
j = prefixTable[j-1]
} else {
// 如果两个字符不等,且j为0,则文本串指针后移
i++
}
}
if j == len(pattern) {
// 如果模式串已全部匹配,则返回匹配位置
return i - j
}
// 否则返回-1,表示未匹配成功
return -1
}
func main() {
// 测试KMP算法
text := "ABCABCDABABCDABCDABDE"
pattern := "ABCDABD"
pos := kmp(text, pattern)
if pos != -1 {
fmt.Printf("在文本串\"%s\"中,模式串\"%s\"的位置为%d\n", text, pattern, pos)
} else {
fmt.Printf("在文本串\"%s\"中,未找到模式串\"%s\"\n", text, pattern)
}
}
这里的buildPrefixTable
函数用于构建模式串的前缀表,kmp
函数则用于执行KMP算法,返回匹配位置或-1表示未匹配成功。
KMP算法的核心思想在代码中得到了体现,即通过前缀表来跳过不必要的比较,从而提高匹配效率。在文本串和模式串匹配的过程中,如果当前字符匹配,则继续向后比较下一个的字符;如果当前字符不匹配,则利用前缀表回溯到上一个相等的字符,从而避免了在模式串中进行重复比较。
KMP算法的时间复杂度为O(m+n),其中m为文本串的长度,n为模式串的长度。这是因为在匹配过程中,i和j指针分别只会后移,不会回退,因此最多移动m+n次。而构建前缀表的时间复杂度为O(n)。
KMP算法适用于在长文本串中查找模式串的匹配位置。与暴力匹配算法相比,KMP算法通过使用前缀表来避免了在模式串中进行重复比较,因此具有更好的时间复杂度和更高的效率。在实际应用中,KMP算法可以用于字符串匹配、子序列匹配、DNA序列匹配等领域。