diff --git a/chapter_graph/graph_operations.md b/chapter_graph/graph_operations.md index a0a0b0423..7ac17b9a1 100644 --- a/chapter_graph/graph_operations.md +++ b/chapter_graph/graph_operations.md @@ -213,7 +213,78 @@ comments: true === "Python" ```python title="graph_adjacency_matrix.py" - [class]{GraphAdjMat}-[func]{} + """ 基于邻接矩阵实现的无向图类 """ + 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) ``` === "Go" @@ -887,7 +958,64 @@ comments: true === "Python" ```python title="graph_adjacency_list.py" - [class]{GraphAdjList}-[func]{} + """ 基于邻接表实现的无向图类 """ + 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},") ``` === "Go" @@ -1246,10 +1374,10 @@ comments: true class GraphAdjList { // 邻接表,使用哈希表来代替链表,以提升删除边、删除顶点的效率 // 请注意,adjList 中的元素是 Vertex 对象 - private var adjList: [Vertex: [Vertex]] + public private(set) var adjList: [Vertex: [Vertex]] /* 构造方法 */ - init(edges: [[Vertex]]) { + public init(edges: [[Vertex]]) { adjList = [:] // 添加所有顶点和边 for edge in edges { @@ -1260,12 +1388,12 @@ comments: true } /* 获取顶点数量 */ - func size() -> Int { + public func size() -> Int { adjList.count } /* 添加边 */ - func addEdge(vet1: Vertex, vet2: Vertex) { + public func addEdge(vet1: Vertex, vet2: Vertex) { if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { fatalError("参数错误") } @@ -1275,7 +1403,7 @@ comments: true } /* 删除边 */ - func removeEdge(vet1: Vertex, vet2: Vertex) { + public func removeEdge(vet1: Vertex, vet2: Vertex) { if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { fatalError("参数错误") } @@ -1285,7 +1413,7 @@ comments: true } /* 添加顶点 */ - func addVertex(vet: Vertex) { + public func addVertex(vet: Vertex) { if adjList[vet] != nil { return } @@ -1294,7 +1422,7 @@ comments: true } /* 删除顶点 */ - func removeVertex(vet: Vertex) { + public func removeVertex(vet: Vertex) { if adjList[vet] == nil { fatalError("参数错误") } @@ -1307,7 +1435,7 @@ comments: true } /* 打印邻接表 */ - func print() { + public func print() { Swift.print("邻接表 =") for entry in adjList { var tmp: [Int] = [] diff --git a/chapter_graph/graph_traversal.md b/chapter_graph/graph_traversal.md index 6790ca926..b2cfffb75 100644 --- a/chapter_graph/graph_traversal.md +++ b/chapter_graph/graph_traversal.md @@ -66,7 +66,27 @@ BFS 常借助「队列」来实现。队列具有“先入先出”的性质, === "Python" ```python title="graph_bfs.py" - + """ 广度优先遍历 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 ``` === "Go" @@ -102,7 +122,31 @@ BFS 常借助「队列」来实现。队列具有“先入先出”的性质, === "Swift" ```swift title="graph_bfs.swift" - + /* 广度优先遍历 BFS */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + func graphBFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { + // 顶点遍历序列 + var res: [Vertex] = [] + // 哈希表,用于记录已被访问过的顶点 + var visited: Set = [startVet] + // 队列用于实现 BFS + var que: [Vertex] = [startVet] + // 以顶点 vet 为起点,循环直至访问完所有顶点 + while !que.isEmpty { + let vet = que.removeFirst() // 队首顶点出队 + res.append(vet) // 记录访问顶点 + // 遍历该顶点的所有邻接顶点 + for adjVet in graph.adjList[vet] ?? [] { + if visited.contains(adjVet) { + continue // 跳过已被访问过的顶点 + } + que.append(adjVet) // 只入队未访问的顶点 + visited.insert(adjVet) // 标记该顶点已被访问 + } + } + // 返回顶点遍历序列 + return res + } ``` === "Zig" @@ -203,7 +247,26 @@ BFS 常借助「队列」来实现。队列具有“先入先出”的性质, === "Python" ```python title="graph_dfs.py" + """ 深度优先遍历 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 ``` === "Go" @@ -239,7 +302,30 @@ BFS 常借助「队列」来实现。队列具有“先入先出”的性质, === "Swift" ```swift title="graph_dfs.swift" + /* 深度优先遍历 DFS 辅助函数 */ + func dfs(graph: GraphAdjList, visited: inout Set, res: inout [Vertex], vet: Vertex) { + res.append(vet) // 记录访问顶点 + visited.insert(vet) // 标记该顶点已被访问 + // 遍历该顶点的所有邻接顶点 + for adjVet in graph.adjList[vet] ?? [] { + if visited.contains(adjVet) { + continue // 跳过已被访问过的顶点 + } + // 递归访问邻接顶点 + dfs(graph: graph, visited: &visited, res: &res, vet: adjVet) + } + } + /* 深度优先遍历 DFS */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + func graphDFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { + // 顶点遍历序列 + var res: [Vertex] = [] + // 哈希表,用于记录已被访问过的顶点 + var visited: Set = [] + dfs(graph: graph, visited: &visited, res: &res, vet: startVet) + return res + } ``` === "Zig" diff --git a/chapter_heap/heap.md b/chapter_heap/heap.md index 46d4973d3..e9aec8f73 100644 --- a/chapter_heap/heap.md +++ b/chapter_heap/heap.md @@ -52,7 +52,7 @@ comments: true // 初始化小顶堆 Queue minHeap = new PriorityQueue<>(); // 初始化大顶堆(使用 lambda 表达式修改 Comparator 即可) - Queue maxHeap = new PriorityQueue<>((a, b) -> { return b - a; }); + Queue maxHeap = new PriorityQueue<>((a, b) -> b - a); /* 元素入堆 */ maxHeap.add(1); @@ -123,7 +123,41 @@ comments: true === "Python" ```python title="heap.py" + # 初始化小顶堆 + min_heap, flag = [], 1 + # 初始化大顶堆 + max_heap, flag = [], -1 + # Python 的 heapq 模块默认实现小顶堆 + # 考虑将“元素取负”后再入堆,这样就可以将大小关系颠倒,从而实现大顶堆 + # 在本示例中,flag = 1 时对应小顶堆,flag = -1 时对应大顶堆 + """ 元素入堆 """ + heapq.heappush(max_heap, flag * 1) + heapq.heappush(max_heap, flag * 3) + heapq.heappush(max_heap, flag * 2) + heapq.heappush(max_heap, flag * 5) + heapq.heappush(max_heap, flag * 4) + + """ 获取堆顶元素 """ + peek = flag * max_heap[0] # 5 + + """ 堆顶元素出堆 """ + # 出堆元素会形成一个从大到小的序列 + val = flag * heapq.heappop(max_heap) # 5 + val = flag * heapq.heappop(max_heap) # 4 + val = flag * heapq.heappop(max_heap) # 3 + val = flag * heapq.heappop(max_heap) # 2 + val = flag * heapq.heappop(max_heap) # 1 + + """ 获取堆大小 """ + size = len(max_heap) + + """ 判断堆是否为空 """ + is_empty = not max_heap + + """ 输入列表并建堆 """ + min_heap = [1, 3, 2, 5, 4] + heapq.heapify(min_heap) ``` === "Go" @@ -329,7 +363,17 @@ comments: true === "Python" ```python title="my_heap.py" + """ 获取左子结点索引 """ + 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 # 向下整除 ``` === "Go" @@ -486,7 +530,9 @@ comments: true === "Python" ```python title="my_heap.py" - + """ 访问堆顶元素 """ + def peek(self) -> int: + return self.max_heap[0] ``` === "Go" @@ -633,7 +679,25 @@ comments: true === "Python" ```python title="my_heap.py" + """ 元素入堆 """ + 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 ``` === "Go" @@ -929,7 +993,35 @@ comments: true === "Python" ```python title="my_heap.py" + """ 元素出堆 """ + 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 ``` === "Go" @@ -1216,7 +1308,13 @@ comments: true === "Python" ```python title="my_heap.py" - + """ 构造方法 """ + 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) ``` === "Go"