图:邻接矩阵和邻接表
parent
43b19abc51
commit
6d113fa237
|
@ -0,0 +1,155 @@
|
||||||
|
"""
|
||||||
|
无向图:边表示两顶点之间的双向链接关系,如 A---B,即仅通过一条边,A 可达到 B,B 也可达到 A
|
||||||
|
有向图:边具有方向性,如 A --> B 或 B <-- A,即通过一条边,只能由 A 向 B 或 B 向 A
|
||||||
|
|
||||||
|
连通图:从某个顶点出发,可以到达其余任意顶点。
|
||||||
|
非连通图:从某个顶点出发,至少有一个顶点无法到达。
|
||||||
|
|
||||||
|
邻接(adjacency):当两顶点之间存在边相连时,称这两顶点“邻接“
|
||||||
|
路径(path):从顶点 A 到顶点 B 经过的边构成的序列被称为从 A 到 B 的“路径
|
||||||
|
度(degree):一个顶点拥有的边数
|
||||||
|
|
||||||
|
有权图:每条边都具有一个值(权重),表示从一个顶点到另一个顶点之间的代价/开销等....
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
邻接矩阵:设图的顶点数量为 n,邻接矩阵使用一个 n*n大小的矩阵来表示图,行(列)表示一个顶点
|
||||||
|
矩阵元素代表边,用1或0表示两个顶点之间是否存在边,如:
|
||||||
|
|
||||||
|
1 3 5 6 顶点
|
||||||
|
1 0 1 1 0
|
||||||
|
3 1 0 1 0
|
||||||
|
5 1 1 0 1
|
||||||
|
6 0 1 0 0
|
||||||
|
|
||||||
|
注意这里表示的是无向图,关于矩阵关于主对角线对称
|
||||||
|
|
||||||
|
将 1 和 0 替换为其他的值,则可表示有权图
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 实现一个邻接矩阵
|
||||||
|
|
||||||
|
class GraphAdjMat:
|
||||||
|
def __init__(self, vertices: list[int], edges: list[list[int]]):
|
||||||
|
self.vertices: list[int] = []
|
||||||
|
self.adj_mat: list[list[int]] = []
|
||||||
|
# 添加顶点
|
||||||
|
for val in vertices:
|
||||||
|
self.add_vertex(val)
|
||||||
|
|
||||||
|
for e in edges:
|
||||||
|
self.add_edge(e[0], e[1])
|
||||||
|
|
||||||
|
def size(self) -> int:
|
||||||
|
return len(self.vertices)
|
||||||
|
|
||||||
|
def add_vertex(self, val: int):
|
||||||
|
n = self.size()
|
||||||
|
self.vertices.append(val)
|
||||||
|
# 在邻接矩阵中添加一行
|
||||||
|
new_row = [0] * n
|
||||||
|
self.adj_mat.append(new_row)
|
||||||
|
# 向邻接矩阵中添加一列
|
||||||
|
for row in self.adj_mat:
|
||||||
|
row.append(0)
|
||||||
|
|
||||||
|
def add_edge(self, i:int, j:int):
|
||||||
|
# 参数 i, j 对应 vertices 元素索引
|
||||||
|
if i < 0 or j < 0 or i >= self.size() or j >= self.size():
|
||||||
|
raise IndexError()
|
||||||
|
|
||||||
|
# 无向图中,邻接矩阵关于主对角线对称
|
||||||
|
self.adj_mat[i][j] = 1
|
||||||
|
self.adj_mat[j][i] = 1
|
||||||
|
|
||||||
|
def remove_vertex(self, index: int):
|
||||||
|
if index >= self.size():
|
||||||
|
raise IndexError()
|
||||||
|
|
||||||
|
self.vertices.pop(index)
|
||||||
|
# 删除对应索引的行
|
||||||
|
self.adj_mat.pop(index)
|
||||||
|
# 删除对应索引的列
|
||||||
|
for row in self.adj_mat:
|
||||||
|
row.pop(index)
|
||||||
|
|
||||||
|
def remove_edge(self, i: int, j: int):
|
||||||
|
if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j:
|
||||||
|
raise IndexError()
|
||||||
|
self.adj_mat[i][j] = 0
|
||||||
|
self.adj_mat[j][i] = 0
|
||||||
|
|
||||||
|
"""
|
||||||
|
设无向图的顶点总数为 n、边总数为 m,则:
|
||||||
|
|
||||||
|
添加边:在顶点对应链表的末尾添加边即可,因为是无向图,所以需要同时添加两个方向的边。
|
||||||
|
删除边:在顶点对应链表中查找并删除指定边,在无向图中,需要同时删除两个方向的边
|
||||||
|
添加顶点:在邻接表中添加一个链表,并将新增顶点作为链表头节点
|
||||||
|
删除顶点:需遍历整个邻接表,删除包含指定顶点的所有边
|
||||||
|
初始化:在邻接表中创建 n 个顶点和 2m 条边
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Vertex:
|
||||||
|
"""顶点类"""
|
||||||
|
def __init__(self, val: int):
|
||||||
|
self.val = val
|
||||||
|
|
||||||
|
def vals_to_vets(vals: list[int]) -> list["Vertex"]:
|
||||||
|
"""输入值列表 vals ,返回顶点列表 vets"""
|
||||||
|
return [Vertex(val) for val in vals]
|
||||||
|
|
||||||
|
|
||||||
|
def vets_to_vals(vets: list["Vertex"]) -> list[int]:
|
||||||
|
"""输入顶点列表 vets ,返回值列表 vals"""
|
||||||
|
return [vet.val for vet in vets]
|
||||||
|
|
||||||
|
|
||||||
|
class GraphAdjList:
|
||||||
|
def __init__(self, edges: list[list[Vertex]]):
|
||||||
|
self.adj_list = dict[Vertex, list[Vertex]]()
|
||||||
|
for edge in edges:
|
||||||
|
self.add_vertex(edge[0])
|
||||||
|
self.add_vertex(edge[1])
|
||||||
|
self.add_edge(edge[0], edge[1])
|
||||||
|
|
||||||
|
def size(self):
|
||||||
|
return len(self.adj_list)
|
||||||
|
|
||||||
|
def add_edge(self, vet1: Vertex, vet2: Vertex):
|
||||||
|
if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2:
|
||||||
|
raise ValueError()
|
||||||
|
|
||||||
|
# 无向表,两边都需添加
|
||||||
|
self.adj_list[vet1].append(vet2)
|
||||||
|
self.adj_list[vet2].append(vet1)
|
||||||
|
|
||||||
|
def remove_edge(self, vet1: Vertex, vet2: Vertex):
|
||||||
|
if vet1 not in self.adj_list or vet2 not in self.adj_list:
|
||||||
|
raise ValueError()
|
||||||
|
|
||||||
|
self.adj_list[vet1].remove(vet2)
|
||||||
|
self.adj_list[vet2].remove(vet1)
|
||||||
|
|
||||||
|
def add_vertex(self, vet: Vertex):
|
||||||
|
"""添加顶点"""
|
||||||
|
if vet in self.adj_list:
|
||||||
|
return
|
||||||
|
# 在邻接表中添加一个新链表
|
||||||
|
self.adj_list[vet] = []
|
||||||
|
|
||||||
|
def remove_vertex(self, vet: Vertex):
|
||||||
|
"""删除顶点"""
|
||||||
|
if vet not in self.adj_list:
|
||||||
|
raise ValueError()
|
||||||
|
# 在邻接表中删除顶点 vet 对应的链表
|
||||||
|
self.adj_list.pop(vet)
|
||||||
|
# 遍历其他顶点的链表,删除所有包含 vet 的边
|
||||||
|
for vertex in self.adj_list:
|
||||||
|
if vet in self.adj_list[vertex]:
|
||||||
|
self.adj_list[vertex].remove(vet)
|
||||||
|
|
||||||
|
def print(self):
|
||||||
|
print("邻接表 =")
|
||||||
|
for vertex in self.adj_list:
|
||||||
|
tmp = [v.val for v in self.adj_list[vertex]]
|
||||||
|
print(f'{vertex.val}: {tmp}')
|
|
@ -0,0 +1,72 @@
|
||||||
|
"""
|
||||||
|
广度优先遍历:
|
||||||
|
|
||||||
|
广度优先遍历是一种由近及远的遍历方式,从某个节点出发,始终优先访问距离最近的顶点,
|
||||||
|
并一层层向外扩张。
|
||||||
|
|
||||||
|
BFS 通常借助队列来实现,代码如下所示。队列具有“先入先出”的性质,
|
||||||
|
这与 BFS 的“由近及远”的思想异曲同工。
|
||||||
|
|
||||||
|
1. 将遍历起始顶点 startVet 加入队列,并开启循环
|
||||||
|
2. 在循环的每轮迭代中,弹出队首顶点并记录访问,然后将该顶点的所有邻接顶点加入到队列尾部。
|
||||||
|
3. 循环步骤 2. ,直到所有顶点被访问完毕后结束。
|
||||||
|
|
||||||
|
时间复杂度:
|
||||||
|
|
||||||
|
所有顶点都会入队并出队一次,使用 O(V) 时间;在遍历邻接顶点的过程中,由于是无向图,
|
||||||
|
因此所有边都会被访问2次,使用2O(E)时间;总体使用O(V+E)时间。
|
||||||
|
"""
|
||||||
|
from collections import deque
|
||||||
|
|
||||||
|
from .graph import GraphAdjList, Vertex
|
||||||
|
|
||||||
|
|
||||||
|
def graph_bfs(graph: GraphAdjList, start_vet: Vertex):
|
||||||
|
res = []
|
||||||
|
visited = set[Vertex]([start_vet])
|
||||||
|
que = deque[Vertex]([start_vet])
|
||||||
|
while len(que) > 0:
|
||||||
|
vet = que.popleft()
|
||||||
|
res.append(vet)
|
||||||
|
for adj_vet in graph.adj_list[vet]:
|
||||||
|
if adj_vet in visited:
|
||||||
|
continue
|
||||||
|
que.append(adj_vet)
|
||||||
|
visited.add(adj_vet)
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
深度优先遍历: 深度优先遍历是一种优先走到底、无路可走再回头的遍历方式。如,从左上角顶点出发,访问当前顶点的某个邻接顶点,
|
||||||
|
直到走到尽头时返回,再继续走到尽头并返回,以此类推,直至所有顶点遍历完成。
|
||||||
|
|
||||||
|
这种“走到尽头再返回”的算法范式通常基于递归来实现。
|
||||||
|
|
||||||
|
深度优先遍历的算法流程如图 9-12 所示。
|
||||||
|
|
||||||
|
直虚线代表向下递推,表示开启了一个新的递归方法来访问新顶点。
|
||||||
|
曲虚线代表向上回溯,表示此递归方法已经返回,回溯到了开启此方法的位置。
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def dfs(graph: GraphAdjList, visited: set[Vertex], res: list[Vertex], vet: Vertex):
|
||||||
|
"""深度优先遍历 DFS 辅助函数"""
|
||||||
|
res.append(vet) # 记录访问顶点
|
||||||
|
visited.add(vet) # 标记该顶点已被访问
|
||||||
|
# 遍历该顶点的所有邻接顶点
|
||||||
|
for adjVet in graph.adj_list[vet]:
|
||||||
|
if adjVet in visited:
|
||||||
|
continue # 跳过已被访问的顶点
|
||||||
|
# 递归访问邻接顶点
|
||||||
|
dfs(graph, visited, res, adjVet)
|
||||||
|
|
||||||
|
|
||||||
|
def graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]:
|
||||||
|
"""深度优先遍历 DFS"""
|
||||||
|
# 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点
|
||||||
|
# 顶点遍历序列
|
||||||
|
res = []
|
||||||
|
# 哈希表,用于记录已被访问过的顶点
|
||||||
|
visited = set[Vertex]()
|
||||||
|
dfs(graph, visited, res, start_vet)
|
||||||
|
return res
|
|
@ -63,7 +63,7 @@ class TreeNode:
|
||||||
child = node.right
|
child = node.right
|
||||||
grand_child = child.left
|
grand_child = child.left
|
||||||
|
|
||||||
# 以 child 为原点,将 node 向右旋转
|
# 以 child 为原点,将 node 向左旋转
|
||||||
child.left = node
|
child.left = node
|
||||||
node.right = grand_child
|
node.right = grand_child
|
||||||
# 更新节点高度
|
# 更新节点高度
|
||||||
|
@ -102,16 +102,20 @@ class TreeNode:
|
||||||
|
|
||||||
# 查找插入位置:
|
# 查找插入位置:
|
||||||
if val < node.val:
|
if val < node.val:
|
||||||
|
# 递归左子树插入
|
||||||
|
# 接收旋转之后返回的根节点,并将其设为左子节点
|
||||||
node.left = self.insert_helper(node.left, val)
|
node.left = self.insert_helper(node.left, val)
|
||||||
elif val > node.val:
|
elif val > node.val:
|
||||||
|
# 递归右子树插入
|
||||||
|
# 接收旋转之后返回的根节点,并将其设为左子节点
|
||||||
node.right = self.insert_helper(node.right, val)
|
node.right = self.insert_helper(node.right, val)
|
||||||
else:
|
else:
|
||||||
# 重复节点不插入
|
# 重复节点不插入
|
||||||
return node
|
return node
|
||||||
|
|
||||||
# 更新节点高度
|
# 插入之后,更新节点高度
|
||||||
self.update_height(node)
|
self.update_height(node)
|
||||||
|
# 返回旋转之后的节点,如果未发生旋转则还是 node,如果发生了旋转,则是 node 的其中一个子节点
|
||||||
return self.rotate(node)
|
return self.rotate(node)
|
||||||
|
|
||||||
def remove(self, val: int):
|
def remove(self, val: int):
|
||||||
|
|
Loading…
Reference in New Issue