1 KMP算法
KMP O(N)
1.1 KMP算法原理
# KMP
# KMP
def getIndexOf(s,m): # s是主字符串 m是匹配子字符串
if s == None or m == None or len(m) < 1 or len(s) < len(m):
return -1
str = list(s)
match = list(m)
x = 0 # str 中当前比对到的位置
y = 0 # match中当前比对到的位置,match第一个位置和第二个位置固定 -1 和 0
next = getNextArray(match) # 得到next数组 next[i]代表 在match中i之前的字符串match[0..i-1]
# 每个位置都有最长前缀和后缀相等的数字
while x < len(str) and y < len(match): # 不能越界
if str[x] == match[y]: # 比对相同,都往下走
x += 1
y += 1
elif next[y] == -1: # y == 0,x与y不同情况下,y已经来到0位置了,
x += 1 # s换一个开头++
else:
y = next[y] # 跳转到前缀上
# x越界 y没有越界,返回-1,s就没有匹配m的字符串
# x没越界 y越界, 匹配完成,返回开头 x-y就是str开始位置
# x越界,y越界
return x - y if y == len(match) else -1
# M o(M)
# next数组 第一个位置和第二个位置固定 -1 和 0
# 求i位置时,i-1位置都知道信息
def getNextArray(match):
if len(match) == -1:
return [-1]
next = [0] * len(match)
next[0] = -1
next[1] = 0
i = 2
# cn代表,cn位置的字符,是当前和i-1位置比较的字符
# 如果比对一样,i位置就设置为cn+1
# 求二位置,初始cn就为0
cn = 0
while i < len(next):
# 如果i-1位置字符==此时跳到位置的字符,i位置可以cn++了,cn是跳着的
if match[i - 1] == match[cn]: # 跳出来的时候
cn += 1 # 因为下一个位置要用到上一个位置的cn值
next[i] = cn
i += 1 # 求下一个位置next的值
elif cn > 0: # 往前跳吧
cn = next[cn]
else: # 都不相等,直接跳到开头
next[i] = 0
i = i + 1
return next
a = "abcdaececdeas"
b = "cde"
print(getIndexOf(a,b))
1.2 KMP算法应用
1.2.1 一个字符串是否为主串的旋转词
“123456”:旋转词有“234561”,“345612”,“456123”,就是123…往右移动
解决办法:
将“123456”扩展两倍变成“123456123456”
也就是利用KMP算法来寻找子串:
# KMP
def getIndexOf(s,m): # s是主字符串 m是匹配子字符串
if s == None or m == None or len(m) < 1 or len(s) < len(m):
return -1
str = list(s)
match = list(m)
x = 0 # str 中当前比对到的位置
y = 0 # match中当前比对到的位置,match第一个位置和第二个位置固定 -1 和 0
next = getNextArray(match) # 得到next数组 next[i]代表 在match中i之前的字符串match[0..i-1]
# 每个位置都有最长前缀和后缀相等的数字
while x < len(str) and y < len(match): # 不能越界
if str[x] == match[y]: # 比对相同,都往下走
x += 1
y += 1
elif next[y] == -1: # y == 0,x与y不同情况下,y已经来到0位置了,
x += 1 # s换一个开头++
else:
y = next[y] # 跳转到前缀上
# x越界 y没有越界,返回-1,s就没有匹配m的字符串
# x没越界 y越界, 匹配完成,返回开头 x-y就是str开始位置
# x越界,y越界
return x - y if y == len(match) else -1
# M o(M)
# next数组 第一个位置和第二个位置固定 -1 和 0
# 求i位置时,i-1位置都知道信息
def getNextArray(match):
if len(match) == -1:
return [-1]
next = [0] * len(match)
next[0] = -1
next[1] = 0
i = 2
# cn代表,cn位置的字符,是当前和i-1位置比较的字符
# 如果比对一样,i位置就设置为cn+1
# 求二位置,初始cn就为0
cn = 0
while i < len(next):
# 如果i-1位置字符==此时跳到位置的字符,i位置可以cn++了,cn是跳着的
if match[i - 1] == match[cn]: # 跳出来的时候
cn += 1 # 因为下一个位置要用到上一个位置的cn值
next[i] = cn
i += 1 # 求下一个位置next的值
elif cn > 0: # 往前跳吧
cn = next[cn]
else: # 都不相等,直接跳到开头
next[i] = 0
i = i + 1
return next
a = "123456123456"
b = "345612"
if getIndexOf(a,b) != -1:
print(True)
else:
print(False)
1.2.2 A树是否包含B树整个结构
1.2.2.1 暴力方案
class Node:
def __init__(self,v):
self.value = v
self.left = None
self.right = None
# big 做头节点的数,其中是否有某棵子树的结构,是和small为头的树,完全一样的
def containsTree1(big,small):
if small == None: # small如果是空树,big主树必定有空结构
return True
# 如果small不空,但是big是空树,那么空树一定不包含不空的数
if big == None:
return False
# big和small都不为空
if isSameValueStructure(big,small): # big和small是否完全相同
return True
# 接下来,big往下走,测试到底有没有一部分和small完全相同
return containsTree1(big.left,small) or containsTree1(big.right,small)
# head1为头的树,是否在结构对应上,完全和head2一样
def isSameValueStructure(head1,head2):
if head1 == None and head2 != None:
return False
if head1 != None and head2 == None:
return False
if head1 == None and head2 == None:
return True
if head1.value != head2.value:
return False
# 上面条件通过后,到这里变成,head1.value == head2.value
# 再判断左右孩子
return isSameValueStructure(head1.left,head2.left) and isSameValueStructure(head1.right,head2.right)
1.2.2.2 KMP方案
解决:序列化,B树的序列化是否存在A序列化中,若存在就说明存在
import bingcha # https://blog.youkuaiyun.com/Jieyi_/article/details/132817063 第8集,二叉树递归
# big 做头节点的数,其中是否有某棵子树的结构,是和small为头的树,完全一样的
def containsTree2(big,small):
if small == None: # small如果是空树,big主树必定有空结构
return True
# 如果small不空,但是big是空树,那么空树一定不包含不空的数
if big == None:
return False
# big和small都不为空
b = preSerial(big) # 将big树先序序列化,转为字符串组成的list,里面每个元素都是字符串
s = preSerial(small) # 将small树先序序列化,比如["5","3",None,None,"103","54",None,None]
# 用于KMP都得是字符串才行, b和s都是列表。str和match得是字符串,但是KMP内有转列表功能,所以也可直接传过去列表
return getIndexOf(b,s) != -1
# 先序序列化
def preSerial(head):
ans = []
pres(head,ans)
return ans
def pres(head,ans):
if head == None:
ans.append(None)
else:
ans.append(str(head.value))
pres(head.left,ans)
pres(head.right,ans)
# KMP算法稍微改一下
def getIndexOf(s, m): # s是主字符串 m是匹配子字符串
if s == None or m == None or len(m) < 1 or len(s) < len(m):
return -1
# 已经传过来列表了,不需要转化了
# str = list(s)
# match = list(m)
x = 0 # str 中当前比对到的位置
y = 0 # match中当前比对到的位置,match第一个位置和第二个位置固定 -1 和 0
next = getNextArray(m) # 得到next数组 next[i]代表 在match中i之前的字符串match[0..i-1]
# 每个位置都有最长前缀和后缀相等的数字
while x < len(s) and y < len(m): # 不能越界
# if s[x] == m[y]: # 比对相同,都往下走
# x += 1
# y += 1
# 这个地方改成字符串比较,因为列表内每个位置都是字符串
if s[x] == m[y]: # 如果字符串相等,往下一个位置
x += 1
y += 1
elif next[y] == -1: # y == 0,x与y不同情况下,y已经来到0位置了,
x += 1 # s换一个开头++
else:
y = next[y] # 跳转到前缀上
# x越界 y没有越界,返回-1,s就没有匹配m的字符串
# x没越界 y越界, 匹配完成,返回开头 x-y就是str开始位置
# x越界,y越界
return x - y if y == len(m) else -1
def getNextArray(match):
if len(match) == -1:
return [-1]
next = [0] * len(match)
next[0] = -1
next[1] = 0
i = 2
# cn代表,cn位置的字符,是当前和i-1位置比较的字符
# 如果比对一样,i位置就设置为cn+1
# 求二位置,初始cn就为0
cn = 0
while i < len(next):
# 如果i-1位置字符==此时跳到位置的字符,i位置可以cn++了,cn是跳着的
if match[i - 1] == match[cn]: # 跳出来的时候
cn += 1 # 因为下一个位置要用到上一个位置的cn值
next[i] = cn
i += 1 # 求下一个位置next的值
elif cn > 0: # 往前跳吧
cn = next[cn]
else: # 都不相等,直接跳到开头
next[i] = 0
i = i + 1
return next
T = bingcha.Binarytree()
T.insert_node(1)
T.insert_node(2)
T.insert_node(3)
T.insert_node(4)
T.insert_node(5)
Q = bingcha.Binarytree()
Q.insert_node(2)
Q.insert_node(4)
Q.insert_node(5)
print(containsTree2(T.root,Q.root))
2 bfprt算法(很重要)
找到数组中第k小的数,并且时间复杂度为O(N)。笔试写第一个,面试聊bfprt算法
- 随机选一个数M
- 荷兰国旗问题
- 如果命中了k,返回index左侧或右侧,只返回一侧
import random
# arr 第k小的数
# process2(arr,0,N - 1,k - 1) 从0到N - 1范围上,位于k-1位置返回
# arr[L.....R] -> L到R位置 这个范围内,如果排序的话(不是真的去排序),找到位于index的数
# index[L...R]
def process2(arr,L,R,index):
if L == R: # L == R == index 只有一个数
return arr[L] # base case
# 不止一个数 随机选一个数,范围 L ~ R-L
pivot = arr[L + random.randint(0,(R - L + 1))] # 等概率随机选一个值,作为划分
# 第二步,荷兰国旗问题
# 返回长度为2的数组 range[0] range[1],表示划分值的左边界和右边界在哪
# L.....R 范围是0....1000 荷兰国旗划分完后 划分值范围是70...800
# range[0] = 70 range[1] = 800
range = partition(arr,L,R) # 荷兰国旗问题
if index >= range[0] and index <= range[1]: # 如果你要找的数正好是划分值
return arr[index]
elif index < range[0]: # 如果index在划分值左边,就去左边找
return process2(arr,L,range[0] - 1,index)
else:
return process2(arr,range[1] + 1,R,index) # 右侧去找
def swap(arr, i, j):
tmp = arr[i]
arr[i] = arr[j]
arr[j] = tmp
# 荷兰国旗问题
def partition(arr, L, R):
if L > R:
return [-1, -1]
if L == R:
return [L, R]
less = L - 1 # 小于区域右边界,不包括左边第一个数
more = R # 大于区域左边界,[6],more = 6,包括了右边第一个数,要固定右边第一个数
index = L # 左边第一个数
while index < more: # 左右两个指针没有相遇时
if arr[index] == arr[R]: # 以arr[R]作分割
index = index + 1
elif arr[index] < arr[R]:
less = less + 1 # 小于区域向右扩一个位置
swap(arr, index, less) # 把小值和小于区域的数交换
index = index + 1 # index移动到下一个
else: # 第一次,如果右边第一个数大于左边
more = more - 1 # 大于区域向左扩一个位置,more = 5,先将R[6]固定在最后一个位置上
swap(arr, index, more) # R[5] 与 左边第一个数交换,将R[6]数固定在最后一个位置上
swap(arr, more, R) # 将大于区域的第一个数和R(固定的数)位置上交换,接触R位置固定
return [less + 1, more] # 返回等于区域的位置,返回大小为2的数组,第一个数是等于区域最左边的数,第二个数是等于区域最右边的数
arr = [1,2,3,4,7,8,9]
print(process2(arr,0,len(arr)-1,2))
真正的bftrp算法
不用概率直接求,收敛到O(N)
- 随机选一个数M(只有这个步骤更改了)
- 荷兰国旗问题
- 如果命中了k,返回index左侧或右侧,只返回一侧
快排是两边都要递归
那么,如何将M精挑细选出来?
- 5个数划分为一组 O(1)
- 每一个小组内排序(N/5) ,只有5个数之间排序 ,跨组不排序 O(N)
- 递归调用bfprt,marr中位数数组传到bfprt,bfprt(marr),所有数组求完,递归完,得到一个精确数字M
- 选出M数字后,进行荷兰国旗操作
- 左右两侧选一侧取递归
T(N) = T(N/5) + T(?) + O(N)
精挑细选可以舍弃3/10 * N规模
# 利用bfprt算法,时间复杂度O(N)
def minKth3(array,k):
arr = copyArray(array)
return
# arr[L...R] 如果排序的话,位于index位置的数,是什么,返回
def bfprt(arr,L,R,index):
if L == R: # 如果只剩一个数
return arr[L]
pivot = medianOfmedians(arr,L,R) # 精挑细选一个数做划分值
range = partition(arr,L,R) # 荷兰国旗问题
if index >= range[0] and index <= range[1]: # 如果你要找的数正好是划分值
return arr[int(index)]
elif index < range[0]: # 如果index在划分值左边,就去左边找
return bfprt(arr,L,range[0] - 1,index)
else:
return bfprt(arr,range[1] + 1,R,index) # 右侧去找
# arr[L...R] 五个数一组
# 每个小组内部排序
# 每个小组中位数拿出来,组成marr
# marr中的中位数,返回
def medianOfmedians(arr,L,R):
size = R - L + 1 # 一共的数量
# 要不要补最后一组数,以5分组,size 是 10 有两组,是11有三组
# 如果余5不等于0,单独补一组
offset = 0 if size % 5 == 0 else 1
mArr = []
for team in range(int(size / 5) + offset): # 组数 + 是否需要补组
teamFirst = L + team * 5
# 每一组的中位数放入mArr中
# 在arr的teamFirst....min(R,teamFirst + 4) 范围内,排完序后,选择中间的位置放到放到此时组里,做组长
# 第一组位置 L........L+4
# 第二组位置 L+5......L+9
# 第二组位置 L+10......L+14
# 开始位置是teamFirst,结尾位置是teamFirst + 4,如果最后一组不够5个,就只能R了
mArr.append(getMedian(arr,teamFirst,min(R,teamFirst + 4)))
# mArr中,找到中位数
# mArr(0,len(mArr) - 1,len(mArr) / 2) bfprt快速拿到中位数
return bfprt(mArr,0,len(mArr) - 1,len(mArr) / 2) # 找到中位数数组的中位数
# 5个数之间排序,并且拿出来
def getMedian(arr,L,R):
insertionSort(arr,L,R) # 排序插入
return arr[int((L + R) / 2)] # 拿到中间位置数
def insertionSort(arr,L,R):
for i in range(L+1,R+1):
for j in range(i-1,L-1,-1):
if arr[j] > arr[j+1]:
swap(arr,j,j+1)
else:
break
def swap(arr, i, j):
tmp = arr[i]
arr[i] = arr[j]
arr[j] = tmp
def partition(arr, L, R):
if L > R:
return [-1, -1]
if L == R:
return [L, R]
less = L - 1 # 小于区域右边界,不包括左边第一个数
more = R # 大于区域左边界,[6],more = 6,包括了右边第一个数,要固定右边第一个数
index = L # 左边第一个数
while index < more: # 左右两个指针没有相遇时
if arr[index] == arr[R]: # 以arr[R]作分割
index = index + 1
elif arr[index] < arr[R]:
less = less + 1 # 小于区域向右扩一个位置
swap(arr, index, less) # 把小值和小于区域的数交换
index = index + 1 # index移动到下一个
else: # 第一次,如果右边第一个数大于左边
more = more - 1 # 大于区域向左扩一个位置,more = 5,先将R[6]固定在最后一个位置上
swap(arr, index, more) # R[5] 与 左边第一个数交换,将R[6]数固定在最后一个位置上
swap(arr, more, R) # 将大于区域的第一个数和R(固定的数)位置上交换,接触R位置固定
return [less + 1, more] # 返回等于区域的位置,返回大小为2的数组,第一个数是等于区域最左边的数,第二个数是等于区域最右边的数
arr = [1,2,3,4,7,8,9]
print(bfprt(arr,0,len(arr)-1,1))