1.用途
用法:查看某个词在句子中出现的位置
2.概念
假设:strData="ABCAB",下面出现的strData都是代表字符串的意思
子串:字符串中,任意连续的字符连接的串,strData的子串就有A,AB,ABC,ABCA,ABCAB,B等等
前缀:以首位字母开头的子串,strData的前缀就有A,AB,ABC,ABCA,ABCAB
真前缀:以首位字母开头且不包括末位的子串,strData的真前缀就有A,AB,ABC,ABCA
后缀:以末位字母结尾的子串,strData的后缀就有B,AB,CAB,BCAB,ABCAB
真后缀:以末位字母结尾且不包括首位的子串,strData的后缀就有B,AB,CAB,BCAB
公共前后缀:真前缀和真后缀的交集,strData的公共前后缀就有B,AB
最长公共前后缀:长度最长的公共前后缀,strData的最长公共前后缀是AB
3.获取某个词的全部前缀的最长公共前后缀的长度(最长公共前后缀&部分匹配表)
index | 0 | 1 | 2 | 3 | 4 |
字符 | A | B | C | A | B |
长度 | 0 | 0 | 0 | 1 | 2 |
index = 1,代表前缀子串AB,其没有最长公共前后缀,所以最长公共前后缀长度为0
index = 3,代表前缀子串ABCA,其最长公共前后缀是A,所以最长公共前后缀长度为1
假设next为词的最长公共前后缀的长度数组
假设已知next[i-1] = preLen,则字符串形式(里面的A、B、C单纯表示的是占位字符,A可能等于B,也可能不相等)如下:
字符 | A | B | ... | C | H | ... | M | A | B | ... | C | x |
index | 0 | 1 | ... | preLen-1 | preLen | ... | i-preLen-1 | i-preLen | i-preLen+1 | ... | i-1 | i |
在计算next[i]的时候,利用一个特性公共前后缀代表前面部分要很后面部分一模一样,我们期望 x = strData[preLen],那样就有next[i] = next[i-1]+1=preLen+1。但有时候会出现x != strDatap[preLen],那就得从strData[i-1]的最长公共前后缀里抽取,如下面:strData="ABCABHABCABC",i=11,preLen=5
字符 | A | B | C | A | B | H | A | B | C | A | B | C |
index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
长度 | 0 | 0 | 0 | 1 | 2 | 0 | 1 | 2 | 3 | 4 | 5 |
H != C => strData[5] != strData[11] => strData[preLen] != strData[i]
preLen = next[preLen] => preLen = 2
字符 | A | B | C | A | B | H | A | B | C | A | B | C |
index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
长度 | 0 | 0 | 0 | 1 | 2 | 0 | 1 | 2 | 3 | 4 | 5 | 3 |
C = C => strData[2] = strData[11] => strData[preLen] = strData[i]
next[i] = preLen + 1 => next[i] = 2 + 1 = 3
python代码:
def kmpNext(strData):
# 字符串的长度
strSize = len(strData)
if strSize == 0:
return []
# 记录字符串最长公共前后缀&部分匹配表
next = [0 for i in range(strSize)]
# 遍历的索引
strIndex = 1
# 记录上一个最大的子串的最长公共前后缀
preLen = 0
# 任意一个字符的最长公共前后缀都是0
next[0] = 0
while strIndex < strSize:
# 假设一个子串的最长公共前后缀的长度是Len,而strIndex代表了当前子串的长度,则该子串的最长公共前后缀是[0,Len-1]或者[i-(Len-1),i]
# 如果当前子串string的前一个子串的最长公共前后缀的长度是preLen,此时,
# (1)如果当前子串的string[preLen](前一个子串的最长公共字串前后缀的下一个字符) = string[i],
# 则当前子串的最长公共字串前后缀长度为preLen+1,最长公共字串前后缀为[0,preLen]
# (2)如果当前子串的string[preLen](前一个子串的最长公共字串前后缀的下一个字符) != string[i],
# 1)如果此时preLen为0,说明上一个没有最长公共子串前后缀,子串的最长公共子串前后缀的长度为0
# 2)如果此时preLen不为0,则需要利用一个特性(最大公共前后缀的公共前后缀也是子串的公共前后缀,如:ABABA的最长公共前后缀有:ABA,其中ABA的最长公共前后缀有:A)
# 此时需要不断地拿最大公共前后缀的最大前后缀与当前字符进行比较
if strData[strIndex] == strData[preLen]:
preLen += 1
next[strIndex] = preLen
strIndex += 1
else:
if preLen == 0:
next[strIndex] = 0
strIndex += 1
else:
preLen = next[preLen - 1]
return next
4.KMP算法(找到所有匹配到的字符串)
我们有一个词patternStr,一个句子matchStr,在matchStr中找到patternStr的位置
matchStr字符 | A | H | C | H | H | H | B | C | D |
index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
i | 0 | ||||||||
patternStr字符 | H | H | B | ||||||
index | 0 | 1 | 2 | ||||||
len | 0 | 1 | 0 | ||||||
j | 0 |
如果matchStr[i] = patternStr[j],那就i +=1,j+=1,会有matchStr[i-j, j-1] = patternStr[0, j-1],这一步是为了尽可能找到匹配的字符。当 j = len(patternStr),说明匹配完成
当matchStr[i] != patternStr[j],那就有两种情况
1)j = 0,那就 i += 1。这一步说明matchStr在i位置上的字符和patternStr的首字符不匹配,此时的特点是首位都无法匹配,寻找能进行首位匹配的i
2)j != 0,那就j = next[j],利用patternStr的最长公共子串的下一位进行判断,看看是否能判断成功。此时的特点是匹配失败,选择缓存点看看能不能继续匹配,尽可能避免重新匹配,也就是j=0。
python代码:
def kmpNext(strData):
# 字符串的长度
strSize = len(strData)
if strSize == 0:
return []
# 记录字符串最长公共前后缀&部分匹配表
next = [0 for i in range(strSize)]
# 遍历的索引
strIndex = 1
# 记录上一个最大的子串的最长公共前后缀
preLen = 0
# 初始化
next[0] = 0
while strIndex < strSize:
# 假设一个子串的最长公共前后缀的长度是Len,而strIndex代表了当前子串的长度,则该子串的最长公共前后缀是[0,Len-1]或者[i-(Len-1),i]
# 如果当前子串string的前一个子串的最长公共前后缀的长度是preLen,此时,
# (1)如果当前子串的string[preLen](前一个子串的最长公共字串前后缀的下一个字符) = string[i],
# 则当前子串的最长公共字串前后缀长度为preLen+1,最长公共字串前后缀为[0,preLen]
# (2)如果当前子串的string[preLen](前一个子串的最长公共字串前后缀的下一个字符) != string[i],
# 1)如果此时preLen为0,说明上一个没有最长公共子串前后缀,子串的最长公共子串前后缀的长度为0
# 2)如果此时preLen不为0,则需要利用一个特性(最大公共前后缀的公共前后缀也是子串的公共前后缀,如:ABABA的最长公共前后缀有:ABA,其中ABA的最长公共前后缀有:A)
# 此时需要不断地拿最大公共前后缀的最大前后缀与当前字符进行比较
if strData[strIndex] == strData[preLen]:
preLen += 1
next[strIndex] = preLen
strIndex += 1
else:
if preLen == 0:
next[strIndex] = 0
strIndex += 1
else:
preLen = next[preLen - 1]
return next
def kmp(patternStr, matchStr):
patternNext = kmpNext(patternStr)
i = 0
j = 0
# 记录匹配到的词的首位,如果为空,则代表匹配不成功
resultList = []
while i < len(matchStr) and j < len(patternStr):
if matchStr[i] == patternStr[j]:
i += 1
j += 1
if j == len(patternStr):
resultList.append(i-j)
j = patternNext[j - 1]
else:
if j == 0:
i += 1
else:
j = patternNext[j - 1]
return resultList