图:邻接矩阵和邻接表

main
3wish 2023-12-21 18:00:10 +08:00
parent 43b19abc51
commit 6d113fa237
3 changed files with 234 additions and 3 deletions

155
graph/graph.py 100644
View File

@ -0,0 +1,155 @@
"""
无向图边表示两顶点之间的双向链接关系 A---B即仅通过一条边A 可达到 BB 也可达到 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}')

View File

@ -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

View File

@ -63,7 +63,7 @@ class TreeNode:
child = node.right
grand_child = child.left
# 以 child 为原点,将 node 向旋转
# 以 child 为原点,将 node 向旋转
child.left = node
node.right = grand_child
# 更新节点高度
@ -102,16 +102,20 @@ class TreeNode:
# 查找插入位置:
if val < node.val:
# 递归左子树插入
# 接收旋转之后返回的根节点,并将其设为左子节点
node.left = self.insert_helper(node.left, val)
elif val > node.val:
# 递归右子树插入
# 接收旋转之后返回的根节点,并将其设为左子节点
node.right = self.insert_helper(node.right, val)
else:
# 重复节点不插入
return node
# 更新节点高度
# 插入之后,更新节点高度
self.update_height(node)
# 返回旋转之后的节点,如果未发生旋转则还是 node如果发生了旋转则是 node 的其中一个子节点
return self.rotate(node)
def remove(self, val: int):