当前位置: 首页 > news >正文

常用算法模板函数(Python)

并查集模板

# # ==> 并查集模板(附优化)
# author: Geek0070@blog.geek007.top
class UnionFind():def __init__(self):self.roots = {}self.setCnt = 0   # 连通分量的个数# Union优化:存储根节点主导的集合的总节点数self.rootSizes = {}def add(self, x):if x not in self.roots:self.roots[x] = xself.rootSizes[x] = 1self.setCnt += 1def find(self, x):root = xwhile root != self.roots[root]:root = self.roots[root]# 优化:压缩路径while x != root:temp = self.roots[x]self.roots[x] = rootx = tempreturn rootdef union(self, x, y):rootx, rooty = self.find(x), self.find(y)if rootx != rooty:# 优化:小树合并到大树上if self.rootSizes[rootx] < self.rootSizes[rooty]:self.roots[rootx] = rootyself.rootSizes[rooty] += self.rootSizes[rootx]else:self.roots[rooty] = rootxself.rootSizes[rootx] += self.rootSizes[rooty]self.setCnt -= 1

二分查找(左闭右开)

# # ==> 二分法(左闭右开)
# author: Geek0070@blog.geek007.top
def lower_bound(nums,target):left,right=0,len(nums)while left<right:mid=(right-left)//2+leftif nums[mid]<target:left=mid+1   # 维持left的绝对合理性;# left的左边一定小于targetelse:right=mid   # 逼出while循环return left

二叉树遍历

二叉树中序遍历和前序遍历(栈)
# # ==> 二叉树的中/前序遍历(栈)
# author: Geek0070@blog.geek007.top
def inorder(root):stack=[]node=rootwhile stack or node:while node:print(node.val) # 前序遍历stack.append(node)node=node.leftnode=stack.pop()print(node.val) # 中序遍历node=node.right
二叉树后序遍历(栈)
# # ==> 二叉树的后序遍历(栈)
# author: Geek0070@blog.geek007.top
def afterOrder(root):result=[]stack=[]prev=Nonenode=rootwhile stack or node:while node:stack.append(node)node=node.leftnode=stack.pop()# 在中序遍历的基础上增加这部分。使用prev记录上一个访问的节点;如果node的右节点为空,那直接访问node节点即可,并更新prev和node,node更新为None是因为最终访问的是根节点,如果不置为空,将陷入死循环;如果不为空且右节点已经访问过(即等于prev),那么说明右节点已经全部访问了,那么访问node即可,并更新prev和node(同上);如果右节点不为空且没有访问过,则将node入栈,为了在右子树遍历完进行访问,并让node=node.right,让子树进行和node一样的操作if node.right is None or node.right==prev:result.append(node.val)prev=nodenode=None   # 方便下一次循环直接弹出当前节点的父节点,这个node会弹出两次,分为right==prev和right!=prevelse:stack.append(node)  # 该节点还没处理,先处理右节点,先回到栈中node=node.rightreturn result
二叉树的Morris算法前序遍历和中序遍历
# # ==> 二叉树的Morris算法前/中序遍历
# author: Geek0070@blog.geek007.top
def preOrderByMorris(root) -> list[int]:# 第一步,特殊情况处理。树为空的情况下if root is None:return []# 第二步,遍历树直到当前结点为空cur = rootwhile cur:if not cur.left:# 2.1.当前结点没有左子树,则更新当前结点为右结点(也只有这么走);并在更新之前访问当前结点print(cur.val)cur = cur.rightelse:# 2.2.当前结点有左子树的情况。找到左子树最右侧的结点rightMost;让rightMost的右指针线索指向当前结点并在更新后让当前结点继续往左走;如果rightMost已经指向了当前结点,说明处于回退阶段,左子树已经访问完成,断开rightMost的右线索并让当前结点往右走# 2.2.1.获取左子树的最右侧结点rightMost = cur.leftwhile rightMost.right is not None and rightMost.right is not cur:rightMost = rightMost.rightif rightMost.right is None:# 2.2.2.线索连接并更新当前结点往左走(前序遍历在构建线索时访问当前结点)print(cur.val)rightMost.right = curcur = cur.leftelse:# 2.2.3.线索断开并更新当前结点往右走(中序遍历在断开线索时访问当前结点)# print(cur.val)rightMost.right = Nonecur = cur.right

字典树/前缀树模板

# # ==> 字典树模板
# author: Geek0070@blog.geek007.top
class Trie:def __init__(self):self.sons={}     # 孩子节点self.end=False      # 是否是单词的尾节点# 往当前前缀树中插入单词def insert(self,word):cur=selffor c in word:if c not in cur.sons:cur.sons[c]=Trie()cur=cur.sons[c]cur.end=True# 查找单词。返回:0->不存在该单词;1->存在word的路径,但是不存在单词;2->存在word的单词def find(self,word):cur=selffor c in word:if c not in cur.sons:return 0cur=cur.sons[c]return 2 if cur.end else 1# 展现树;(非必要)def display(self):que=[self]print("->")while que:for i in range(len(que)):node=que.pop(0)for key,subNode in node.sons.items():print(key,end=" ")que.append(subNode)print("")print("=>")

辗转相除法求最大公约数

# # ==> 辗转相除法求最大公约数
# author: Geek0070@blog.geek007.top
def gcd(m,n):maxVal,minVal=max(m,n),min(m,n)r=maxVal%minValwhile r!=0:maxVal=minValminVal=rr=maxVal%minValreturn minVal

树状数组模板

树状数组(求区间和)
# ==> 树状数组(求区间和)
# author: Geek0070@blog.geek007.top
class TreeArr():def __init__(self, length):self.length=lengthself.arr=[0]*self.length# 工具函数:二进制从右向左第一个1和其右边的0组成的数字def lowerbit(self,x):return x&(-x)   # 计算机的负数采用的是补码(和取反加1效果一致)# 基础函数:原数组arr[index]增加val,更新树状数组def add(self, index:int, val:int) -> None:while index < self.length:self.arr[index] += valindex = index + self.lowerbit(index + 1)# 基础函数:arr[0]->arr[index]项前缀和def query(self, index:int) -> int:sum_ = 0while index >= 0:sum_ += self.arr[index]index -= self.lowerbit(index + 1)return sum_# 获取两个索引之间的和def sumRange(self, left: int, right: int) -> int:# 使用query的求前缀和函数进行实现pass
树状数组(求区间最大值)
# ==> 树状数组(求区间最大值)(需要维护原数组)
# author: Geek0070@blog.geek007.top
class MaxTreeArr():def __init__(self, n:int):self.n = nself.nums = [-inf] * nself.arr = [-inf] * ndef lowerbit(self, x:int) -> int:return x & (-x)# > 更新原数组的index下标处的元素值为max(nums[index],val),并更新树状数组中index的祖先结点def update(self, index:int, val:int) -> None:# 注意:这个地方对原数组的更新,一定要取最大值,否则可能导致覆盖最大值导致错误(跳过坑)self.nums[index] = max(self.nums[index], val)while index < self.n:self.arr[index] = max(self.arr[index], val)index += self.lowerbit(index + 1)# > 查找arr闭区间[left,right]之间的最大值def queryMax(self, left:int, right:int) -> int:ans = -infindex = rightwhile index >= left:# l维护index维护的区间的左端点下标;如果l>=left,说明index维护的区间在[left,right]闭区间中,所以可以算最大值,反之,只能从原数组nums中选择,并将index自减1l = index - self.lowerbit(index + 1) + 1if l >= left:ans = max(ans, self.arr[index])index = l - 1else:ans = max(ans, self.nums[index])index -= 1return ans

线段树模板

求区间最大值的线段树模板
# ==> 线段树(维护区间最大值)
# author: Geek0070@blog.geek007.top
class SegmentTreeToMax():def __init__(self, n:int):self.n = n  # 原数组的长度self.arr = [-inf] * (4 * n)# 基础函数:修改原数组中的index下标处的元素为val,并更新线段树def change(self, index:int, val:int, node:int, start:int, end:int) -> None:# 第一步,递归退出条件。到达index对应的叶结点if index == start and index == end:# > 防止后面的小元素替换大元素,所以加max(根据具体的修改场景可能会有修改)self.arr[node] = max(self.arr[node], val)return# 第二步,根据mid判断index是在当前node的左子树还是右子树上,并进行递归修改mid = (end - start) // 2 + startif index <= mid:# > index在node左子树的情况self.change(index, val, 2 * node + 1, start, mid)else:# > index在node的右子树的情况self.change(index, val, 2 * node + 2, mid + 1, end)# 第三步,更新当前结点,根据当前结点更新后的左右结点,更新当前结点的最大值self.arr[node] = max(self.arr[2 * node + 1], self.arr[2 * node + 2])# 基础函数:求区间范围内的最大值def rangeMax(self, left:int, right:int, node:int, start:int, end:int) -> int:# 第一步,递归退出条件。当node区间和[left,right]闭区间完全匹配时,递归退出if left == start and right == end:return self.arr[node]# 第二步,递归子问题mid = (end - start) // 2 + startif right <= mid:# > [left,right]区间完全在node结点的左子树区间上return self.rangeMax(left, right, 2 * node + 1, start, mid)elif left > mid:# > [left,right]区间完全在node结点的右子树区间上return self.rangeMax(left, right, 2 * node + 2, mid + 1, end)# > [left,right]区间部分在node的左子树上,部分在右子树上的情况return max(self.rangeMax(left, mid, 2 * node + 1, start, mid), self.rangeMax(mid + 1, right, 2 * node + 2, mid + 1, end))# 封装changedef update(self, index:int, val:int) -> None:return self.change(index, val, 0, 0, self.n - 1)# 封装rangeMaxdef getRangeMax(self, left:int, right:int) -> int:# 注意:left>right时,会导致内存溢出return self.rangeMax(left, right, 0, 0, self.n - 1)
带懒标记的线段树(求区间和)
# ==> 带懒标记 求范围和 的线段树
# author: Geek0070@blog.geek007.top
class SegmentTreeToSumWithLazy():def __init__(self, nums:list[int]):# 初始化线段树的存储数组并进行构建二叉平衡线段树(这里采用平衡二叉树,也可以用最大堆进行构建)self.n = len(nums)self.tree = [0] * (self.n * 4)   # 二叉平衡树的范围为4*n,如果使用最大堆的自底向上,则范围2*n即可;维护区间和self.lazy = [0] * (self.n * 4)  # 懒标记数组,lazy[node]表示node结点的子孙结点对应的区间待增加的值(不包括node结点)self.build(nums, 0, 0, self.n - 1)# 基础方法:构建树,在node树中,将nums[start:end+1]中的区间元素进行插入(node、start、end确定一个线段树结点)def build(self, nums:list[int], node:int, start:int, end:int) -> None:if start == end:self.tree[node] = nums[start]return mid = (end - start) // 2 + start# 构建左子树self.build(nums, node * 2 + 1, start, mid)# 构建右子树self.build(nums, node * 2 + 2, mid + 1, end)# 和形式的线段树;tree[node]记录nums[start:end+1]之间元素的和self.tree[node] = self.tree[node * 2 + 1] + self.tree[node * 2 + 2]# 结点懒标记向下推送(基础函数):将node结点的懒标记推送到左右子结点中,并将自身的懒标记清空def pushDown(self, node:int, start:int, end:int) -> None:# 第一步,特殊判断。node结点的懒标记值为0,无需向下推送,直接退出if self.lazy[node] == 0:return # 第二步,获取左右结点的结点编号和范围,更新左右结点的tree和lazy中的值mid = (end - start) // 2 + startleftChild = node * 2 + 1rightChild = node * 2 + 2self.tree[leftChild] += self.lazy[node] * (mid - start + 1)self.lazy[leftChild] += self.lazy[node]self.tree[rightChild] += self.lazy[node] * (end - mid)self.lazy[rightChild] += self.lazy[node]# 第三步,清空node对应的lazy值self.lazy[node] = 0# 区间修改(基础方法):在结点node对应的[start,end]区间中,将其中[left,right]区间部分的原数组值全部增加valuedef rangeUpdate(self, value:int, left:int, right:int, node:int, start:int, end:int) -> None:# 第一步,递归退出条件# 1.1.node结点区间[start,end]和目标区间[left,right]不存在交集时,直接退出if left > end or right < start:return # 1.2.node结点区间[start,end]被目标区间[left,right]包含,直接更新lazy和tree数组。tree这种node结点对应值自增;在lazy数组中更新node结点的懒标记,lazy[node]自增valueif left <= start and right >= end:self.tree[node] += value * (end - start + 1)self.lazy[node] += valuereturn# 第二步,node区间和目标区间存在重叠的情况下,递归处理# 2.1.由于需要分到两个子区间中进行递归操作,所以node对应的lazy值需要向下推送到子结点中;本质就是将node结点的lazy值分配到左右结点中,更新tree和lazy中对应的值self.pushDown(node, start, end)# 2.2.递归范围更新两个子树leftChild, rightChild = node * 2 + 1, node * 2 + 2mid = (end - start) // 2 + startself.rangeUpdate(value, left, right, leftChild, start, mid)self.rangeUpdate(value, left, right, rightChild, mid + 1, end)# 第三步,更新当前结点的tree值(node中增加value的范围不确定,所以通过子结点来更新)self.tree[node] = self.tree[leftChild] + self.tree[rightChild]# 单点修改:调用区间函数rangeUpdate进行def pointUpdate(self, index:int, value:int, node:int, start:int, end:int) -> None:self.rangeUpdate(value, index, index, node, start, end)# 区间查询(基础方法):在结点node对应的[start,end]区间中,求[left,right]区间部分的原数组值的和def rangeSum(self, left:int, right:int, node:int, start:int, end:int) -> int:# 第一步,递归退出条件# 1.1.node结点区间和目标区间[left,right]不存在交集时,直接退出if left > end or right < start:return 0# 1.2.node结点区间被目标区间[left,right]包含,直接返回tree数组中node结点的值if left <= start and right >= end:return self.tree[node]# 第二步,node区间和目标区间存在重叠的情况下,递归处理# 2.1.由于需要分到两个子区间中进行递归操作,所以node对应的lazy值需要向下推送到子结点中;本质就是将node结点的lazy值分配到左右结点中,更新tree和lazy中对应的值self.pushDown(node, start, end)# 2.2.递归获取两个子树的范围和,相加进行返回mid = (end - start) // 2 + startreturn self.rangeSum(left, right, node * 2 + 1, start, mid) + self.rangeSum(left, right, node * 2 + 2, mid + 1, end)
带懒标记的线段树(求区间最大值)
# ==> 带懒标记 求范围最大值 的线段树【未经过题目测验,用了一个数组实验了一下】
# author: Geek0070@blog.geek007.top
inf = float("inf")
class SegmentTreeToMaxWithLazy():def __init__(self, nums:list[int]):# 初始化线段树的存储数组并进行构建二叉平衡线段树(这里采用平衡二叉树,也可以用最大堆进行构建)self.n = len(nums)self.tree = [0] * (self.n * 4)   # 二叉平衡树的范围为4*n,如果使用最大堆的自底向上,则范围2*n即可;维护区间最大值self.lazy = [0] * (self.n * 4)  # 懒标记数组,lazy[node]表示node结点的子孙结点对应的区间待增加的值(不包括node结点)self.build(nums, 0, 0, self.n - 1)# 基础方法:构建树,在node树中,将nums[start:end+1]中的区间元素进行插入(node、start、end确定一个线段树结点)def build(self, nums:list[int], node:int, start:int, end:int) -> None:if start == end:self.tree[node] = nums[start]return mid = (end - start) // 2 + start# 构建左子树self.build(nums, node * 2 + 1, start, mid)# 构建右子树self.build(nums, node * 2 + 2, mid + 1, end)# 和形式的线段树;tree[node]记录nums[start:end+1]之间元素的最大值self.tree[node] = max(self.tree[node * 2 + 1], self.tree[node * 2 + 2])# 结点懒标记向下推送(基础函数):将node结点的懒标记推送到左右子结点中,并将自身的懒标记清空def pushDown(self, node:int, start:int, end:int) -> None:# 第一步,特殊判断。node结点的懒标记值为0,无需向下推送,直接退出if self.lazy[node] == 0:return # 第二步,获取左右结点的结点编号和范围,更新左右结点的tree和lazy中的值mid = (end - start) // 2 + startleftChild = node * 2 + 1rightChild = node * 2 + 2self.tree[leftChild] += self.lazy[node]self.lazy[leftChild] += self.lazy[node]self.tree[rightChild] += self.lazy[node]self.lazy[rightChild] += self.lazy[node]# 第三步,清空node对应的lazy值self.lazy[node] = 0# 区间修改(基础方法):在结点node对应的[start,end]区间中,将其中[left,right]区间部分的原数组值全部增加valuedef rangeUpdate(self, value:int, left:int, right:int, node:int, start:int, end:int) -> None:# 第一步,递归退出条件# 1.1.node结点区间[start,end]和目标区间[left,right]不存在交集时,直接退出if left > end or right < start:return # 1.2.node结点区间[start,end]被目标区间[left,right]包含,直接更新lazy和tree数组。tree这种node结点对应值自增;在lazy数组中更新node结点的懒标记,lazy[node]自增valueif left <= start and right >= end:self.tree[node] += valueself.lazy[node] += valuereturn# 第二步,node区间和目标区间存在重叠的情况下,递归处理# 2.1.由于需要分到两个子区间中进行递归操作,所以node对应的lazy值需要向下推送到子结点中;本质就是将node结点的lazy值分配到左右结点中,更新tree和lazy中对应的值self.pushDown(node, start, end)# 2.2.递归范围更新两个子树leftChild, rightChild = node * 2 + 1, node * 2 + 2mid = (end - start) // 2 + startself.rangeUpdate(value, left, right, leftChild, start, mid)self.rangeUpdate(value, left, right, rightChild, mid + 1, end)# 第三步,更新当前结点的tree值(node中增加value的范围不确定,所以通过子结点来更新)self.tree[node] = max(self.tree[leftChild], self.tree[rightChild])# 单点修改:调用区间函数rangeUpdate进行def pointUpdate(self, index:int, value:int, node:int, start:int, end:int) -> None:self.rangeUpdate(value, index, index, node, start, end)# 区间查询(基础方法):在结点node对应的[start,end]区间中,求[left,right]区间部分的原数组值的最大值def rangeMax(self, left:int, right:int, node:int, start:int, end:int) -> int:# 第一步,递归退出条件# 1.1.node结点区间和目标区间[left,right]不存在交集时,直接退出if left > end or right < start:return -inf# 1.2.node结点区间被目标区间[left,right]包含,直接返回tree数组中node结点的值if left <= start and right >= end:return self.tree[node]# 第二步,node区间和目标区间存在重叠的情况下,递归处理# 2.1.由于需要分到两个子区间中进行递归操作,所以node对应的lazy值需要向下推送到子结点中;本质就是将node结点的lazy值分配到左右结点中,更新tree和lazy中对应的值self.pushDown(node, start, end)# 2.2.递归获取两个子树的范围最大值,取最大值进行返回mid = (end - start) // 2 + startreturn max(self.rangeMax(left, right, node * 2 + 1, start, mid), self.rangeMax(left, right, node * 2 + 2, mid + 1, end))

拓扑排序算法

# author: Geek0070@blog.geek007.top
from typing import Dict,List
from collections import deque
# ==> 通过List[List]结构的邻接表图和List形式的入度信息进行拓扑排序,返回拓扑排序序列,如果不存在,则返回空列表
def topoSort(adjListGraph:List[List[int]],inDegreeList:List[int]):que=deque()length=len(adjListGraph)for node in range(length):inDegree=inDegreeList[node]if inDegree==0:que.append(node)result=[]while que:node=que.popleft()result.append(node)for subNode in adjListGraph[node]:inDegreeList[subNode]-=1if inDegreeList[subNode]==0:que.append(subNode)return result if len(result)==len(adjListGraph) else []

最小生成树算法

prim算法
# author: Geek0070@blog.geek007.top
import heapq
from typing import Dict,List
# ==> Prim算法模板:用于计算无向图的最小生成树及最小权值和
# graph:无向图的邻接表;item项例子:{节点:[[相邻边的权值,相邻边对面的节点],...],...}
# return:minWeightsSum为最小生成树的权值和;treeEdges为一个合法的最小生成树的边的列表(列表项:[节点,对面节点,两点之间的边的权值]);visited为最小生成树的节点,可以用来判断图中是否存在最小生成树
def primMinSpanningTree(graph:Dict[object,List[List]]):minWeightsSum,treeEdges=0,[]firstNode=list(graph.keys())[0] # *# 记录已经加入最小生成树的节点visited=set([firstNode])# 选择从firstNode开始,相邻的边加到堆中neighEdgesHeap=[item+[firstNode] for item in graph[firstNode]]heapq.heapify(neighEdgesHeap)while neighEdgesHeap:weight,node,startNode=heapq.heappop(neighEdgesHeap) # node2为node的weight权值对应的边的对面的节点if node in visited:    # 这个地方必须进行判断,否则会造成重复添加已访问节点,造成最小权值和偏大(因为前面遍历的节点可能将未遍历的共同相邻节点重复添加到堆中)continueminWeightsSum+=weighttreeEdges.append([startNode,node,weight])visited.add(node)# 遍历新访问的节点的边,加入堆中for nextWeight,nextNode in graph[node]:if nextNode not in visited:heapq.heappush(neighEdgesHeap,[nextWeight,nextNode,node])return minWeightsSum,treeEdges,visited

单源最短路径算法

dijkstra算法
# author: Geek0070@blog.geek007.top
# ==> dijkstra求单源最短路径
# params:graph为邻接表,邻接表结构为graph[FromNode_i]=[[Dist1,ToNode1],[Dist2,ToNode2],...],三维数组;startNode为源节点
# return:dists为各个节点的最短路径的长度;pathsPrevs为各个节点的最短路径的前驱节点
# waring: *处修改可以改成求最大路径
# 举例:获取最后一个节点的最短路径
# paths=[]
# node=length-1
# while node!=-1:# paths.append(node)# node=pathsPrevs[node]
from typing import List
import heapq
inf=float("inf")
def dijkstraMinDist(graph:List[List[List]],startNode:int):length=len(graph)# 第一步,构建距离数组、前驱节点数组、距离最小堆。并初始化startNode的最小距离为0distances=[inf]*length  # *各个节点到startNode的最小距离distances[startNode]=0  # *初始化startNode到startNode的最小距离0pathsPrevs=[-1]*length      # 最短路径的最后节点的前驱节点distsHeap=[[0,startNode]]   # *距离优先队列,项的结构为[距离startNode的距离,node]# 第二步,遍历。每次从最小堆中弹出一个(直到堆为空);在弹出的节点距离小于或等于distances数组中的对应节点的距离的情况下,遍历节点的子节点,如果到达子节点的距离小于原先距离,更新距离数组、前驱节点数组、距离最小堆while distsHeap:dist,node=heapq.heappop(distsHeap)if dist>distances[node]:    # *排除同一个continuefor edgeWeight,subNode in graph[node]:thisDist=edgeWeight+distances[node]if thisDist<distances[subNode]: # *distances[subNode]=thisDistpathsPrevs[subNode]=nodeheapq.heappush(distsHeap,[thisDist,subNode])    # *return distances,pathsPrevs
spfa算法
# author: Geek0070@blog.geek007.top
from collections import deque
# ==> 求单源最短路径,可以包含负权边(不能有负环)
# graph:[(结点,有向边权重)]
def spfa(graph:list[list[int]], start:int, end:int):inf = float("inf")n = len(graph)# 第一步,构建维护变量。que队列用来维护当前层的结点;visited用来记录各个结点当前是否在队列中,如果当前结点已经在队列中了,那么就不用重复入队了;dists数组用来维护各个结点到起始点的最短距离,初始化为inf,start结点初始化为0;paths用来记录最短路径上各个结点的前驱结点,初始化为None,start结点初始化为-1;cnt用来维护每个结点入队的次数,用来验证是否存在负环,如果存在某个结点入队次数超过n,那么说明存在负环que = deque([start])visited = [False] * nvisited[start] = Truedists = [inf] * ndists[start] = 0paths = [None] * npaths[start] = -1cnt = [0] * ncnt[0] = 1# 第二步,循环从队列中取出结点,类似广度优先搜索。一层层的从队列中弹出元素,并遍历相邻结点;先根据相邻结点的新最短距离来胡决定更新更近的相邻结点;相邻结点距离更近的情况下将没有在当前队列中的相邻结点添加到队列中,继续进行遍历(两个if的关键点),并更新入队次数和判断是否为负环while que:for _ in range(len(que)):node = que.popleft()visited[node] = Falsefor neighNode, weight in graph[node]:if dists[neighNode] > dists[node] + weight:dists[neighNode] = dists[node] + weightpaths[neighNode] = nodeif not visited[neighNode]:que.append(neighNode)visited[neighNode] = Truecnt[neighNode] += 1if cnt[neighNode] > n:raise Exception("存在负环,请检查图是否有错误!")# print(dists)# print(paths)return dists, paths

二分图最大匹配数算法

匈牙利算法
# author: Geek0070@blog.geek007.top
# ==> 匈牙利算法(时间复杂度:O(V*E))
# params:graph:二分图的邻接表;leftNodes:二分图左集合的所有结点数组
# return:返回二分图中的最大匹配数(等于最小点覆盖数)
from collections import defaultdict
def hungarian(graph:dict[int,list[int]], leftNodes:list[int]) -> int:# 第一步,构建维护变量。rightMatch维护右集合中各个结点的匹配状态rightMatch = {}# 第二步,构建dfs递归函数。递归任务:判断左集合中结点u是否能成功匹配。如果u相邻结点v没有匹配,则直接匹配;如果v已经匹配,则将v标记为已访问,dfs(match[v])观察是否能让match[v]结点匹配非v结点来释放v结点,让u进行匹配,即贪心地进行匹配。def dfs(u:int) -> bool:# 2.1.枚举u的相邻的右集合结点vfor v in graph[u]:# 2.2.判断v是否已访问,已访问则跳过if not visited[v]:# 2.3.标记v为已访问,并根据条件判断是否能匹配,能匹配的情况下记录右结点v的匹配状态到rightMatch中visited[v] = Trueif v not in rightMatch or dfs(rightMatch[v]):rightMatch[v] = ureturn True# 2.4.如果最终都没能匹配一个,则返回Falsereturn False# 第三步,枚举每一个左集合结点u,调用dfs判断各个u结点是否能成功进行匹配,并记录最大匹配数到ans中ans = 0for u in leftNodes:visited = defaultdict(bool)if dfs(u):ans += 1return ans
Hopcroft-karp算法
# author: Geek0070@blog.geek007.top
# ==> Hopcroft-karp算法(时间复杂度:O(sqrt(V)*E))
# params:graph:二分图的邻接表;leftNodes:二分图左集合的所有结点数组;
# return:返回二分图中的最大匹配数(等于最小点覆盖数)
from collections import deque
def hopcroftKarp(graph:list[list[int]], leftNodes:list[int]):inf = float("inf")# 第一步,构建维护变量。match_维护二分图中各个匹配关系;dist维护各个交替路中距离左集合中未匹配点的最短距离,dist中映射值为inf代表结点已经匹配或者不存在以该结点开头的增广路径match_ = {}dist = {}# 第二步,构建广搜函数。bfs功能:返回在match_和dist的情况下,是否还存在更多的增广路径,同时更新distdef bfs():# 2.1.将左侧集合的未匹配结点全部加入到队列中,作为BFS的起点;同时将左集合中已经匹配的结点的距离值设置为infqueue = deque()for u in leftNodes:if u not in match_:dist[u] = 0queue.append(u)else:dist[u] = inf# 2.2.广搜二分图。使用found维护图中是否还存在增广路径found = Falsewhile queue:u = queue.popleft()for v in graph[u]:if v not in match_:# 2.2.1.如果右侧结点v没有在match_中,说明v结点还没有匹配,表示图中还存在增广路径,设置found=Truefound = Trueelif dist[match_[v]] == inf:# 2.2.2.如果右侧结点v在match_中且对应的match_[v]结点还没有被广搜到(即dist[match_[v]==inf]),说明v结点已经匹配,则将v的匹配结点match_[v]加入到广搜队列中,继续寻找增广路径;并更新match_[v]结点所在的最小广搜层次queue.append(match_[v])dist[match_[v]] = dist[u] + 1# 2.3.返回图中是否还存在增广路径return found# 第三步,构建受限深搜函数。深搜任务:判断以结点u开头是否存在增广路径;如果存在,则在该路径上进行结点匹配,反转该增广路径。def dfs(u):for v in graph.get(u, []):if v not in match_ or (dist.get(match_[v], inf) == dist[u] + 1 and dfs(match_[v])):# 3.1.如果结点v没有匹配,则u->v就是最短的增广路径,直接匹配u和v并返回即可;如果结点v已经匹配了,只有满足match_[v]是广搜中结点u的下一层级时(即dist[match_[v]]==dist[u]+1)(这个条件也能过滤掉递归时match_[v]遍历到相邻结点v的情况),才能继续dfs判断是否存在增广路径,如果存在则匹配并反转该增广路径match_[v] = umatch_[u] = vreturn True# 3.2.如果不存在以u开头的增广路径,则在dist中进行标记,并返回Falsedist[u] = infreturn False# 第四步,循环地进行bfs判断图中是否存在增广路径;如果存在则遍历所有左侧没有匹配的结点u,dfs寻找是否存在u开头的最短增广路径,并在dfs中更新match_和dist,如果存在以u开头的增广路径,则通过反转增广路径进行匹配,并将ans自增1ans = 0while bfs():for u in leftNodes:if u not in match_:if dfs(u):ans += 1return ans

懒删除小根堆模板

# author: Geek0070@blog.geek007.top
# ==> 模板:懒删除小根堆
from collections import defaultdict
from heapq import heappush, heappop, heappushpop
class LazyMinHeap:def __init__(self):# 第一步,构建维护变量。heap维护最小堆;size维护最小堆中元素的个数;sum维护堆中所有元素的和;cnts维护堆中各个有效元素的频数;delayDelete维护待删除的各个元素的个数;self.heap = []self.size = 0self.sum = 0self.cnts = defaultdict(int)self.delayDelete = defaultdict(int)# 修剪堆顶部待删除的元素def prune(self):while self.heap:topVal = self.heap[0]if self.delayDelete[topVal] > 0:heappop(self.heap)self.delayDelete[topVal] -= 1else:break# 懒删除堆中的元素;这里必须保证val在堆中def delete(self, val:int):if self.cnts[val] > 0:self.cnts[val] -= 1self.delayDelete[val] += 1self.size -= 1self.sum -= valreturn Truereturn False# 判断堆是否为空def empty(self):self.prune()return len(self.heap) == 0# 获取顶部元素def top(self):if self.empty():returnreturn self.heap[0]# 堆中添加元素def push(self, val:int):if self.delayDelete[val] > 0:self.delayDelete[val] -= 1else:heappush(self.heap, val)self.cnts[val] += 1self.sum += valself.size += 1# 堆顶弹出元素def pop(self):if self.empty():returnval = heappop(self.heap)self.cnts[val] -= 1self.size -= 1self.sum -= valreturn valdef __str__(self):return f"size={self.size}\nsum={self.sum}\ndelayDelete={self.delayDelete}\ncnts={self.cnts}\nheap={self.heap}\n"

相关文章:

  • 用Python玩转人工智能——手搓图像分类模型
  • 【PhysUnits】13 改进减法(sub.rs)
  • 【加密算法】
  • 从“被动养老”到“主动健康管理”:平台如何重构代际关系?
  • Odoo 条码功能全面深度解析(VIP15万字版)
  • LiveNVR :实现非国标流转国标流的全方位解决方案
  • 勾股数的性质和应用
  • 接地气的方式认识JVM(一)
  • 通过teamcity cloud创建你的一个build
  • 【C语言】详解 指针
  • Java开发之定时器学习
  • 欧拉角转为旋转矩阵
  • 二叉树的锯齿形层序遍历——灵活跳跃的层次结构解析
  • w~视觉~合集6
  • 自我觉察是成长的第一步,如何构建内心的平静
  • 【线程与进程区别】
  • Spring AI框架快速入门
  • 华为OD机试真题——最佳的出牌方法(2025A卷:200分)Java/python/JavaScript/C/C++/GO最佳实现
  • SAR ADC 比较器的offset 校正
  • 加密协议知多少
  • 哪些网站做品牌特卖/东营网站推广公司
  • 西安网站建设罗鑫/网站推广营销的步骤
  • 哪些网站建设公司/网络推广引流有哪些渠道
  • 自己做视频直播网站/企业网址怎么注册
  • 在相亲网站做红娘/如何建网站不花钱
  • 技术支持 沧州网站建设/seo自动工具