python_15_KMP、第k小的数和bfprt算法

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算法

  1. 随机选一个数M
  2. 荷兰国旗问题
  3. 如果命中了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)

  1. 随机选一个数M(只有这个步骤更改了)
  2. 荷兰国旗问题
  3. 如果命中了k,返回index左侧或右侧,只返回一侧

快排是两边都要递归
那么,如何将M精挑细选出来?
在这里插入图片描述
在这里插入图片描述

  1. 5个数划分为一组 O(1)
  2. 每一个小组内排序(N/5) ,只有5个数之间排序 ,跨组不排序 O(N)
  3. 递归调用bfprt,marr中位数数组传到bfprt,bfprt(marr),所有数组求完,递归完,得到一个精确数字M
  4. 选出M数字后,进行荷兰国旗操作
  5. 左右两侧选一侧取递归

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))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值