1、問題
檢查字符串中是否包含子字符串
main_string = 'abcxabcdabcdabcy'
sub_string = 'abcdabcy'
2、關鍵字實現
方法一、find
# 關鍵字 find ,找到返回索引,沒找到返回 -1
print("內置方法,find,索引為:",main_string.find(sub_string))
方法二、index
# 關鍵字 index ,找到返回索引,沒找到返回 Error,ValueError: substring not found
try:
print("內置方法,index,索引為:",main_string.index(sub_string))
except Exception as e:
print("內置方法,index未找到,返回異常:",str(e))
3、簡單實現方法
特點,簡單,低效
核心,按主串字符索引,逐個完整匹配
方法一、暴力遞歸
def violent_search(main_string,sub_string,index=0):
'''
暴力查找,遞歸
主字符串、子字符串、當前檢索索引,默認為0
'''
main_l = len(main_string)
sub_l = len(sub_string)
if main_l < sub_l + index:
return -1
elif main_string[index:sub_l+index] == sub_string:
return index
else:
index += 1
return violent_search(main_string,sub_string,index)
print("暴力查找,遞歸,索引為:",violent_search(main_string,sub_string))
方法二、循環迭代
def violent_search_next(main_string,sub_string):
'''
暴力查找,for循環
主字符串、子字符串
'''
main_l = len(main_string)
sub_l = len(sub_string)
for i in range(main_l-sub_l+1):
if main_string[i:sub_l+i] == sub_string:
return i
return -1
print("暴力查找,循環,索引為:",violent_search_next(main_string,sub_string))
4、KMP
實現
發現者,D.E.Knuth,J.H.Morris,V.R.Pratt
時間復雜度,O(m+n)
核心,利用匹配失敗后的信息,盡量減少模式串與主串的匹配次數以達到快速匹配的目的
步驟一、構建部分匹配表
def kpm_gen_while(substring):
"""
構造臨時數組pnext,用于計算 “部分匹配表”
"""
index = 0
m = len(substring)
pnext = [0]*m
i = 1
while i < m:
if (substring[i] == substring[index]):
pnext[i] = index + 1
index += 1
i += 1
elif (index!=0):
index = pnext[index-1]
else:
pnext[i] = 0
i += 1
return pnext
def kpm_gen_for(substring):
m = len(substring)
pnext = [0]*m
# 首字符前綴和后綴都為空集,共有元素的長度為0,循環從 1 開始
for i in range(1,m):
temp = substring[0:i+1]
# print("判斷字符串,",temp)
for j in range(1,len(temp)):
# print("前綴,",temp[0:j],"后綴,",temp[-j:])
if temp[0:j] == temp[-j:]:
pnext[i] = j
return pnext
print(kpm_gen_while(sub_string))
print(kpm_gen_for(sub_string))
兩種構建方式,區別不大,都是根據子串,計算前綴、后綴,記錄基于前綴,后綴中重復最長的字段長度。
例如 "ABCDABD"
"A"的前綴和后綴都為空集,共有元素的長度為0;
"AB"的前綴為[A],后綴為[B],共有元素的長度為0;
"ABC"的前綴為[A, AB],后綴為[BC, C],共有元素的長度0;
"ABCD"的前綴為[A, AB, ABC],后綴為[BCD, CD, D],共有元素的長度為0;
"ABCDA"的前綴為[A, AB, ABC, ABCD],后綴為[BCDA, CDA, DA, A],共有元素為"A",長度為1;
"ABCDAB"的前綴為[A, AB, ABC, ABCD, ABCDA],后綴為[BCDAB, CDAB, DAB, AB, B],共有元素為"AB",長度為2;
"ABCDABD"的前綴為[A, AB, ABC, ABCD, ABCDA, ABCDAB],后綴為[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的長度為0。
部分匹配表 即為 0 0 0 0 1 2 0
步驟二、實現比對方法
def kmp_match_for_else(main_string, sub_string):
m = len(main_string)
s = len(sub_string)
cur = 0 #起始指針cur
table = kpm_gen_for(sub_string)
while cur <= m - s:
for i in range(s):
print("當前指針 ",cur,"子字符串中索引 ",i,"主值 ",main_string[i+cur],"輔值 ",sub_string[i])
if main_string[i+cur] != sub_string[i]:
cur += max(i - table[i-1], 1)
break
else:
return cur
return -1
def kmp_match(main_string, sub_string):
m = len(main_string)
s = len(sub_string)
cur = 0 #起始指針cur
table = kpm_gen_for(sub_string)
while cur <= m - s:
for i in range(s):
# print("當前指針 ",cur,"子字符串中索引 ",i,"主值 ",main_string[i+cur],"輔值 ",sub_string[i])
if main_string[i+cur] != sub_string[i]:
#根據匹配表,移動多位 ,移動位數 = 已匹配的字符數 - 對應的部分匹配值(最后一次相等的值即,i - 1)
cur += max(i - table[i-1], 1)
break
elif i == s-1:
# 如果子字符串最后一位也匹配上,返回當前索引
return cur
return -1
print("KMP查找,索引為:",kmp_match(main_string,sub_string))
兩種寫法區別在于第一種使用了 Python
中 for ... else
語法,考慮其他語言可能不支持,第二種使用了常規elif