From 1f4dba4845249aee9367d6efd62ba71cb3e7d1d3 Mon Sep 17 00:00:00 2001 From: Yudong Jin Date: Thu, 23 Feb 2023 20:00:40 +0800 Subject: [PATCH] Add the Python codes for the chapter of Graph and Heap (#382) --- codes/java/include/Vertex.java | 4 +- .../chapter_graph/graph_adjacency_list.py | 104 +++++++++++++ .../chapter_graph/graph_adjacency_matrix.py | 116 ++++++++++++++ codes/python/chapter_graph/graph_bfs.py | 48 ++++++ codes/python/chapter_graph/graph_dfs.py | 48 ++++++ codes/python/chapter_heap/heap.py | 62 ++++++++ codes/python/chapter_heap/my_heap.py | 144 ++++++++++++++++++ codes/python/include/__init__.py | 4 +- codes/python/include/print_util.py | 10 +- codes/python/include/vertex.py | 18 +++ 10 files changed, 553 insertions(+), 5 deletions(-) create mode 100644 codes/python/chapter_graph/graph_adjacency_list.py create mode 100644 codes/python/chapter_graph/graph_adjacency_matrix.py create mode 100644 codes/python/chapter_graph/graph_bfs.py create mode 100644 codes/python/chapter_graph/graph_dfs.py create mode 100644 codes/python/chapter_heap/heap.py create mode 100644 codes/python/chapter_heap/my_heap.py create mode 100644 codes/python/include/vertex.py diff --git a/codes/java/include/Vertex.java b/codes/java/include/Vertex.java index 80c59cfd8..ade707d82 100644 --- a/codes/java/include/Vertex.java +++ b/codes/java/include/Vertex.java @@ -1,6 +1,6 @@ /** - * File: TreeNode.java - * Created Time: 2022-11-25 + * File: Vertex.java + * Created Time: 2023-02-15 * Author: Krahets (krahets@163.com) */ diff --git a/codes/python/chapter_graph/graph_adjacency_list.py b/codes/python/chapter_graph/graph_adjacency_list.py new file mode 100644 index 000000000..eed5f98d8 --- /dev/null +++ b/codes/python/chapter_graph/graph_adjacency_list.py @@ -0,0 +1,104 @@ +""" +File: graph_adjacency_list.py +Created Time: 2023-02-23 +Author: Krahets (krahets@163.com) +""" + +import sys, os.path as osp +sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) +from include import * + + +""" 基于邻接表实现的无向图类 """ +class GraphAdjList: + # 邻接表,key: 顶点,value:该顶点的所有邻接结点 + adj_list = {} + + """ 构造方法 """ + def __init__(self, edges: List[List[Vertex]]) -> None: + self.adj_list = {} + # 添加所有顶点和边 + for edge in edges: + self.add_vertex(edge[0]) + self.add_vertex(edge[1]) + self.add_edge(edge[0], edge[1]) + + """ 获取顶点数量 """ + def size(self) -> int: + return len(self.adj_list) + + """ 添加边 """ + def add_edge(self, vet1: Vertex, vet2: Vertex) -> None: + if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2: + raise ValueError + # 添加边 vet1 - vet2 + self.adj_list[vet1].append(vet2) + self.adj_list[vet2].append(vet1) + + """ 删除边 """ + def remove_edge(self, vet1: Vertex, vet2: Vertex) -> None: + if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2: + raise ValueError + # 删除边 vet1 - vet2 + self.adj_list[vet1].remove(vet2) + self.adj_list[vet2].remove(vet1) + + """ 添加顶点 """ + def add_vertex(self, vet: Vertex) -> None: + if vet in self.adj_list: + return + # 在邻接表中添加一个新链表 + self.adj_list[vet] = [] + + """ 删除顶点 """ + def remove_vertex(self, vet: Vertex) -> None: + 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) -> None: + print("邻接表 =") + for vertex in self.adj_list: + tmp = [v.val for v in self.adj_list[vertex]] + print(f"{vertex.val}: {tmp},") + + +""" Driver Code """ +if __name__ == "__main__": + """ 初始化无向图 """ + v = vals_to_vets([1, 3, 2, 5, 4]) + edges = [[v[0], v[1]], [v[0], v[3]], [v[1], v[2]], + [v[2], v[3]], [v[2], v[4]], [v[3], v[4]]] + graph = GraphAdjList(edges) + print("\n初始化后,图为") + graph.print() + + """ 添加边 """ + # 顶点 1, 2 即 v[0], v[2] + graph.add_edge(v[0], v[2]) + print("\n添加边 1-2 后,图为") + graph.print() + + """ 删除边 """ + # 顶点 1, 3 即 v[0], v[1] + graph.remove_edge(v[0], v[1]) + print("\n删除边 1-3 后,图为") + graph.print() + + """ 添加顶点 """ + v5 = Vertex(6) + graph.add_vertex(v5) + print("\n添加顶点 6 后,图为") + graph.print() + + """ 删除顶点 """ + # 顶点 3 即 v[1] + graph.remove_vertex(v[1]) + print("\n删除顶点 3 后,图为") + graph.print() diff --git a/codes/python/chapter_graph/graph_adjacency_matrix.py b/codes/python/chapter_graph/graph_adjacency_matrix.py new file mode 100644 index 000000000..00af6455b --- /dev/null +++ b/codes/python/chapter_graph/graph_adjacency_matrix.py @@ -0,0 +1,116 @@ +""" +File: graph_adjacency_matrix.py +Created Time: 2023-02-23 +Author: Krahets (krahets@163.com) +""" + +import sys, os.path as osp +sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) +from include import * + +""" 基于邻接矩阵实现的无向图类 """ +class GraphAdjMat: + # 顶点列表,元素代表“顶点值”,索引代表“顶点索引” + vertices = [] + # 邻接矩阵,行列索引对应“顶点索引” + adj_mat = [] + + """ 构造方法 """ + def __init__(self, vertices, edges): + self.vertices = [] + self.adj_mat = [] + # 添加顶点 + for val in vertices: + self.add_vertex(val) + # 添加边 + # 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + for e in edges: + self.add_edge(e[0], e[1]) + + """ 获取顶点数量 """ + def size(self): + return len(self.vertices) + + """ 添加顶点 """ + def add_vertex(self, val): + 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 remove_vertex(self, index): + if index >= self.size(): + raise IndexError() + # 在顶点列表中移除索引 index 的顶点 + self.vertices.pop(index) + # 在邻接矩阵中删除索引 index 的行 + self.adj_mat.pop(index) + # 在邻接矩阵中删除索引 index 的列 + for row in self.adj_mat: + row.pop(index) + + """ 添加边 """ + # 参数 i, j 对应 vertices 元素索引 + def add_edge(self, i, j): + # 索引越界与相等处理 + if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j: + raise IndexError() + # 在无向图中,邻接矩阵沿主对角线对称,即满足 (i, j) == (j, i) + self.adj_mat[i][j] = 1 + self.adj_mat[j][i] = 1 + + """ 删除边 """ + # 参数 i, j 对应 vertices 元素索引 + def remove_edge(self, i, j): + # 索引越界与相等处理 + 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 + + # 打印邻接矩阵 + def print(self): + print("顶点列表 =", self.vertices) + print("邻接矩阵 =") + print_matrix(self.adj_mat) + + +""" Driver Code """ +if __name__ == "__main__": + """ 初始化无向图 """ + # 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + vertices = [1, 3, 2, 5, 4] + edges = [[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]] + graph = GraphAdjMat(vertices, edges) + print("\n初始化后,图为") + graph.print() + + """ 添加边 """ + # 顶点 1, 2 的索引分别为 0, 2 + graph.add_edge(0, 2) + print("\n添加边 1-2 后,图为") + graph.print() + + """ 删除边 """ + # 顶点 1, 3 的索引分别为 0, 1 + graph.remove_edge(0, 1) + print("\n删除边 1-3 后,图为") + graph.print() + + """ 添加顶点 """ + graph.add_vertex(6) + print("\n添加顶点 6 后,图为") + graph.print() + + """ 删除顶点 """ + # 顶点 3 的索引为 1 + graph.remove_vertex(1) + print("\n删除顶点 3 后,图为") + graph.print() diff --git a/codes/python/chapter_graph/graph_bfs.py b/codes/python/chapter_graph/graph_bfs.py new file mode 100644 index 000000000..cb4d6c67c --- /dev/null +++ b/codes/python/chapter_graph/graph_bfs.py @@ -0,0 +1,48 @@ +""" +File: graph_bfs.py +Created Time: 2023-02-23 +Author: Krahets (krahets@163.com) +""" + +import sys, os.path as osp +sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) +from include import * +from graph_adjacency_list import GraphAdjList + +""" 广度优先遍历 BFS """ +# 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 +def graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> List[Vertex]: + # 顶点遍历序列 + res = [] + # 哈希表,用于记录已被访问过的顶点 + visited = set([start_vet]) + # 队列用于实现 BFS + que = collections.deque([start_vet]) + # 以顶点 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 + + +if __name__ == "__main__": + """初始化无向图""" + v = vals_to_vets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + edges = [[v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], + [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], + [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]]] + graph = GraphAdjList(edges) + print("\n初始化后,图为") + graph.print() + + """广度优先遍历 BFS""" + res = graph_bfs(graph, v[0]) + print("\n广度优先遍历(BFS)顶点序列为") + print(vets_to_vals(res)) \ No newline at end of file diff --git a/codes/python/chapter_graph/graph_dfs.py b/codes/python/chapter_graph/graph_dfs.py new file mode 100644 index 000000000..f4a81ed56 --- /dev/null +++ b/codes/python/chapter_graph/graph_dfs.py @@ -0,0 +1,48 @@ +""" +File: graph_dfs.py +Created Time: 2023-02-23 +Author: Krahets (krahets@163.com) +""" + +import sys, os.path as osp +sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) +from include import * +from graph_adjacency_list import GraphAdjList + + +""" 深度优先遍历 DFS 辅助函数 """ +def dfs(graph: GraphAdjList, visited: Set[Vertex], res: List[Vertex], vet: Vertex): + res.append(vet) # 记录访问顶点 + visited.add(vet) # 标记该顶点已被访问 + # 遍历该顶点的所有邻接顶点 + for adjVet in graph.adj_list[vet]: + if adjVet in visited: + continue # 跳过已被访问过的顶点 + # 递归访问邻接顶点 + dfs(graph, visited, res, adjVet) + +""" 深度优先遍历 DFS """ +# 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 +def graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> List[Vertex]: + # 顶点遍历序列 + res = [] + # 哈希表,用于记录已被访问过的顶点 + visited = set() + dfs(graph, visited, res, start_vet) + return res + + +""" Driver Code """ +if __name__ == "__main__": + # 初始化无向图 + v = vals_to_vets([0, 1, 2, 3, 4, 5, 6]) + edges = [[v[0], v[1]], [v[0], v[3]], [v[1], v[2]], + [v[2], v[5]], [v[4], v[5]], [v[5], v[6]]] + graph = GraphAdjList(edges) + print("\n初始化后,图为") + graph.print() + + # 深度优先遍历 BFS + res = graph_dfs(graph, v[0]) + print("\n深度优先遍历(DFS)顶点序列为") + print(vets_to_vals(res)) diff --git a/codes/python/chapter_heap/heap.py b/codes/python/chapter_heap/heap.py new file mode 100644 index 000000000..e6bc0c7f1 --- /dev/null +++ b/codes/python/chapter_heap/heap.py @@ -0,0 +1,62 @@ +""" +File: heap.py +Created Time: 2023-02-23 +Author: Krahets (krahets@163.com) +""" + +import sys, os.path as osp +sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) +from include import * + +def test_push(heap, val, flag=1): + heapq.heappush(heap, flag * val) # 元素入堆 + print(f"\n元素 {val} 入堆后") + print_heap([flag * val for val in heap]) + +def test_pop(heap, flag=1): + val = flag * heapq.heappop(heap) # 堆顶元素出堆 + print(f"\n堆顶元素 {val} 出堆后") + print_heap([flag * val for val in heap]) + +if __name__ == "__main__": + # 初始化小顶堆 + min_heap, flag = [], 1 + # 初始化大顶堆 + max_heap, flag = [], -1 + + print("\n以下测试样例为大顶堆") + # Python 的 heapq 模块默认实现小顶堆 + # 考虑将“元素取负”后再入堆,这样就可以将大小关系颠倒,从而实现大顶堆 + # 在本示例中,flag = 1 时对应小顶堆,flag = -1 时对应大顶堆 + """ 元素入堆 """ + test_push(max_heap, 1, flag) + test_push(max_heap, 3, flag) + test_push(max_heap, 2, flag) + test_push(max_heap, 5, flag) + test_push(max_heap, 4, flag) + + """ 获取堆顶元素 """ + peek = flag * max_heap[0] + print(f"\n堆顶元素为 {peek}") + + """ 堆顶元素出堆 """ + test_pop(max_heap, flag) + test_pop(max_heap, flag) + test_pop(max_heap, flag) + test_pop(max_heap, flag) + test_pop(max_heap, flag) + + """ 获取堆大小 """ + size = len(max_heap) + print(f"\n堆元素数量为 {size}") + + """ 判断堆是否为空 """ + is_empty = not max_heap + print(f"\n堆是否为空 {is_empty}") + + """ 输入列表并建堆 """ + # 时间复杂度为 O(n) ,而非 O(nlogn) + min_heap = [1, 3, 2, 5, 4] + heapq.heapify(min_heap) + print("\n输入列表并建立小顶堆后") + print_heap(min_heap) diff --git a/codes/python/chapter_heap/my_heap.py b/codes/python/chapter_heap/my_heap.py new file mode 100644 index 000000000..f7adc508d --- /dev/null +++ b/codes/python/chapter_heap/my_heap.py @@ -0,0 +1,144 @@ +""" +File: my_heap.py +Created Time: 2023-02-23 +Author: Krahets (krahets@163.com) +""" + +import sys, os.path as osp +sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) +from include import * + +# 大顶堆 +class MaxHeap: + # 使用列表而非数组,这样无需考虑扩容问题 + def __init__(self, nums: List[int]): + # 将列表元素原封不动添加进堆 + self.max_heap = nums + # 堆化除叶结点以外的其他所有结点 + for i in range(self.parent(self.size() - 1), -1, -1): + self.sift_down(i) + + # 获取左子结点索引 + def left(self, i: int) -> int: + return 2 * i + 1 + + # 获取右子结点索引 + def right(self, i: int) -> int: + return 2 * i + 2 + + # 获取父结点索引 + def parent(self, i: int) -> int: + return (i - 1) // 2 # 向下整除 + + # 交换元素 + def swap(self, i: int, j: int): + a, b = self.max_heap[i], self.max_heap[j] + self.max_heap[i], self.max_heap[j] = b, a + + # 获取堆大小 + def size(self) -> int: + return len(self.max_heap) + + # 判断堆是否为空 + def is_empty(self) -> bool: + return self.size() == 0 + + # 访问堆顶元素 + def peek(self) -> int: + return self.max_heap[0] + + # 元素入堆 + def push(self, val: int): + # 添加结点 + self.max_heap.append(val) + # 从底至顶堆化 + self.sift_up(self.size() - 1) + + # 从结点 i 开始,从底至顶堆化 + def sift_up(self, i: int): + while True: + # 获取结点 i 的父结点 + p = self.parent(i) + # 当“越过根结点”或“结点无需修复”时,结束堆化 + if p < 0 or self.max_heap[i] <= self.max_heap[p]: + break + # 交换两结点 + self.swap(i, p) + # 循环向上堆化 + i = p + + # 元素出堆 + def poll(self) -> int: + # 判空处理 + assert not self.is_empty() + # 交换根结点与最右叶结点(即交换首元素与尾元素) + self.swap(0, self.size() - 1) + # 删除结点 + val = self.max_heap.pop() + # 从顶至底堆化 + self.sift_down(0) + # 返回堆顶元素 + return val + + # 从结点 i 开始,从顶至底堆化 + def sift_down(self, i: int): + while True: + # 判断结点 i, l, r 中值最大的结点,记为 ma + l, r, ma = self.left(i), self.right(i), i + if l < self.size() and self.max_heap[l] > self.max_heap[ma]: + ma = l + if r < self.size() and self.max_heap[r] > self.max_heap[ma]: + ma = r + # 若结点 i 最大或索引 l, r 越界,则无需继续堆化,跳出 + if ma == i: + break + # 交换两结点 + self.swap(i, ma) + # 循环向下堆化 + i = ma + + # 打印堆(二叉树) + def print(self): + print_heap(self.max_heap) + + +def test_push(max_heap: MaxHeap, val: int): + max_heap.push(val) # 元素入堆 + print(f"\n添加元素 {val} 后\n") + max_heap.print() + + +def test_poll(max_heap: MaxHeap): + val = max_heap.poll() # 堆顶元素出堆 + print(f"\n出堆元素为 {val}\n") + max_heap.print() + + +if __name__ == "__main__": + # 初始化大顶堆 + max_heap = MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) + print("\n输入列表并建堆后") + max_heap.print() + + # 获取堆顶元素 + peek = max_heap.peek() + print(f"\n堆顶元素为 {peek}") + + # 元素入堆 + val = 7 + max_heap.push(val) + print(f"\n元素 {val} 入堆后") + max_heap.print() + + # 堆顶元素出堆 + peek = max_heap.poll() + print(f"\n堆顶元素 {peek} 出堆后") + max_heap.print() + + # 获取堆大小 + size = max_heap.size() + print(f"\n堆元素数量为 {size}") + + # 判断堆是否为空 + is_empty = max_heap.is_empty() + print(f"\n堆是否为空 {is_empty}") diff --git a/codes/python/include/__init__.py b/codes/python/include/__init__.py index 9d4e90bee..0c899941e 100644 --- a/codes/python/include/__init__.py +++ b/codes/python/include/__init__.py @@ -1,5 +1,6 @@ import copy import math +import heapq import queue import random import functools @@ -7,4 +8,5 @@ import collections from typing import Optional, List, Dict, DefaultDict, OrderedDict, Set, Deque from .linked_list import ListNode, list_to_linked_list, linked_list_to_list, get_list_node from .binary_tree import TreeNode, list_to_tree, tree_to_list, get_tree_node -from .print_util import print_matrix, print_linked_list, print_tree, print_dict \ No newline at end of file +from .vertex import Vertex, vals_to_vets, vets_to_vals +from .print_util import print_matrix, print_linked_list, print_tree, print_dict, print_heap \ No newline at end of file diff --git a/codes/python/include/print_util.py b/codes/python/include/print_util.py index 6aa5f7a4e..c9b5504f3 100644 --- a/codes/python/include/print_util.py +++ b/codes/python/include/print_util.py @@ -6,7 +6,7 @@ Author: Krahets (krahets@163.com), msk397 (machangxinq@gmail.com) import copy import queue -from .binary_tree import TreeNode, tree_to_list +from .binary_tree import TreeNode, tree_to_list, list_to_tree from .linked_list import ListNode, linked_list_to_list def print_matrix(mat): @@ -80,4 +80,10 @@ def print_dict(d): d ([type]): [description] """ for key, value in d.items(): - print(key, '->', value) \ No newline at end of file + print(key, '->', value) + +def print_heap(heap): + print("堆的数组表示:", heap); + print("堆的树状表示:"); + root = list_to_tree(heap) + print_tree(root); diff --git a/codes/python/include/vertex.py b/codes/python/include/vertex.py new file mode 100644 index 000000000..63d08dc35 --- /dev/null +++ b/codes/python/include/vertex.py @@ -0,0 +1,18 @@ +# File: vertex.py +# Created Time: 2023-02-23 +# Author: Krahets (krahets@163.com) + +from typing import List + +# 顶点类 +class Vertex: + def __init__(self, val: int) -> None: + self.val = val + +# 输入值列表 vals ,返回顶点列表 vets +def vals_to_vets(vals: List[int]) -> List['Vertex']: + return [Vertex(val) for val in vals] + +# 输入顶点列表 vets ,返回值列表 vals +def vets_to_vals(vets: List['Vertex']) -> List[int]: + return [vet.val for vet in vets]