├── .gitignore ├── .travis.yml ├── README.md └── play_with_graph_algorithms ├── __init__.py ├── chapter02 ├── __init__.py ├── adj_list.py ├── adj_matrix.py ├── adj_set.py └── g.txt ├── chapter03 ├── __init__.py ├── g1.txt ├── g2.txt └── graph_dfs.py ├── chapter04 ├── __init__.py ├── bi_partite_detection.py ├── cc.py ├── cycle_detection.py ├── g1.txt ├── g2.txt ├── g3.txt ├── g4.txt ├── path.py └── single_source_path.py ├── chapter05 ├── __init___.py ├── all_pairs_path.py ├── bi_partition_detection.py ├── cc.py ├── cycle_detection.py ├── g.txt ├── g2.txt ├── gg.txt ├── gg2.txt ├── gg3.txt ├── graph_bfs.py ├── single_source_path.py └── usss_path.py ├── chapter06 ├── __init__.py ├── lc1020_numbef_of_enclaves.py ├── lc1034_coloring_a_border.py ├── lc130_surrounded_regions.py ├── lc200_num_of_islands.py ├── lc529_ minesweeper.py ├── lc695_max_area_of_island.py ├── lc733_flood_fill.py ├── lc785_is_bipartite.py └── lc827_making_a_large_island.py ├── chapter07 ├── __init__.py ├── cross_river.py ├── lc1091_shortest_path_in_binary_matrix.py ├── lc752_open_the_lock.py ├── lc773_sliding_puzzle.py └── water_puzzle.py ├── chapter08 ├── find_bridges.py ├── find_cut_points.py ├── g.txt ├── g2.txt └── tree.txt ├── chapter09 ├── __init___.py ├── g.txt ├── g2.txt ├── hamilton_loop.py ├── hamilton_path.py └── lc980_unique_path_III.py ├── chapter10 ├── __init__.py ├── euler_loop.py ├── g.txt └── g2.txt ├── chapter11 ├── __init__.py ├── g.txt ├── kruskal.py ├── prim.py ├── uf.py ├── weighted_edge.py └── weighted_graph.py ├── chapter12 ├── __init__.py ├── bellman_ford.py ├── dijkstra.py ├── dijkstra_opt.py ├── floyd.py ├── g.txt └── g2.txt ├── chapter13 ├── __init__.py ├── bellman_ford.py ├── directed_circle_detection.py ├── directed_euler_loop.py ├── floyd.py ├── graph.py ├── graph_bfs.py ├── graph_dfs.py ├── scc.py ├── topo_sort.py ├── topo_sort2.py ├── ug.txt ├── ug2.txt ├── ug3.txt ├── ug4.txt ├── weighted_graph.py ├── wg.txt └── wg2.txt ├── chapter14 ├── __init__.py ├── baseball.txt ├── max_flow.py ├── network.txt └── network2.txt └── chapter15 ├── __init__.py ├── bipartite_matching.py ├── g.txt ├── g2.txt ├── hungarian.py ├── lcp4.py └── weighted_graph.py /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | virtualenv 3 | __pycache__ 4 | .travis.yml -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # play-with-graph-algorithms 2 | 3 | Python implementation of imooc course [玩转图论算法](https://coding.imooc.com/class/370.html), will update as the course going. 4 | 5 | Check/fork original repo from course instructor [liuyubobobo](https://github.com/liuyubobobo)! 6 | 7 | ## Notes 8 | ### Chapter 2 图的基本表示 9 | 10 | 1. 顶点Vertex,边Edge 11 | 12 | 2. 无向图Undirected Graph,有向图Directed Graph 13 | 14 | 3. 有权图Weighted Graph,无权图Unweighted Graph 15 | 16 | 4. 自环边,平行边 17 | 18 | 5. 没有自环边并且没有平行边的图称为简单图 19 | 20 | 6. 树是一种无环图 21 | 22 | 7. 无环图不一定一定是树,比如多个联通分量 23 | 24 | 8. 联通的无环图是树 25 | 26 | 9. 生成树就是指包括了原来图中的所有的点并且联通的图,该生成树的边数是V - 1,但是V - 1的边组成的新图并不一定是生成树 27 | 28 | 10. 只有连通图才有(并且一定有)生成树 29 | 30 | 11. 一个图一定有生成森林,但是并不一定有生成树(由于是否联通的问题) 31 | 32 | 12. 对于无向图来说,顶点相邻的边数就是degree 33 | 34 | 13. 邻接矩阵,邻接表来表示图 35 | 36 | 14. 邻接矩阵空间复杂度O(V^2),建图O(E),查看两点是否相邻O(1),求一个点的相邻节点O(V)(相当于遍历全部节点) 37 | 38 | 15. 稀疏图(sparse graph)和稠密图(dense graph)-> 完全图(complete graph) 39 | 40 | 16. 邻接表的空间复杂度O(V + E),建图O(E*V),未优化时查看两点是否相邻O(degree(v)),求一个点的相邻节点O(degree(v));优化后(使用HashSet)建图O(E),查看两点是否相邻O(1) 41 | 42 | ### Chapter 3 图的深度优先遍历 43 | 44 | 17. 树结构的遍历不用担心重复访问的问题,但是图的遍历会有重复遍历的问题(由于可能存在环),需要记录是否该点被访问过 45 | 46 | 18. dfs模板: 47 | ```python 48 | dfs(0, visited=[False] * V, lst=[]) 49 | 50 | def dfs(v, visited, lst): 51 | visited[v] = True 52 | # 近似于图的先序遍历 53 | lst.apppend(v) 54 | for w in adj(v): 55 | if not visited[w]: 56 | dfs(v, visited, lst) 57 | 58 | # 感觉递归出口写在开头更好一些 59 | def dfs(v, visited, lst): 60 | if visited[v]: 61 | return 62 | visited[v] = True 63 | # 近似于图的先序遍历 64 | lst.apppend(v) 65 | for w in adj(v): 66 | dfs(v, visited, lst) 67 | ``` 68 | 69 | 19. 深度优先时间复杂度是O(V + E),如果是联通图,可以简化成O(E) 70 | 71 | ### Chapter 4 图的深度优先遍历的应用 72 | 73 | 20. 可以利用visited数组(初始化全部为-1表示没有被访问过,其实赋什么都可以。访问时候赋给非负的值),而赋的值就是该节点的group id 74 | 75 | 21. 图的路径问题:询问两点之间有没有路径,如果属于同一个联通分量,意味着两点之间有路径 76 | 77 | 22. 深度优先遍历过程中记录当前递归栈的路径即可 78 | 79 | 23. 或者记录下当前遍历点的前一个点即可 80 | 81 | 23. 一个点到其他点的路径问题 -> 单源最短路径问题 82 | 83 | 24. 只需要使用一个pre数组(不需要visited)也可以做DFS遍历,可读性略低一点儿。 84 | 85 | 25. 原始单源最短路径问题是给出了源点到其他所有点的路径,但有时候我们只是单纯想知道从A点到B点能不能联通,路径是什么,并不关心A到其他点的路径是什么,此时就可以剪枝来加速。 86 | 87 | 26. 递归的语义尤为重要,要很清楚递归函数的意义和返回值(如果有的话)的意义。 88 | 89 | 27. 无向图中的环的检测:相当于从起点出发,看有没有路径返回起点(注意当前遍历点的下一个节点不能是上一个节点),也是图的深度优先遍历的思路 90 | 91 | 28. 二分图:顶点V可以分为不相交的两部分,其中图中每一条边的两个端点都分属于不同的两部分。处理匹配问题时候(比如分类)很有效 92 | 93 | 29. 思路就是对图深度优先遍历+对个当前访问的节点的下一层递归的节点(即当前边的下一个节点)染色成跟当前节点不一样的颜色;如果发现下一个节点已经染过色并且跟当前节点颜色相同,说明不是二分图了 94 | 95 | 30. 递归同时记录更多信息,并且利用返回值剪枝 96 | 97 | 31. 概念:图同构,平面图(无交叉) 98 | 99 | ### Chapter 5 图的广度优先遍历 100 | 101 | 32. 图的BFS时间复杂度是O(V+E),相当于全部的点和边都访问了一遍 102 | 103 | 33. BFS求解单源最短路径问题跟DFS类似,使用一个pre数组,在往队列里添加节点的时候,设置该被添加的节点的父节点(即当前从队列里pop出来的那个节点) 104 | 105 | 34. BFS在求单源最短路径的时候的解是全局最优解。而使用DFS则需要全局比较(所有的paths)才能确定最优解 106 | 107 | 35. 可以在遍历的过程中多记录一个信息distance,记录下某个点target到source点的steps 108 | 109 | 36. 可以对无权图(即权值一致)直接用BFS求最短路径 110 | 111 | 37. 遍历过程中可以使用随机队列(随机的线性数据结构),比如迷宫的生成 112 | 113 | ### Chapter 7 图论搜索和人工智能 114 | 115 | 38. BFS能够比较快速解决无权图最短路径问题 116 | 117 | 39. 有时候并不能很快确定这是一个BFS问题,需要多抽象 118 | 119 | 40. 核心:状态表达。其实如果要最短的状态表达,往往就是BFS。即可以将某一个状态看做是图中的某一个顶点 120 | 121 | ### Chapter 8 桥和割点 122 | 123 | 41. 如果删除了一条边,整个图的联通分量数量变化,则这条边称为桥(Bridge) 124 | 125 | 42. 桥就意味着图中最脆弱的关系 126 | 127 | 43. 图中可以有多个桥;树中多有的边都是桥 128 | 129 | 44. 环是图的属性,桥是边的属性,基本思路就是DFS遍历每一条边,判断每一条边是否是桥 130 | 131 | 45. 如何判断0-1是不是桥:看通过1,能否从另外一条路回到0,如果能,就不是桥;如果不能,0-1就是桥 132 | 133 | 46. 如何使得一个桥不再是桥?将其连接的两个分量再加一条边即可 134 | 135 | 47. 寻找桥的算法使用DFS就可以解决,其实就是Tarjan算法 136 | 137 | 48. 寻找桥不能使用DFS解决 138 | 139 | 49. 前向边 -> 生成树中的边,后向边 -> 可以指向自己的祖先节点 140 | 141 | 50. 删除割点以及相临边,图中的连通分量的数量发生变化 Cut points, Articulation points 142 | 143 | 51. 桥可以找割点,但是割点找不到桥 144 | 145 | 52. 对于割点来说,low[w] >= ord[v]就可以说明w是割点 146 | 147 | 53. 对于根节点来说,如果有两个或者两个以上的孩子,则根节点就是割点 148 | 149 | ### Chapter 9 哈密尔顿问题和状态压缩 150 | 151 | 54. 汉密尔顿回路即遍历完整张图回到原点,而且每个点只访问一次 152 | 153 | 55. 汉密尔顿路径就是遍历整张图,每个点只访问一次,此时起点的选择就很重要 154 | 155 | 56. TSP问题:以最小的代价在有权图中遍历完所有的点,而且每个点只能遍历一次 156 | 157 | 57. 本质上就是回溯法 158 | 159 | 58. 利用位操作实现状态压缩 160 | 161 | 59. 记忆化搜索 ^ 162 | 163 | ### Chapter 10 欧拉回路和欧拉路径 164 | 165 | 60. 每条边都访问过一次并回到原点的路径叫做欧拉回路;如果不能回到原点,就叫欧拉路径 166 | 167 | 61. 每个点的度都是偶数 <=> 图存在欧拉回路,前提是无向连通图 168 | 169 | 62. 两个相连的环,一定可以组成一个新环 170 | 171 | 63. 对于无向连通图,如果所有的点的度都是偶数,先随便找一个环,如果有剩下的边,一定还是环 172 | 173 | 64. 两个相连的环,一定可以组成一个新环 174 | 175 | 65. 结合63和64,就能推出 所有点的度都是偶数 => 一定存在欧拉回路 176 | 177 | ### Chapter 11 最小生成树 178 | 66. Kruskal基本思路就是尽量使用最短边(贪心),只要当前遍历的边的两个顶点不在同一个并查集内,就添加到mst中 179 | 180 | 67. 切分定理:图中的顶点分成两个部分,称为一个切分;如果一个边的两个端点,属于不同的两个部分的两边,这条边称为横切边 181 | 182 | 68. 横切边中的最短边,一定属于最终的最小生成树的一部分 183 | 184 | 69. Kruskal的时间复杂度是O(ElogE),即只跟边的数目有关 185 | 186 | 70. Prim算法:逐点遍历,将当前最短的横切边添加到mst中 187 | 188 | 71. Prim是逐点遍历,Kruskal思想是逐边遍历 189 | 190 | 72. Prim时间复杂度:O((V-1)*(V+E)) = O(VE),可以优化为O(ElogE),和Kruskal一样 191 | 192 | 73. 带权图的最小生成树才考虑使用Kruskal或者Prim算法,否则直接DFS或者BFS也可以 193 | 194 | 74. Prim其实可以优化成O(ElogV),但是需要借助索引堆的数据结构 195 | 196 | 75. Fredman-Tarjan O(E + VlogV) 197 | 198 | 76. Chazelle O(E*) 比E高一点点 199 | 200 | ### Chapter 12 最短路径算法 201 | 77. 第一版非经优化的Dijkstra算法的复杂度是O(V^2) 202 | 203 | 78. 可以使用堆优化查找最小值的循环,优化成O(ElogE),相当于对边来遍历 204 | 205 | 79. 求解最短路径:使用pre数组来判断每个点在哪儿来的 206 | 207 | 80. 可以用索引堆优化到O(ElogV) 208 | 209 | 81. Dijkstra不能处理处理负权边!!! 210 | 211 | 82. Bellman-ford可以求解负权边的图最短路径 212 | 213 | 83. Bellman-ford算法: 214 | ``` 215 | (1) 初始dis[s] = 0, 其余dis为正无穷 216 | (2) 对所有边进行一次松弛操作,则求出了到所有点,经过的变数最多为1的最短路径 217 | (3) 对所有边再进行一次松弛操作,则求出了到所有点,经过的边数最多为2的最短路径 218 | (4) 对所有边进行V - 1次松弛操作,则求出了到所有点,经过的变数最多为V - 1的最短路径 219 | (5) 最后再进行一次松弛操作,如果有更新最短距离dis,则肯定有负权环 220 | ``` 221 | 222 | 84. Bellman-Ford时间复杂度:O(V*E) 223 | 224 | 85. Bellman-Ford如果只关注s到t之间的最短路径,不能提前终止 225 | 226 | 86. Bellman-Ford的优化(SPFA, shortest path fast algorithm) 227 | 228 | 87. Floyd算法:求所有点到所有点的最短距离,基本思路就是三重循环,t v w其中t是v和w的中间点 229 | 230 | 88. Floyd算法复杂度O(V^3) 231 | 232 | ### Chapter 13 有向图算法 233 | 89. floodfill, 最小生成树,桥和割点,二分图检测不会在有向图中考虑 234 | 235 | 90. DFS BFS Dijkstra Bellman-ford Floyd 有向图和无向图是一样的 236 | 237 | 91. 有向图的环检测和无向图很不一样;已经遍历过不代表环,需要再遍历过程中添加一个标记,表示某个点在当前路径上 238 | 239 | 92. DAG: directed acyclic graph 240 | 241 | 93. 有向图有入度和出度的概念 242 | 243 | 94. 有向图存在欧拉回路的充分必要条件:每个点的入度等于出度 244 | 245 | 95. 有向图存在欧拉路径的充分必要条件:除了两个点,其余每个点的入度等于出度,这两个点,一个入度比出度大1,一个出度比入度大1 246 | 247 | 96. 拓扑排序可以用来检测环,时间复杂度O(V+E) 248 | 249 | 97. 只有DAG才能进行拓扑排序 250 | 251 | 98. 拓扑排序还可以使用深度优先遍历后序遍历 252 | 253 | 99. 对于一个节点,遍历完所有相邻节点之后,再遍历它自身,注意最后的结果是反向的,需要reverse一下 254 | 255 | 100. 但是注意深度优先比那里后序遍历不能是有环,只能是在DAG(有向无环图)中才能用来求拓扑排序 256 | 257 | 101. 强连通分量:在有向图中,任意两点都可以(注意一定要是在有向图中!!!) 258 | 259 | 102. 一个有向图中,可能有多个强连通分量! 260 | 261 | 103. 可以将所有的强连通分量看做一个点,得到的有向图一定是一个DAG 262 | 263 | 104. Kosaraju:先做原图的反图(即将所有的有向边反向),再对这个反图做深度优先后序遍历 264 | 265 | 105. Kosaraju中"反图"的思路是核心中的核心!!! 266 | 267 | 106. Kosaraju求有向图中的强连通分量(scc),时间复杂度O(V+E) 268 | 269 | ### Chapter 14 网络流 270 | 106. 源点:入度为0,汇点:出度为0,有向非负带权图,权值表示容量(capacity) 271 | 272 | 107. 0/9 -> 表示流量为0,容量为9 273 | 274 | 108. 容量限制,平衡限制(对于每个点,流入量等于流出量) 275 | 276 | 109. Ford-Fulkerson思想: 277 | - 有个虚拟的反向边(反向边最大容量是当前正向边的流量) 278 | - 在残量图中不断找增广路径,直到没有增广路径为止 279 | - 增广路径就是指还能找到的新的能通的路径 280 | 281 | 110. 反向可以逆流,但是有范围限制。比如现在是点1到2的路径是3/5,表示容量为5,当前流量为3,则反向只能是0~3之间的流量。因为如果超过3的反向,相当于当前整条路径是负值了,同理反向流量也不能小于0 282 | 283 | 111. 残量图(residual graph),正向图的权值是正向的容量-正向的当前流量,表示当前最多还能容纳多少;反向图的权值是正向边的当前流量,也能够表示当前反向能容纳多少 284 | 285 | 112. Edmonds-Karp算法 286 | - 先构建残量图 287 | - 使用BFS在残量图中寻找增广路径 288 | - 该增广路径的最大流量是当前每条边上的权值的(记得权值是当前的最大流量,即表示当前还能容纳多少)的最小值 289 | - 更新增广路径上的每条边的权值 290 | - 继续寻找下一条增广路径,直到找不到增广路径为止 291 | - (要区分原图的x/y和残量图权值的意义!!!) 292 | 293 | 113. Ford-Fulkerson思想:时间复杂度O(maxflow * E) 294 | 295 | 114. 具体到Edmonds-Karp算法:时间复杂度O(VE ^ 2) 296 | 297 | 115. 最大流算法总结: 298 | - 容量限制:每条通路都有限制 299 | - 平衡限制:除了入点和出点以外,所有的点出等于入 300 | - Ford - Fulkerson思想:在残量图上寻找增广路径 301 | - Edmonds - Karp算法:BFS, O(VE^2) 302 | - Dinic算法:O(EV^2) 303 | - MPM算法:O(V^3) 304 | 305 | ### Chapter 15 匹配问题 306 | 116. 最大匹配:一个二分图,最多的一一匹配 307 | 308 | 117. 完全匹配一定是最大匹配,最大匹配不一定是完全匹配 309 | 310 | 118. 可以使用最大流算法解决匹配问题,即所有边的容量都为1,最大流即为最大匹配数 311 | 312 | 119. LCP 4覆盖问题: 313 | - 首先将棋盘所有的点认为是类似国际象棋的棋盘的样子(黑白相间),然后就可以建图。 314 | - 可以认为每个黑白连起来的2x1的格子都是一条边,连接了二分图中的两个点(黑点和白点) 315 | - 这样问题就可以转化为最大匹配问题 316 | 317 | 120. 匈牙利算法解决最大流(hungarian algorithm) 318 | - 从左边开始,往右边去连第一个还没有匹配的点 319 | - 如果右边的点是一个匹配点,从右往左的边,永远走匹配边 320 | - 最后将匹配的边换成未匹配的边,未匹配的边再变成匹配的边 321 | - 匹配边和非匹配边交替出现:交替路 322 | - 终止与另外一个非匹配点:即找到了一条增广路径 323 | - 有增广路径,意味着最大匹配数可以加一 324 | - 在交替过程中,由于我们起始于非匹配点,终止与非匹配点,所以中间经过的非匹配边的数目一定比匹配边的数目大1 325 | 326 | 121. 匈牙利算法总结:对左侧每一个尚未匹配的点,不断地寻找可以增广的交替路 327 | 328 | 122. 可以利用BFS/DFS寻找增广路径 329 | 330 | 123. BFS队列中只存储左边的点 331 | 332 | 124. 经典问题:Lintcode 1576. 最佳匹配 333 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zwang96-dl/play-with-graph-algorithms/27606571a71d5516eaac2bfdb68b9e98e872639a/play_with_graph_algorithms/__init__.py -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter02/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zwang96-dl/play-with-graph-algorithms/27606571a71d5516eaac2bfdb68b9e98e872639a/play_with_graph_algorithms/chapter02/__init__.py -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter02/adj_list.py: -------------------------------------------------------------------------------- 1 | class AdjList: 2 | 3 | def __init__(self, filename): 4 | lines = None 5 | with open(filename, 'r') as f: 6 | lines = f.readlines() 7 | if not lines: 8 | raise ValueError('Expected something from input file!') 9 | 10 | # lines[0] -> V E 11 | self._V, self._E = (int(i) for i in lines[0].split()) 12 | 13 | if self._V < 0: 14 | raise ValueError('V must be non-negative') 15 | 16 | if self._E < 0: 17 | raise ValueError('E must be non-negative') 18 | 19 | # size V list of list 20 | self._adj = [[] for _ in range(self._V)] 21 | for each_line in lines[1:]: 22 | a, b = (int(i) for i in each_line.split()) 23 | self._validate_vertex(a) 24 | self._validate_vertex(b) 25 | 26 | if a == b: 27 | raise ValueError('Self-Loop is detected!') 28 | 29 | if self._adj[a].count(b): 30 | raise ValueError('Paralles edges are detected!') 31 | 32 | self._adj[a].append(b) 33 | self._adj[b].append(a) 34 | 35 | @property 36 | def V(self): 37 | return self._V 38 | 39 | @property 40 | def E(self): 41 | return self._E 42 | 43 | def has_edge(self, v, w): 44 | self._validate_vertex(v) 45 | self._validate_vertex(w) 46 | return self._adj[v].count(w) 47 | 48 | def adj(self, v): 49 | self._validate_vertex(v) 50 | return self._adj[v] 51 | 52 | def degree(self, v): 53 | return len(self.adj(v)) 54 | 55 | def _validate_vertex(self, v): 56 | if v < 0 or v >= self._V: 57 | raise ValueError('vertex ' + v + ' is invalid') 58 | 59 | def __str__(self): 60 | res = ['V = {}, E = {}'.format(self._V, self._E)] 61 | for v in range(self._V): 62 | res.append('{}: {}'.format(v, ' '.join(str(w) for w in self._adj[v]))) 63 | return '\n'.join(res) 64 | 65 | def __repr__(self): 66 | return self.__str__() 67 | 68 | 69 | if __name__ == '__main__': 70 | filename = 'play_with_graph_algorithms/chapter02/g.txt' 71 | print(__file__) 72 | adj_list = AdjList(filename) 73 | print(adj_list) 74 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter02/adj_matrix.py: -------------------------------------------------------------------------------- 1 | class AdjMatrix: 2 | 3 | def __init__(self, filename): 4 | lines = None 5 | with open(filename, 'r') as f: 6 | lines = f.readlines() 7 | if not lines: 8 | raise ValueError('Expected something from input file!') 9 | 10 | # lines[0] -> V E 11 | self._V, self._E = (int(i) for i in lines[0].split()) 12 | 13 | if self._V < 0: 14 | raise ValueError('V must be non-negative') 15 | 16 | if self._E < 0: 17 | raise ValueError('E must be non-negative') 18 | 19 | # size V x V matrix 20 | self._adj = [[0] * self._V for _ in range(self._V)] 21 | for each_line in lines[1:]: 22 | a, b = (int(i) for i in each_line.split()) 23 | self._validate_vertex(a) 24 | self._validate_vertex(b) 25 | 26 | if a == b: 27 | raise ValueError('Self-Loop is detected!') 28 | 29 | if self._adj[a][b] == 1: 30 | raise ValueError('Paralles edges are detected!') 31 | 32 | self._adj[a][b] = 1 33 | self._adj[b][a] = 1 34 | 35 | @property 36 | def V(self): 37 | return self._V 38 | 39 | @property 40 | def E(self): 41 | return self._E 42 | 43 | def has_edge(self, v, w): 44 | self._validate_vertex(v) 45 | self._validate_vertex(w) 46 | return self._adj[v][w] == 1 47 | 48 | def adj(self, v): 49 | self._validate_vertex(v) 50 | res = [] 51 | for i in range(self._V): 52 | if self._adj[v][i] == 1: 53 | res.append(i) 54 | return res 55 | 56 | def degree(self, v): 57 | return len(self.adj(v)) 58 | 59 | def _validate_vertex(self, v): 60 | if v < 0 or v >= self._V: 61 | raise ValueError('vertex ' + v + ' is invalid') 62 | 63 | def __str__(self): 64 | res = ['V = {}, E = {}'.format(self._V, self._E)] 65 | for i in range(self._V): 66 | each_line = '' 67 | for j in range(self._V): 68 | each_line += '{} '.format(self._adj[i][j]) 69 | res.append(each_line) 70 | return '\n'.join(res) 71 | 72 | def __repr__(self): 73 | return self.__str__() 74 | 75 | 76 | if __name__ == '__main__': 77 | filename = 'play_with_graph_algorithms/chapter02/g.txt' 78 | adj_matrix = AdjMatrix(filename) 79 | print(adj_matrix) 80 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter02/adj_set.py: -------------------------------------------------------------------------------- 1 | class AdjSet: 2 | 3 | """Suppose to use RB tree, but no TreeSet in vanilla Python, using 4 | set instead. 5 | """ 6 | 7 | def __init__(self, filename): 8 | self._filename = filename 9 | lines = None 10 | with open(filename, 'r') as f: 11 | lines = f.readlines() 12 | if not lines: 13 | raise ValueError('Expected something from input file!') 14 | 15 | # lines[0] -> V E 16 | self._V, self._E = (int(i) for i in lines[0].split()) 17 | 18 | if self._V < 0: 19 | raise ValueError('V must be non-negative') 20 | 21 | if self._E < 0: 22 | raise ValueError('E must be non-negative') 23 | 24 | # size V list of set 25 | self._adj = [set() for _ in range(self._V)] 26 | for each_line in lines[1:]: 27 | a, b = (int(i) for i in each_line.split()) 28 | self.validate_vertex(a) 29 | self.validate_vertex(b) 30 | 31 | if a == b: 32 | raise ValueError('Self-Loop is detected!') 33 | 34 | if b in self._adj[a]: 35 | raise ValueError('Paralles edges are detected!') 36 | 37 | self._adj[a].add(b) 38 | self._adj[b].add(a) 39 | 40 | @property 41 | def V(self): 42 | return self._V 43 | 44 | @property 45 | def E(self): 46 | return self._E 47 | 48 | def has_edge(self, v, w): 49 | self.validate_vertex(v) 50 | self.validate_vertex(w) 51 | return w in self._adj[v] 52 | 53 | def adj(self, v): 54 | self.validate_vertex(v) 55 | return self._adj[v] 56 | 57 | def degree(self, v): 58 | return len(self.adj(v)) 59 | 60 | def remove_edge(self, v, w): 61 | self.validate_vertex(v) 62 | self.validate_vertex(w) 63 | if w in self._adj[v]: 64 | self._adj[v].remove(w) 65 | if v in self._adj[w]: 66 | self._adj[w].remove(v) 67 | 68 | def validate_vertex(self, v): 69 | if v < 0 or v >= self._V: 70 | raise ValueError('vertex ' + v + ' is invalid') 71 | 72 | def __str__(self): 73 | res = ['V = {}, E = {}'.format(self._V, self._E)] 74 | for v in range(self._V): 75 | res.append('{}: {}'.format(v, ' '.join(str(w) for w in self._adj[v]))) 76 | return '\n'.join(res) 77 | 78 | def __repr__(self): 79 | return self.__str__() 80 | 81 | def __copy__(self): 82 | return AdjSet(self._filename) 83 | 84 | 85 | if __name__ == '__main__': 86 | filename = 'play_with_graph_algorithms/chapter02/g.txt' 87 | adj_set = AdjSet(filename) 88 | print(adj_set) 89 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter02/g.txt: -------------------------------------------------------------------------------- 1 | 7 9 2 | 0 1 3 | 0 3 4 | 1 2 5 | 1 6 6 | 2 3 7 | 2 5 8 | 3 4 9 | 4 5 10 | 5 6 11 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter03/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zwang96-dl/play-with-graph-algorithms/27606571a71d5516eaac2bfdb68b9e98e872639a/play_with_graph_algorithms/chapter03/__init__.py -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter03/g1.txt: -------------------------------------------------------------------------------- 1 | 7 8 2 | 0 1 3 | 0 2 4 | 1 3 5 | 1 4 6 | 2 3 7 | 2 6 8 | 3 5 9 | 5 6 10 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter03/g2.txt: -------------------------------------------------------------------------------- 1 | 7 6 2 | 0 1 3 | 0 2 4 | 1 3 5 | 1 4 6 | 2 3 7 | 2 6 8 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter03/graph_dfs.py: -------------------------------------------------------------------------------- 1 | from play_with_graph_algorithms.chapter02.adj_set import AdjSet as Graph 2 | 3 | 4 | class GraphDFS: 5 | 6 | def __init__(self, G, recursive=True): 7 | self._pre_order = [] 8 | self._post_order = [] 9 | self._G = G 10 | self._visited = [False] * G.V 11 | 12 | # 遍历所有的点,相当于遍历图中所有可能存在的联通块 13 | for v in range(G.V): 14 | if not self._visited[v]: 15 | if recursive: 16 | self._dfs_recursive(v) 17 | else: 18 | self._dfs_iteration(v) 19 | 20 | def _dfs_recursive(self, v): 21 | self._visited[v] = True 22 | self._pre_order.append(v) 23 | for w in self._G.adj(v): 24 | if not self._visited[w]: 25 | self._dfs_recursive(w) 26 | self._post_order.append(v) 27 | 28 | def _dfs_iteration(self, v): 29 | """For pre-order that's straight-forward by using one stack, 30 | but for post-order we need an augmented stack2 31 | """ 32 | stack1 = [v] 33 | self._visited[v] = True 34 | stack2 = [] 35 | while stack1: 36 | curr = stack1.pop() 37 | self._pre_order.append(curr) 38 | stack2.append(curr) 39 | for w in self._G.adj(curr): 40 | if not self._visited[w]: 41 | stack1.append(w) 42 | self._visited[w] = True 43 | self._post_order += stack2[::-1] 44 | 45 | @property 46 | def pre_order(self): 47 | return self._pre_order 48 | 49 | @property 50 | def post_order(self): 51 | return self._post_order 52 | 53 | 54 | if __name__ == '__main__': 55 | print('For one block, recursive:') 56 | filename = 'play_with_graph_algorithms/chapter03/g1.txt' 57 | g = Graph(filename) 58 | graph_dfs = GraphDFS(g) 59 | print(graph_dfs.pre_order) 60 | print(graph_dfs.post_order) 61 | 62 | print('*' * 40) 63 | 64 | print('For two blocks, recursive:') 65 | filename = 'play_with_graph_algorithms/chapter03/g2.txt' 66 | g = Graph(filename) 67 | graph_dfs = GraphDFS(g) 68 | print(graph_dfs.pre_order) 69 | print(graph_dfs.post_order) 70 | 71 | print('*' * 40) 72 | 73 | print('For one block, iteration:') 74 | filename = 'play_with_graph_algorithms/chapter03/g1.txt' 75 | g = Graph(filename) 76 | graph_dfs = GraphDFS(g, recursive=False) 77 | print(graph_dfs.pre_order) 78 | print(graph_dfs.post_order) 79 | 80 | print('*' * 40) 81 | 82 | print('For two blocks, iteration:') 83 | filename = 'play_with_graph_algorithms/chapter03/g2.txt' 84 | g = Graph(filename) 85 | graph_dfs = GraphDFS(g, recursive=False) 86 | print(graph_dfs.pre_order) 87 | print(graph_dfs.post_order) 88 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter04/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zwang96-dl/play-with-graph-algorithms/27606571a71d5516eaac2bfdb68b9e98e872639a/play_with_graph_algorithms/chapter04/__init__.py -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter04/bi_partite_detection.py: -------------------------------------------------------------------------------- 1 | from play_with_graph_algorithms.chapter02.adj_set import AdjSet as Graph 2 | 3 | 4 | class BiPartiteDetection: 5 | 6 | def __init__(self, G): 7 | self._G = G 8 | self._visited = [False] * G.V 9 | # -1 means not colored yet, should be 0 or 1 10 | self._colors = [-1] * G.V 11 | self._is_bi_partite = True 12 | for v in range(G.V): 13 | if not self._visited[v]: 14 | # if v is not visited 15 | # means we are entering a new tree!! 16 | # so it doesn't matter to color it as 0 or 1 17 | if self._dfs_recursive(v, 0) is False: 18 | self._is_bi_partite = False 19 | break 20 | 21 | # v should be colored using "color" value 22 | # the return value of _dfs_recursive represents whether 23 | # current tree is a bi-partite graph or not 24 | def _dfs_recursive(self, v, color): 25 | self._visited[v] = True 26 | self._colors[v] = color 27 | for w in self._G.adj(v): 28 | if not self._visited[w]: 29 | if not self._dfs_recursive(w, 1 - color): 30 | return False 31 | elif self._colors[w] == self._colors[v]: 32 | return False 33 | return True 34 | 35 | def is_bi_partite(self): 36 | return self._is_bi_partite 37 | 38 | def colors(self): 39 | return self._colors 40 | 41 | 42 | if __name__ == '__main__': 43 | filename = 'play_with_graph_algorithms/chapter04/g3.txt' 44 | g = Graph(filename) 45 | bi_partite_detection = BiPartiteDetection(g) 46 | print(bi_partite_detection.is_bi_partite()) 47 | 48 | filename = 'play_with_graph_algorithms/chapter04/g4.txt' 49 | g = Graph(filename) 50 | bi_partite_detection = BiPartiteDetection(g) 51 | print(bi_partite_detection.is_bi_partite()) 52 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter04/cc.py: -------------------------------------------------------------------------------- 1 | from play_with_graph_algorithms.chapter02.adj_set import AdjSet as Graph 2 | 3 | 4 | class CC: 5 | 6 | def __init__(self, G, recursive=True): 7 | self._G = G 8 | self._visited = [-1] * G.V 9 | self._ccount = 0 10 | 11 | if self._G.is_directed: 12 | raise ValueError('CC only works in undirected graph') 13 | 14 | # 遍历所有的点,相当于遍历图中所有可能存在的联通块 15 | for v in range(G.V): 16 | if self._visited[v] == -1: 17 | if recursive: 18 | self._dfs_recursive(v, self._ccount) 19 | else: 20 | self._dfs_iteration(v, self._ccount) 21 | self._ccount += 1 22 | 23 | def _dfs_recursive(self, v, ccid): 24 | self._visited[v] = ccid 25 | for w in self._G.adj(v): 26 | if self._visited[w] == -1: 27 | self._dfs_recursive(w, ccid) 28 | 29 | def _dfs_iteration(self, v, ccid): 30 | """For preorder that's straight-forward by using one stack, 31 | but for postorder we need a augmented stack2 32 | """ 33 | stack = [v] 34 | # within one func call, all visited nodes should be the same ccid 35 | self._visited[v] = ccid 36 | while stack: 37 | curr = stack.pop() 38 | for w in self._G.adj(curr): 39 | if self._visited[w] == -1: 40 | stack.append(w) 41 | # the same ccid 42 | self._visited[w] = ccid 43 | 44 | def is_connected(self, v, w): 45 | self._G.validate_vertex(v) 46 | self._G.validate_vertex(w) 47 | return self._visited[v] == self._visited[w] 48 | 49 | @property 50 | def ccount(self): 51 | return self._ccount 52 | 53 | @property 54 | def groups(self): 55 | res = [[] for _ in range(self._ccount)] 56 | for v in range(self._G.V): 57 | res[self._visited[v]].append(v) 58 | return res 59 | 60 | 61 | if __name__ == '__main__': 62 | filename = 'play_with_graph_algorithms/chapter04/g1.txt' 63 | g = Graph(filename) 64 | cc = CC(g) 65 | print(cc.ccount) 66 | print(cc.groups) 67 | 68 | filename = 'play_with_graph_algorithms/chapter04/g2.txt' 69 | g = Graph(filename) 70 | cc = CC(g) 71 | print(cc.ccount) 72 | print(cc.groups) 73 | 74 | filename = 'play_with_graph_algorithms/chapter04/g1.txt' 75 | g = Graph(filename) 76 | cc = CC(g, recursive=False) 77 | print(cc.ccount) 78 | print(cc.groups) 79 | 80 | filename = 'play_with_graph_algorithms/chapter04/g2.txt' 81 | g = Graph(filename) 82 | cc = CC(g, recursive=False) 83 | print(cc.ccount) 84 | print(cc.groups) 85 | 86 | print(cc.is_connected(0, 6)) 87 | print(cc.is_connected(0, 5)) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter04/cycle_detection.py: -------------------------------------------------------------------------------- 1 | from play_with_graph_algorithms.chapter02.adj_set import AdjSet as Graph 2 | 3 | 4 | class CycleDetection: 5 | 6 | def __init__(self, G): 7 | self._G = G 8 | self._visited = [False] * G.V 9 | self._has_cycle = False 10 | 11 | # 遍历所有的点,相当于遍历图中所有可能存在的联通块 12 | for v in range(G.V): 13 | if not self._visited[v]: 14 | self._dfs_recursive(v, v) 15 | 16 | def _dfs_recursive(self, v, parent): 17 | self._visited[v] = True 18 | for w in self._G.adj(v): 19 | if not self._visited[w]: 20 | if self._dfs_recursive(w, v): 21 | return True 22 | # 说明此时找到一个环 23 | elif w != parent: 24 | self._has_cycle = True 25 | return True 26 | return False 27 | 28 | def has_cycle(self): 29 | return self._has_cycle 30 | 31 | 32 | if __name__ == '__main__': 33 | filename = 'play_with_graph_algorithms/chapter04/g2.txt' 34 | g = Graph(filename) 35 | cycle_detection = CycleDetection(g) 36 | print(cycle_detection.has_cycle()) 37 | 38 | filename = 'play_with_graph_algorithms/chapter04/g3.txt' 39 | g = Graph(filename) 40 | cycle_detection = CycleDetection(g) 41 | print(cycle_detection.has_cycle()) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter04/g1.txt: -------------------------------------------------------------------------------- 1 | 7 8 2 | 0 1 3 | 0 2 4 | 1 3 5 | 1 4 6 | 2 3 7 | 2 6 8 | 3 5 9 | 5 6 10 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter04/g2.txt: -------------------------------------------------------------------------------- 1 | 7 6 2 | 0 1 3 | 0 2 4 | 1 3 5 | 1 4 6 | 2 3 7 | 2 6 8 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter04/g3.txt: -------------------------------------------------------------------------------- 1 | 7 6 2 | 0 1 3 | 0 2 4 | 1 3 5 | 1 4 6 | 2 6 7 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter04/g4.txt: -------------------------------------------------------------------------------- 1 | 4 6 2 | 0 1 3 | 0 2 4 | 0 3 5 | 1 2 6 | 1 3 7 | 2 3 8 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter04/path.py: -------------------------------------------------------------------------------- 1 | from play_with_graph_algorithms.chapter02.adj_set import AdjSet as Graph 2 | 3 | 4 | class Path: 5 | 6 | def __init__(self, G, s, t, recursive=True): 7 | G.validate_vertex(s) 8 | G.validate_vertex(t) 9 | self._G = G 10 | self._s = s 11 | self._t = t 12 | self._visited = [False] * G.V 13 | self._pre = [-1] * G.V 14 | 15 | if recursive: 16 | self._dfs_recursive(s, s) 17 | else: 18 | self._dfs_iteration(s, s) 19 | print(self._visited) 20 | 21 | def _dfs_recursive(self, v, parent): 22 | self._visited[v] = True 23 | self._pre[v] = parent 24 | if v == self._t: 25 | return True 26 | for w in self._G.adj(v): 27 | if not self._visited[w]: 28 | if self._dfs_recursive(w, v): 29 | return True 30 | return False 31 | 32 | def _dfs_iteration(self, v, parent): 33 | stack = [v] 34 | self._visited[v] = True 35 | self._pre[v] = parent 36 | 37 | while stack: 38 | curr = stack.pop() 39 | # 在pop时候check比较合适 40 | # 因为确保了visited和pre都被赋过值了 41 | if curr == self._t: 42 | return True 43 | for w in self._G.adj(curr): 44 | if not self._visited[w]: 45 | stack.append(w) 46 | self._visited[w] = True 47 | self._pre[w] = curr 48 | 49 | return False 50 | 51 | def is_connected(self): 52 | return self._visited[self._t] 53 | 54 | def path(self): 55 | res = [] 56 | if not self.is_connected(): 57 | return res 58 | curr = self._t 59 | while curr != self._s: 60 | res.append(curr) 61 | curr = self._pre[curr] 62 | res.append(self._s) 63 | return res[::-1] 64 | 65 | 66 | if __name__ == '__main__': 67 | filename = 'play_with_graph_algorithms/chapter04/g2.txt' 68 | g = Graph(filename) 69 | 70 | path = Path(g, 0, 6) 71 | print('0 -> 6: ' + str(path.path())) 72 | 73 | path = Path(g, 0, 5) 74 | print('0 -> 5: ' + str(path.path())) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter04/single_source_path.py: -------------------------------------------------------------------------------- 1 | from play_with_graph_algorithms.chapter02.adj_set import AdjSet as Graph 2 | 3 | 4 | class SingleSourcePath: 5 | 6 | def __init__(self, G, s, recursive=True): 7 | G.validate_vertex(s) 8 | self._G = G 9 | self._s = s 10 | self._visited = [False] * G.V 11 | self._pre = [-1] * G.V 12 | 13 | if recursive: 14 | self._dfs_recursive(s, s) 15 | else: 16 | self._dfs_iteration(s, s) 17 | 18 | def _dfs_recursive(self, v, parent): 19 | self._visited[v] = True 20 | self._pre[v] = parent 21 | for w in self._G.adj(v): 22 | if not self._visited[w]: 23 | self._dfs_recursive(w, v) 24 | 25 | def _dfs_iteration(self, v, parent): 26 | stack = [v] 27 | self._visited[v] = True 28 | self._pre[v] = parent 29 | while stack: 30 | curr = stack.pop() 31 | for w in self._G.adj(curr): 32 | if not self._visited[w]: 33 | stack.append(w) 34 | self._visited[w] = True 35 | self._pre[w] = curr 36 | 37 | def is_connected_to(self, t): 38 | """this funtion is called during the dfs 39 | so if current node t is visited (self._visited[t] == True) 40 | this means the current t is connected to the source node 41 | """ 42 | self._G.validate_vertex(t) 43 | return self._visited[t] 44 | 45 | def path(self, t): 46 | res = [] 47 | if not self.is_connected_to(t): 48 | return res 49 | curr = t 50 | while curr != self._s: 51 | res.append(curr) 52 | curr = self._pre[curr] 53 | res.append(self._s) 54 | return res[::-1] 55 | 56 | 57 | if __name__ == '__main__': 58 | filename = 'play_with_graph_algorithms/chapter04/g2.txt' 59 | g = Graph(filename) 60 | 61 | single_source_path = SingleSourcePath(g, 0) 62 | print('0 -> 6: ' + str(single_source_path.path(6))) 63 | 64 | single_source_path = SingleSourcePath(g, 0) 65 | print('0 -> 5: ' + str(single_source_path.path(5))) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter05/__init___.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zwang96-dl/play-with-graph-algorithms/27606571a71d5516eaac2bfdb68b9e98e872639a/play_with_graph_algorithms/chapter05/__init___.py -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter05/all_pairs_path.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | from play_with_graph_algorithms.chapter02.adj_set import AdjSet as Graph 3 | from play_with_graph_algorithms.chapter05.single_source_path import SingleSourcePath 4 | 5 | 6 | class AllPairsPath: 7 | 8 | def __init__(self, G): 9 | self._G = G 10 | self._paths = [] 11 | 12 | for v in range(G.V): 13 | self._paths.append(SingleSourcePath(G, v)) 14 | 15 | def is_connected_to(self, s, t): 16 | self._G.validate_vertex(s) 17 | return self._paths[s].is_connected_to(t) 18 | 19 | def path(self, s, t): 20 | self._G.validate_vertex(s) 21 | return self._paths[s].path(t) 22 | 23 | 24 | if __name__ == '__main__': 25 | filename = 'play_with_graph_algorithms/chapter04/g1.txt' 26 | g = Graph(filename) 27 | appath = AllPairsPath(g) 28 | print(appath.path(0, 6)) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter05/bi_partition_detection.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | from play_with_graph_algorithms.chapter02.adj_set import AdjSet as Graph 3 | 4 | 5 | class BiPartitionDetection: 6 | 7 | def __init__(self, G): 8 | self._G = G 9 | self._colors = [-1] * G.V 10 | self._is_bi_partition_graph = True 11 | 12 | for v in range(G.V): 13 | if self._colors[v] == -1: 14 | if not self._bfs(v): 15 | self._is_bi_partition_graph = False 16 | break 17 | 18 | def _bfs(self, s): 19 | queue = deque() 20 | queue.append(s) 21 | self._colors[s] = 0 22 | 23 | while queue: 24 | v = queue.popleft() 25 | for w in self._G.adj(v): 26 | if self._colors[w] == -1: 27 | queue.append(w) 28 | self._colors[w] = 1 - self._colors[v] 29 | # 如果下一个点w的颜色是当前处理点的颜色 30 | # 说明不能对其染色了 31 | # 即说明当前的图不是二分图 32 | elif self._colors[w] == self._colors[v]: 33 | return False 34 | return True 35 | 36 | def is_bi_partition_graph(self): 37 | return self._is_bi_partition_graph 38 | 39 | 40 | if __name__ == '__main__': 41 | filename = 'play_with_graph_algorithms/chapter05/gg.txt' 42 | g = Graph(filename) 43 | bi_partition_detection = BiPartitionDetection(g) 44 | print('Is this a bi-partition graph? : {}'.format( 45 | bi_partition_detection.is_bi_partition_graph()), 46 | ) 47 | 48 | filename = 'play_with_graph_algorithms/chapter05/gg2.txt' 49 | g = Graph(filename) 50 | bi_partition_detection = BiPartitionDetection(g) 51 | print('Is this a bi-partition graph? : {}'.format( 52 | bi_partition_detection.is_bi_partition_graph()), 53 | ) 54 | 55 | filename = 'play_with_graph_algorithms/chapter05/gg3.txt' 56 | g = Graph(filename) 57 | bi_partition_detection = BiPartitionDetection(g) 58 | print('Is this a bi-partition graph? : {}'.format( 59 | bi_partition_detection.is_bi_partition_graph()), 60 | ) 61 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter05/cc.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | from play_with_graph_algorithms.chapter02.adj_set import AdjSet as Graph 3 | 4 | 5 | class CC: 6 | 7 | def __init__(self, G): 8 | self._G = G 9 | self._visited = [-1] * G.V 10 | self._ccount = 0 11 | 12 | for v in range(G.V): 13 | if self._visited[v] == -1: 14 | self._bfs(v, v) 15 | self._ccount += 1 16 | 17 | def _bfs(self, s, group): 18 | queue = deque() 19 | queue.append(s) 20 | self._visited[s] = group 21 | 22 | while queue: 23 | v = queue.popleft() 24 | for w in self._G.adj(v): 25 | if self._visited[w] == -1: 26 | queue.append(w) 27 | self._visited[w] = w 28 | 29 | @property 30 | def ccount(self): 31 | return self._ccount 32 | 33 | 34 | if __name__ == '__main__': 35 | filename = 'play_with_graph_algorithms/chapter04/g2.txt' 36 | g = Graph(filename) 37 | cc = CC(g) 38 | print('Number of connected components : {}'.format(cc.ccount)) 39 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter05/cycle_detection.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | from play_with_graph_algorithms.chapter02.adj_set import AdjSet as Graph 3 | 4 | 5 | class CycleDetection: 6 | 7 | def __init__(self, G): 8 | self._G = G 9 | self._visited = [False] * G.V 10 | # pre的意义是当前节点的父亲节点是谁 11 | self._pre = [-1] * G.V 12 | self._has_cycle = False 13 | 14 | for v in range(G.V): 15 | if not self._visited[v]: 16 | if self._bfs(v): 17 | self._has_cycle = True 18 | break 19 | 20 | def _bfs(self, s): 21 | queue = deque() 22 | queue.append(s) 23 | self._visited[s] = True 24 | self._pre[s] = s 25 | 26 | while queue: 27 | curr = queue.popleft() 28 | for w in self._G.adj(curr): 29 | if not self._visited[w]: 30 | queue.append(w) 31 | self._visited[w] = True 32 | self._pre[w] = curr 33 | # 如果w已经被访问过了,我们还必须判断,w不是curr的上一个节点 34 | # 正常情况下curr应该是w的上一个节点 35 | # 即pre[w] = curr 36 | # 只能curr指向w 37 | # 不能w指向curr,如果发生了就是有环 38 | elif self._pre[curr] != w: 39 | return True 40 | return False 41 | 42 | @property 43 | def has_cycle(self): 44 | return self._has_cycle 45 | 46 | 47 | if __name__ == '__main__': 48 | filename = 'play_with_graph_algorithms/chapter05/gg.txt' 49 | g = Graph(filename) 50 | cycle_detection = CycleDetection(g) 51 | print('Does this graph has cycle? : {}'.format(cycle_detection.has_cycle)) 52 | 53 | filename = 'play_with_graph_algorithms/chapter05/gg2.txt' 54 | g = Graph(filename) 55 | cycle_detection = CycleDetection(g) 56 | print('Does this graph has cycle? : {}'.format(cycle_detection.has_cycle)) 57 | 58 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter05/g.txt: -------------------------------------------------------------------------------- 1 | 7 6 2 | 0 1 3 | 0 2 4 | 1 3 5 | 1 4 6 | 2 3 7 | 2 6 -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter05/g2.txt: -------------------------------------------------------------------------------- 1 | 7 5 2 | 0 1 3 | 0 2 4 | 1 3 5 | 1 4 6 | 2 6 -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter05/gg.txt: -------------------------------------------------------------------------------- 1 | 7 6 2 | 0 1 3 | 0 2 4 | 1 3 5 | 1 4 6 | 2 3 7 | 2 6 -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter05/gg2.txt: -------------------------------------------------------------------------------- 1 | 4 6 2 | 0 1 3 | 0 2 4 | 0 3 5 | 1 2 6 | 1 3 7 | 2 3 -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter05/gg3.txt: -------------------------------------------------------------------------------- 1 | 4 4 2 | 0 1 3 | 0 3 4 | 1 2 5 | 2 3 -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter05/graph_bfs.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | from play_with_graph_algorithms.chapter02.adj_set import AdjSet as Graph 3 | 4 | 5 | class GraphBFS: 6 | 7 | def __init__(self, G): 8 | self._G = G 9 | self._visited = [False] * G.V 10 | self._order = [] 11 | 12 | for v in range(G.V): 13 | if not self._visited[v]: 14 | self._bfs(v) 15 | 16 | def _bfs(self, s): 17 | queue = deque() 18 | queue.append(s) 19 | self._visited[s] = True 20 | 21 | while queue: 22 | v = queue.popleft() 23 | self._order.append(v) 24 | for w in self._G.adj(v): 25 | if not self._visited[w]: 26 | queue.append(w) 27 | self._visited[w] = True 28 | 29 | def order(self): 30 | return self._order 31 | 32 | 33 | if __name__ == '__main__': 34 | filename = 'play_with_graph_algorithms/chapter04/g1.txt' 35 | g = Graph(filename) 36 | graph_bfs = GraphBFS(g) 37 | print('BFS order : {}'.format(graph_bfs.order())) 38 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter05/single_source_path.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | from play_with_graph_algorithms.chapter02.adj_set import AdjSet as Graph 3 | 4 | class SingleSourcePath: 5 | 6 | def __init__(self, G, s): 7 | self._G = G 8 | self._visited = [False] * G.V 9 | self._s = s 10 | self._pre = [-1] * G.V 11 | 12 | self._bfs(s) 13 | 14 | def _bfs(self, s): 15 | queue = deque() 16 | queue.append(s) 17 | self._visited[s] = True 18 | self._pre[s] = s 19 | 20 | while queue: 21 | v = queue.popleft() 22 | for w in self._G.adj(v): 23 | if not self._visited[w]: 24 | queue.append(w) 25 | self._visited[w] = True 26 | self._pre[w] = v 27 | 28 | def is_connected_to(self, t): 29 | self._G.validate_vertex(t) 30 | # if t is visited for current self._s 31 | # that implies t is in the path of self._s 32 | return self._visited[t] 33 | 34 | def path(self, t): 35 | res = [] 36 | if not self.is_connected_to(t): 37 | return res 38 | curr = t 39 | while curr != self._s: 40 | res.append(curr) 41 | curr = self._pre[curr] 42 | res.append(self._s) 43 | return res[::-1] 44 | 45 | 46 | if __name__ == '__main__': 47 | filename = 'play_with_graph_algorithms/chapter04/g1.txt' 48 | g = Graph(filename) 49 | sspath = SingleSourcePath(g, 0) 50 | print('0 -> 6 : {}'.format(sspath.path(6))) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter05/usss_path.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | from play_with_graph_algorithms.chapter02.adj_set import AdjSet as Graph 3 | 4 | 5 | # Unweighted Single Source Shortest path 6 | class USSSPath: 7 | 8 | def __init__(self, G, s): 9 | self._G = G 10 | self._s = s 11 | self._visited = [False] * G.V 12 | self._pre = [-1] * G.V 13 | self._dis = [-1] * G.V 14 | 15 | self._bfs(s) 16 | 17 | print(', '.join(str(i) for i in self._dis)) 18 | 19 | def _bfs(self, s): 20 | queue = deque() 21 | queue.append(s) 22 | self._visited[s] = True 23 | self._pre[s] = s 24 | self._dis[s] = 0 25 | 26 | while queue: 27 | v = queue.popleft() 28 | for w in self._G.adj(v): 29 | if not self._visited[w]: 30 | queue.append(w) 31 | self._visited[w] = True 32 | self._pre[w] = v 33 | self._dis[w] = self._dis[v] + 1 34 | 35 | def is_connected_to(self, t): 36 | self._G.validate_vertex(t) 37 | # if t is visited for current self._s 38 | # that implies t is in the path of self._s 39 | return self._visited[t] 40 | 41 | def path(self, t): 42 | res = [] 43 | if not self.is_connected_to(t): 44 | return res 45 | curr = t 46 | while curr != self._s: 47 | res.append(curr) 48 | curr = self._pre[curr] 49 | res.append(self._s) 50 | return res[::-1] 51 | 52 | def dis(self, t): 53 | """某个点t到s的steps""" 54 | self._G.validate_vertex(t) 55 | return self._dis[t] 56 | 57 | 58 | if __name__ == '__main__': 59 | filename = 'play_with_graph_algorithms/chapter05/gg.txt' 60 | g = Graph(filename) 61 | sspath = USSSPath(g, 0) 62 | print('0 -> 6 : {}'.format(sspath.path(6))) 63 | print(sspath.dis(6)) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter06/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zwang96-dl/play-with-graph-algorithms/27606571a71d5516eaac2bfdb68b9e98e872639a/play_with_graph_algorithms/chapter06/__init__.py -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter06/lc1020_numbef_of_enclaves.py: -------------------------------------------------------------------------------- 1 | class Solution: 2 | 3 | DIRECTIONS = [(0, 1), (1, 0), (0, -1), (-1, 0)] 4 | 5 | def num_of_enclaves(self, A): 6 | if not A or not A[0]: 7 | return 0 8 | 9 | self._m, self._n = len(A), len(A[0]) 10 | visited = set() 11 | for i in range(self._m): 12 | if A[i][0] == 1: 13 | self._dfs_initial_boundary(A, i, 0, visited) 14 | if A[i][self._n - 1] == 1: 15 | self._dfs_initial_boundary(A, i, self._n - 1, visited) 16 | for j in range(self._n): 17 | if A[0][j] == 1: 18 | self._dfs_initial_boundary(A, 0, j, visited) 19 | if A[self._m - 1][j] == 1: 20 | self._dfs_initial_boundary(A, self._m - 1, j, visited) 21 | 22 | res = 0 23 | for i in range(self._m): 24 | for j in range(self._n): 25 | if A[i][j] == 1: 26 | self._dfs(A, i, j, visited) 27 | res += 1 28 | 29 | for i in range(self._m): 30 | for j in range(self._n): 31 | if A[i][j] == 2: 32 | A[i][j] = 1 33 | 34 | return res 35 | 36 | def _dfs_initial_boundary(self, A, i, j, visited): 37 | A[i][j] = 2 38 | visited.add((i, j)) 39 | for di, dj in self.DIRECTIONS: 40 | newi, newj = i + di, j + dj 41 | if not 0 <= newi < self._m or not 0 <= newj < self._n: 42 | continue 43 | if A[newi][newj] == 0 or A[newi][newj] == 2: 44 | continue 45 | if (newi, newj) in visited: 46 | continue 47 | self._dfs_initial_boundary(A, newi, newj, visited) 48 | 49 | def _dfs(self, A, i, j, visited): 50 | visited.add((i, j)) 51 | for di, dj in self.DIRECTIONS: 52 | newi, newj = i + di, j + dj 53 | if not 0 <= newi < self._m or not 0 <= newj < self._n: 54 | continue 55 | if A[newi][newj] != 1: 56 | continue 57 | if (newi, newj) in visited: 58 | continue 59 | self._dfs(A, newi, newj, visited) 60 | 61 | 62 | if __name__ == '__main__': 63 | sol = Solution() 64 | # data = [[0, 0, 0, 0], [1, 0, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0]] 65 | data = [[0,0,1,1,1,0,1,1,1,0,1],[1,1,1,1,0,1,0,1,1,0,0],[0,1,0,1,1,0,0,0,0,1,0],[1,0,1,1,1,1,1,0,0,0,1],[0,0,1,0,1,1,0,0,1,0,0],[1,0,0,1,1,1,0,0,0,1,1],[0,1,0,1,1,0,0,0,1,0,0],[0,1,1,0,1,0,1,1,1,0,0],[1,1,0,1,1,1,0,0,0,0,0],[1,0,1,1,0,0,0,1,0,0,1]] 66 | print(sol.num_of_enclaves(data)) 67 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter06/lc1034_coloring_a_border.py: -------------------------------------------------------------------------------- 1 | class Solution: 2 | def color_border(self, grid, r0, c0, color): 3 | if not grid or not grid[0]: 4 | return grid 5 | 6 | m, n = len(grid), len(grid[0]) 7 | old_color = grid[r0][c0] 8 | stack = [] 9 | visited = set() 10 | stack.append((r0, c0)) 11 | visited.add((r0, c0)) 12 | 13 | res = [] 14 | while stack: 15 | ci, cj = stack.pop() 16 | for di, dj in [(0, 1), (1, 0), (0, -1), (-1, 0)]: 17 | newi, newj = ci + di, cj + dj 18 | if not 0 <= newi < m or not 0 <= newj < n: 19 | res.append((ci, cj)) 20 | continue 21 | if grid[newi][newj] != old_color: 22 | res.append((ci, cj)) 23 | continue 24 | if (newi, newj) in visited: 25 | continue 26 | stack.append((newi, newj)) 27 | visited.add((newi, newj)) 28 | 29 | for i, j in res: 30 | grid[i][j] = color 31 | 32 | return grid -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter06/lc130_surrounded_regions.py: -------------------------------------------------------------------------------- 1 | class Solution: 2 | 3 | # 主要思路就是先沿着四边向中心扩展,将O变成# 4 | # 然后把所有剩余的O(此时这些O肯定不与边界相连)直接变成X 5 | # 最后将#变回O即可 6 | def solve(self, board): 7 | if not board or not board[0]: 8 | return 9 | 10 | m, n = len(board), len(board[0]) 11 | for i in range(m): 12 | for j in range(n): 13 | if (i in (0, m - 1) or j in (0, n - 1)) and board[i][j] == 'O': 14 | self._dfs(board, i, j) 15 | 16 | for i in range(m): 17 | for j in range(n): 18 | if board[i][j] == 'O': 19 | board[i][j] = 'X' 20 | if board[i][j] == '#': 21 | board[i][j] = 'O' 22 | 23 | def _dfs(self, board, i, j): 24 | board[i][j] = '#' 25 | 26 | m, n = len(board), len(board[0]) 27 | if i > 0 and board[i - 1][j] == 'O': 28 | self._dfs(board, i - 1, j) 29 | if j > 0 and board[i][j - 1] == 'O': 30 | self._dfs(board, i, j - 1) 31 | if i < m - 1 and board[i + 1][j] == 'O': 32 | self._dfs(board, i + 1, j) 33 | if j < n - 1 and board[i][j + 1] == 'O': 34 | self._dfs(board, i, j + 1) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter06/lc200_num_of_islands.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | 4 | class Solution: 5 | 6 | def numIslands(self, grid): 7 | if not grid or not grid[0]: 8 | return 0 9 | 10 | m, n = len(grid), len(grid[0]) 11 | visited = [[False] * n for _ in range(m)] 12 | directions = [(0, 1), (1, 0), (0, -1), (-1, 0)] 13 | res = 0 14 | 15 | for i in range(m): 16 | for j in range(n): 17 | if grid[i][j] == '1' and \ 18 | not visited[i][j]: 19 | res += 1 20 | queue = deque([(i, j)]) 21 | visited[i][j] = True 22 | while queue: 23 | ci, cj = queue.popleft() 24 | for di, dj in directions: 25 | newi, newj = ci + di, cj + dj 26 | if 0 <= newi < m \ 27 | and 0 <= newj < n \ 28 | and grid[newi][newj] == '1' \ 29 | and not visited[newi][newj]: 30 | queue.append((newi, newj)) 31 | visited[newi][newj] = 1 32 | 33 | return res 34 | 35 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter06/lc529_ minesweeper.py: -------------------------------------------------------------------------------- 1 | class Solution: 2 | 3 | _DIRS = [ 4 | (-1, -1), (-1, 0), (-1, 1), (0, -1), 5 | (0, 1), (1, -1), (1, 0), (1, 1), 6 | ] 7 | 8 | def update_board(self, board, click): 9 | if not board or not board[0]: 10 | return board 11 | 12 | m, n = len(board), len(board[0]) 13 | row, col = click 14 | 15 | if board[row][col] == 'M': 16 | board[row][col] = 'X' 17 | else: 18 | mine_counts = 0 19 | for di, dj in self._DIRS: 20 | newi, newj = row + di, col + dj 21 | if not 0 <= newi < m or not 0 <= newj < n: 22 | continue 23 | if board[newi][newj] == 'M': 24 | mine_counts += 1 25 | if mine_counts > 0: 26 | board[row][col] = str(mine_counts) 27 | else: 28 | board[row][col] = 'B' 29 | for di, dj in self._DIRS: 30 | newi, newj = row + di, col + dj 31 | if not 0 <= newi < m or not 0 <= newj < n: 32 | continue 33 | if board[newi][newj] == 'E': 34 | self.updateBoard(board, [newi, newj]) 35 | 36 | return board 37 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter06/lc695_max_area_of_island.py: -------------------------------------------------------------------------------- 1 | class Solution1: 2 | 3 | DIRECTIONS = [(0, 1), (1, 0), (0, -1), (-1, 0)] 4 | 5 | def max_area_of_island(self, grid): 6 | if not grid or not grid[0]: 7 | return 0 8 | self._R, self._C = len(grid), len(grid[0]) 9 | self._grid = grid 10 | self._G = self._construct_graph() 11 | self._visited = [False] * len(self._G) 12 | 13 | res = 0 14 | for v in range(len(self._G)): 15 | x = v // self._C 16 | y = v % self._C 17 | if not self._visited[v] and self._grid[x][y] == 1: 18 | res = max(res, self._dfs(v)) 19 | return res 20 | 21 | def _construct_graph(self): 22 | g = [set() for _ in range(self._R * self._C)] 23 | 24 | for v in range(len(g)): 25 | x = v // self._C 26 | y = v % self._C 27 | if self._grid[x][y] == 1: 28 | for dx, dy in self.DIRECTIONS: 29 | nextx = x + dx 30 | nexty = y + dy 31 | if self._in_area(nextx, nexty) and self._grid[nextx][nexty] == 1: 32 | next_ = nextx * self._C + nexty 33 | g[v].add(next_) 34 | g[next_].add(v) 35 | return g 36 | 37 | def _in_area(self, x, y): 38 | return 0 <= x < self._R and 0 <= y < self._C 39 | 40 | def _dfs(self, v): 41 | self._visited[v] = True 42 | res = 1 43 | for w in self._G[v]: 44 | if not self._visited[w]: 45 | res += self._dfs(w) 46 | return res 47 | 48 | 49 | class Solution2: 50 | 51 | DIRECTIONS = [(0, 1), (1, 0), (0, -1), (-1, 0)] 52 | 53 | def max_area_of_island(self, grid): 54 | if not grid or not grid[0]: 55 | return 0 56 | self._R, self._C = len(grid), len(grid[0]) 57 | self._grid = grid 58 | self._visited = [[False] * self._C for _ in range(self._R)] 59 | res = 0 60 | for i in range(self._R): 61 | for j in range(self._C): 62 | if not self._visited[i][j] and self._grid[i][j] == 1: 63 | res = max(res, self._dfs(i, j)) 64 | return res 65 | 66 | def _in_area(self, x, y): 67 | return 0 <= x < self._R and 0 <= y < self._C 68 | 69 | def _dfs(self, x, y): 70 | self._visited[x][y] = True 71 | res = 1 72 | for dx, dy in self.DIRECTIONS: 73 | nextx, nexty = x + dx, y + dy 74 | if self._in_area(nextx, nexty) and not self._visited[nextx][nexty] and self._grid[nextx][nexty]: 75 | res += self._dfs(nextx, nexty) 76 | return res 77 | 78 | 79 | class Solution3: 80 | 81 | DIRECTIONS = [(0, 1), (1, 0), (0, -1), (-1, 0)] 82 | 83 | def max_area_of_island(self, grid): 84 | max_area = 0 85 | if not grid or not grid[0]: 86 | return max_area 87 | m, n = len(grid), len(grid[0]) 88 | 89 | for i in range(m): 90 | for j in range(n): 91 | if grid[i][j] == 1: 92 | max_area = max(max_area, self._dfs(grid, i, j, 0)) 93 | return max_area 94 | 95 | # return the max area starting from i, j 96 | def _dfs(self, grid, i, j, curr): 97 | grid[i][j] = 2 98 | curr += 1 99 | 100 | for di, dj in self.DIRECTIONS: 101 | newi, newj = i + di, j + dj 102 | if not 0 <= newi < len(grid) or not 0 <= newj < len(grid[0]): 103 | continue 104 | if grid[newi][newj] != 1: 105 | continue 106 | curr = max(curr, self._dfs(grid, newi, newj, curr)) 107 | 108 | return curr 109 | 110 | 111 | class UF: 112 | 113 | def __init__(self, n): 114 | self._parent = [i for i in range(n)] 115 | self._sz = [1] * n 116 | 117 | def _find(self, p): 118 | if p != self._parent[p]: 119 | self._parent[p] = self._find(self._parent[p]) 120 | return self._parent[p] 121 | 122 | def is_connected(self, p, q): 123 | return self._find(p) == self._find(q) 124 | 125 | def union_elements(self, p, q): 126 | p_root = self._find(p) 127 | q_root = self._find(q) 128 | if p_root == q_root: 129 | return 130 | self._parent[p_root] = q_root 131 | self._sz[q_root] += self._sz[p_root] 132 | 133 | def size(self, p): 134 | return self._sz[self._find(p)] 135 | 136 | 137 | class Solution4: 138 | 139 | DIRECTIONS = [(0, 1), (1, 0), (0, -1), (-1, 0)] 140 | 141 | def max_area_of_island(self, grid): 142 | if not grid or not grid[0]: 143 | return 0 144 | 145 | R, C = len(grid), len(grid[0]) 146 | uf = UF(R * C) 147 | 148 | for v in range(R * C): 149 | x = v // C 150 | y = v % C 151 | if grid[x][y] != 1: 152 | continue 153 | for dx, dy in self.DIRECTIONS: 154 | nextx, nexty = x + dx, y + dy 155 | if not 0 <= nextx < R or not 0 <= nexty < C: 156 | continue 157 | if grid[nextx][nexty] != 1: 158 | continue 159 | next_ = nextx * C + nexty 160 | uf.union_elements(v, next_) 161 | 162 | res = 0 163 | for v in range(R * C): 164 | x = v // C 165 | y = v % C 166 | if grid[x][y] == 1: 167 | res = max(res, uf.size(v)) 168 | return res 169 | 170 | 171 | 172 | if __name__ == '__main__': 173 | data = [ 174 | [1, 1, 0, 0, 0], 175 | [1, 1, 0, 0, 0], 176 | [0, 0, 0, 1, 1], 177 | [0, 0, 0, 1, 1], 178 | ] 179 | 180 | sol1 = Solution1() 181 | print(sol1.max_area_of_island(data)) 182 | 183 | sol2 = Solution2() 184 | print(sol2.max_area_of_island(data)) 185 | 186 | sol3 = Solution3() 187 | print(sol3.max_area_of_island(data)) 188 | 189 | data = [ 190 | [1, 1, 0, 0, 0], 191 | [1, 1, 0, 0, 0], 192 | [0, 0, 0, 1, 1], 193 | [0, 0, 0, 1, 1], 194 | ] 195 | sol4 = Solution4() 196 | print(sol4.max_area_of_island(data)) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter06/lc733_flood_fill.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | 4 | class Solution: 5 | 6 | def flood_fill(self, image, sr, sc, new_color): 7 | 8 | if not image or not image[0]: 9 | return image 10 | 11 | m, n = len(image), len(image[0]) 12 | if not 0 <= sr < m or not 0 <= sc < n: 13 | return image 14 | 15 | old_color = image[sr][sc] 16 | queue = deque() 17 | queue.append((sr, sc)) 18 | 19 | dirs = [(0, 1), (1, 0), (0, -1), (-1, 0)] 20 | 21 | while queue: 22 | ci, cj = queue.popleft() 23 | image[ci][cj] = new_color 24 | for di, dj in dirs: 25 | newi, newj = ci + di, cj + dj 26 | # 这里别忘了如果新点不是旧颜色(old_color) 27 | # 或者已经是新颜色(new_color)时候,都不需要入队 28 | # 尤其是后者,如果已经是新颜色还入队的话 29 | # 会使得死循环(反复将这个点入队只因为它不是旧颜色) 30 | if not 0 <= newi < m or not 0 <= newj < n or \ 31 | image[newi][newj] != old_color or \ 32 | image[newi][newj] == new_color: 33 | continue 34 | queue.append((newi, newj)) 35 | image[newi][newj] = new_color 36 | 37 | return image 38 | 39 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter06/lc785_is_bipartite.py: -------------------------------------------------------------------------------- 1 | class Solution: 2 | 3 | def is_partite(self, graph): 4 | V = len(graph) 5 | visited = [False] * V 6 | colors = [-1] * V 7 | 8 | for v in range(V): 9 | if not visited[v]: 10 | if not self._dfs(v, 0, graph, visited, colors): 11 | return False 12 | return True 13 | 14 | def _dfs(self, v, color, graph, visited, colors): 15 | visited[v] = True 16 | colors[v] = color 17 | 18 | for w in graph[v]: 19 | if not visited[w]: 20 | if not self._dfs(w, 1 - color, graph, visited, colors): 21 | return False 22 | elif colors[v] == colors[w]: 23 | return False 24 | return True 25 | 26 | 27 | if __name__ == '__main__': 28 | sol = Solution() 29 | data = [[1,3], [0,2], [1,3], [0,2]] 30 | print(sol.is_partite(data)) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter06/lc827_making_a_large_island.py: -------------------------------------------------------------------------------- 1 | class UF: 2 | 3 | def __init__(self, n): 4 | self._parent = [i for i in range(n)] 5 | self._sz = [1] * n 6 | self._max = 1 7 | 8 | def _find(self, p): 9 | if p != self._parent[p]: 10 | self._parent[p] = self._find(self._parent[p]) 11 | return self._parent[p] 12 | 13 | def is_connected(self, p, q): 14 | return self._find(p) == self._find(q) 15 | 16 | def union_elements(self, p, q): 17 | p_root = self._find(p) 18 | q_root = self._find(q) 19 | if p_root == q_root: 20 | return 21 | self._parent[p_root] = q_root 22 | self._sz[q_root] += self._sz[p_root] 23 | self._max = max(self._max, self._sz[q_root]) 24 | 25 | def size(self, p): 26 | return self._sz[self._find(p)] 27 | 28 | def max(self): 29 | return self._max 30 | 31 | 32 | class Solution: 33 | def largest_island(self, grid): 34 | if not grid or not grid[0]: 35 | return 0 36 | 37 | m, n = len(grid), len(grid[0]) 38 | uf = UF(m * n) 39 | directions = [(0, 1), (1, 0), (0, -1), (-1, 0)] 40 | for v in range(m * n): 41 | i, j = v // m, v % n 42 | if grid[i][j] != 1: 43 | continue 44 | for di, dj in directions: 45 | newi, newj = i + di, j + dj 46 | if not 0 <= newi < m or not 0 <= newj < n: 47 | continue 48 | if grid[newi][newj] != 1: 49 | continue 50 | next_ = newi * n + newj 51 | uf.union_elements(v, next_) 52 | 53 | res = -2 ** 31 54 | for i in range(m): 55 | for j in range(n): 56 | if grid[i][j] != 0: 57 | continue 58 | surrounded_parents = set() 59 | for di, dj in directions: 60 | newi, newj = i + di, j + dj 61 | if not 0 <= newi < m or not 0 <= newj < n: 62 | continue 63 | if grid[newi][newj] == 0: 64 | continue 65 | surrounded_parents.add(uf._find(newi * n + newj)) 66 | temp = 0 67 | for each in surrounded_parents: 68 | temp += uf.size(each) 69 | res = max(res, temp + 1) 70 | 71 | return res if res != -2 ** 31 else uf.max() 72 | 73 | 74 | if __name__ == '__main__': 75 | sol = Solution() 76 | data = [[1, 1], [1, 0]] 77 | print(sol.largestIsland(data)) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter07/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zwang96-dl/play-with-graph-algorithms/27606571a71d5516eaac2bfdb68b9e98e872639a/play_with_graph_algorithms/chapter07/__init__.py -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter07/cross_river.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | 4 | class Solution: 5 | 6 | def cross_river(self, begin): 7 | queue = deque() 8 | visited = dict() 9 | queue.append(begin) 10 | visited[''.join(str(i) for i in begin)] = None 11 | 12 | while queue: 13 | curr = queue.popleft() 14 | for i in range(4): 15 | if curr[0] != curr[i]: 16 | continue 17 | next_ = curr[:] 18 | next_[0] = 1 - curr[0] 19 | next_[i] = 1 - curr[0] 20 | new_state = ''.join(str(i) for i in next_) 21 | if new_state not in visited: 22 | if ( 23 | new_state[1] == new_state[2] and new_state[0] != new_state[1] or 24 | new_state[2] == new_state[3] and new_state[0] != new_state[2] 25 | ): 26 | continue 27 | queue.append(next_) 28 | visited[new_state] = ''.join(str(i) for i in curr) 29 | if new_state == '1111': 30 | break 31 | 32 | path = [] 33 | curr = '1111' 34 | path.append(curr) 35 | while visited.get(curr): 36 | path.append(visited.get(curr)) 37 | curr = visited.get(curr) 38 | return path[::-1] 39 | 40 | 41 | if __name__ == '__main__': 42 | # initial state: 43 | begin = [0, 0, 0, 0] 44 | sol = Solution() 45 | print(sol.cross_river(begin)) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter07/lc1091_shortest_path_in_binary_matrix.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | 4 | class Solution: 5 | 6 | dirs = [ 7 | [-1, 0], 8 | [-1, 1], 9 | [0, 1], 10 | [1, 1], 11 | [1, 0], 12 | [1, -1], 13 | [0, -1], 14 | [-1, -1], 15 | ] 16 | 17 | def shortest_path_binary_matrix(self, grid): 18 | if not grid or not grid[0]: 19 | return -1 20 | 21 | self._R, self._C = len(grid), len(grid[0]) 22 | visited = [[False] * self._C for _ in range(self._R)] 23 | dis = [[0] * self._C for _ in range(self._R)] 24 | 25 | if grid[0][0] == 1: 26 | return -1 27 | 28 | if self._R == 0 and self._C == 0: 29 | return 1 30 | 31 | queue = deque() 32 | queue.append(0) 33 | visited[0][0] = True 34 | # dis saves how many optimized points to get here 35 | dis[0][0] = 1 36 | 37 | while queue: 38 | cur = queue.popleft() 39 | curx, cury = cur // self._C, cur % self._C 40 | for dx, dy in self.dirs: 41 | nextx = curx + dx 42 | nexty = cury + dy 43 | if self._in_area(nextx, nexty) and not visited[nextx][nexty] and grid[nextx][nexty] == 0: 44 | queue.append(nextx * self._C + nexty) 45 | visited[nextx][nexty] = True 46 | dis[nextx][nexty] = dis[curx][cury] + 1 47 | if nextx == self._R - 1 and nexty == self._C - 1: 48 | return dis[nextx][nexty] 49 | 50 | return -1 51 | 52 | 53 | def _in_area(self, x, y): 54 | return 0 <= x < self._R and 0 <= y < self._C 55 | 56 | 57 | if __name__ == '__main__': 58 | data = [[0,0,0],[1,1,0],[1,1,0]] 59 | sol = Solution() 60 | print(sol.shortest_path_binary_matrix(data)) 61 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter07/lc752_open_the_lock.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | 4 | class Solution: 5 | 6 | def open_lock(self, deadends, target): 7 | deadends = set(deadends) 8 | 9 | if target in deadends: 10 | return -1 11 | 12 | if '0000' in deadends: 13 | return -1 14 | 15 | if '0000' == target: 16 | return 0 17 | 18 | queue = deque() 19 | queue.append('0000') 20 | visited = dict() 21 | visited['0000'] = 0 22 | 23 | while queue: 24 | curs = queue.popleft() 25 | nexts = [] 26 | for i in range(4): 27 | int_ch = int(curs[i]) 28 | new_int_ch1 = (int_ch + 1) % 10 29 | new_int_ch2 = (int_ch + 9) % 10 30 | nexts.append(curs[:i] + str(new_int_ch1) + curs[i + 1:]) 31 | nexts.append(curs[:i] + str(new_int_ch2) + curs[i + 1:]) 32 | 33 | for next_ in nexts: 34 | if next_ not in deadends and visited.get(next_) is None: 35 | queue.append(next_) 36 | visited[next_] = visited[curs] + 1 37 | if next_ == target: 38 | return visited[next_] 39 | 40 | return -1 41 | 42 | 43 | if __name__ == '__main__': 44 | sol = Solution() 45 | data = [] 46 | print(sol.open_lock(['0201','0101','0102','1212','2002'], target = '0202')) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter07/lc773_sliding_puzzle.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | 4 | class Solution: 5 | def sliding_puzzle(self, board): 6 | """ 7 | :type board: List[List[int]] 8 | :rtype: int 9 | """ 10 | # 标准BFS题目 11 | m, n = len(board), len(board[0]) 12 | target = '123450' 13 | # 核心: dirs数组的第i个元素表示它可以跟周围哪些位置交换(一维数组) 14 | # 比如0号下标,只能和右边的1下标和下面的3下标交换 15 | dirs = [ 16 | [1, 3], 17 | [0, 2, 4], 18 | [1, 5], 19 | [0, 4], 20 | [1, 3, 5], 21 | [2, 4], 22 | ] 23 | 24 | # 初始化开始的状态 25 | start = '' 26 | for i in range(m): 27 | for j in range(n): 28 | start += str(board[i][j]) 29 | 30 | queue = deque() 31 | queue.append(start) 32 | visited = set() 33 | 34 | res = 0 35 | while queue: 36 | # 注意这里重点要分层遍历才能更新res!!!! 37 | # 所以要for循环而不是单纯的每次popleft 38 | q_len = len(queue) 39 | for _ in range(q_len): 40 | curr = queue.popleft() 41 | if curr == target: 42 | return res 43 | zero_inx = curr.find('0') 44 | for next_pos in dirs[zero_inx]: 45 | # python里面string是immutable 46 | # 这里只好先转成list处理 47 | curr_list = list(curr) 48 | curr_list[next_pos], curr_list[zero_inx] = curr_list[zero_inx], curr_list[next_pos] 49 | new_str = ''.join(curr_list) 50 | if new_str in visited: 51 | continue 52 | queue.append(new_str) 53 | visited.add(new_str) 54 | res += 1 55 | 56 | # 遍历完了queue都没有找到解答 57 | # 说明做不到,只能返回-1 58 | return -1 59 | 60 | 61 | if __name__ == '__main__': 62 | sol = Solution() 63 | data =[[4, 1, 2],[5, 0, 3]] 64 | print(sol.sliding_puzzle(data)) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter07/water_puzzle.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | 4 | class WaterPuzzle: 5 | 6 | def __init__(self): 7 | queue = deque() 8 | visited = [False] * 100 9 | self._pre = [0] * 100 10 | self._end = -1 11 | 12 | queue.append(0) 13 | visited[0] = True 14 | while queue: 15 | cur = queue.popleft() 16 | a, b = cur // 10, cur % 10 17 | # max a = 5, max b = 3 18 | nexts = [] 19 | nexts.append(5 * 10 + b) 20 | nexts.append(a * 10 + 3) 21 | nexts.append(0 * 10 + b) 22 | nexts.append(a * 10 + 0) 23 | 24 | # from a, minus x 25 | x = min(a, 3 - b) 26 | nexts.append((a - x) * 10 + (b + x)) 27 | # from b, minux y 28 | y = min(5 - a, b) 29 | nexts.append((a + y) * 10 + (b - y)) 30 | 31 | for next_ in nexts: 32 | if not visited[next_]: 33 | queue.append(next_) 34 | visited[next_] = True 35 | self._pre[next_] = cur 36 | if next_ // 10 == 4 or next_ % 10 == 4: 37 | self._end = next_ 38 | 39 | def result(self): 40 | res = [] 41 | if self._end == -1: 42 | return [] 43 | cur = self._end 44 | while cur != 0: 45 | res.append(cur) 46 | cur = self._pre[cur] 47 | res.append(0) 48 | return res[::-1] 49 | 50 | if __name__ == '__main__': 51 | prob = WaterPuzzle() 52 | print(prob.result()) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter08/find_bridges.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | 3 | from play_with_graph_algorithms.chapter02.adj_set import AdjSet as Graph 4 | from play_with_graph_algorithms.chapter03.graph_dfs import GraphDFS 5 | 6 | 7 | class Edge(namedtuple('Edge', ['v', 'w'])): 8 | 9 | def __str__(self): 10 | return '{}-{}'.format(self.v, self.w) 11 | 12 | def __repr__(self): 13 | return self.__str__() 14 | 15 | 16 | class FindBridges: 17 | 18 | def __init__(self, G): 19 | self._G = G 20 | self._visited = [False] * G.V 21 | # _ord记录每个点的访问顺序(根据_cnt的值,而且_cnt的值每访问完一个点自加1) 22 | self._ord = [-1] * G.V 23 | # _low记录每个点在当前所有已经访问过的点中(即_ord值比当前的点的_ord低) 24 | # 能够到达的最低的_ord值 25 | self._low = [2 ** 31 - 1] * G.V 26 | self._cnt = 0 27 | self._res = [] 28 | # 遍历所有的点,相当于遍历图中所有可能存在的联通块 29 | for v in range(G.V): 30 | if not self._visited[v]: 31 | self._dfs(v, v) 32 | 33 | def _dfs(self, v, parent): 34 | self._visited[v] = True 35 | # 设置初始值 36 | self._ord[v] = self._cnt 37 | self._low[v] = self._ord[v] 38 | self._cnt += 1 39 | for w in self._G.adj(v): 40 | if not self._visited[w]: 41 | self._dfs(w, v) 42 | self._low[v] = min(self._low[v], self._low[w]) 43 | # 判断v-w是不是桥 44 | if self._low[w] > self._ord[v]: 45 | self._res.append(Edge(v=v, w=w)) 46 | # v w是环上的一条边 47 | # 肯定不是环 48 | # 此时可能需要更新下v的low值 49 | elif w != parent: 50 | self._low[v] = min(self._low[v], self._low[w]) 51 | 52 | @property 53 | def result(self): 54 | return self._res 55 | 56 | 57 | if __name__ == '__main__': 58 | filename = 'play_with_graph_algorithms/chapter08/g.txt' 59 | g = Graph(filename) 60 | find_bridegs = FindBridges(g) 61 | print(find_bridegs.result) 62 | 63 | filename = 'play_with_graph_algorithms/chapter08/g2.txt' 64 | g = Graph(filename) 65 | find_bridegs = FindBridges(g) 66 | print(find_bridegs.result) 67 | 68 | filename = 'play_with_graph_algorithms/chapter08/tree.txt' 69 | g = Graph(filename) 70 | find_bridegs = FindBridges(g) 71 | print(find_bridegs.result) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter08/find_cut_points.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | 3 | from play_with_graph_algorithms.chapter02.adj_set import AdjSet as Graph 4 | from play_with_graph_algorithms.chapter03.graph_dfs import GraphDFS 5 | 6 | 7 | class FindCutPoints: 8 | 9 | def __init__(self, G): 10 | self._G = G 11 | self._visited = [False] * G.V 12 | # _ord记录每个点的访问顺序(根据_cnt的值,而且_cnt的值每访问完一个点自加1) 13 | self._ord = [-1] * G.V 14 | # _low记录每个点在当前所有已经访问过的点中(即_ord值比当前的点的_ord低) 15 | # 能够到达的最低的_ord值 16 | self._low = [2 ** 31 - 1] * G.V 17 | self._cnt = 0 18 | self._res = set() 19 | # 遍历所有的点,相当于遍历图中所有可能存在的联通块 20 | for v in range(G.V): 21 | if not self._visited[v]: 22 | self._dfs(v, v) 23 | 24 | def _dfs(self, v, parent): 25 | self._visited[v] = True 26 | # 设置初始值 27 | self._ord[v] = self._cnt 28 | self._low[v] = self._ord[v] 29 | self._cnt += 1 30 | 31 | child = 0 32 | for w in self._G.adj(v): 33 | if not self._visited[w]: 34 | self._dfs(w, v) 35 | self._low[v] = min(self._low[v], self._low[w]) 36 | if v != parent and self._low[w] >= self._ord[v]: 37 | self._res.add(v) 38 | 39 | child += 1 40 | if v == parent and child > 1: 41 | self._res.add(v) 42 | # v w是环上的一条边 43 | # 肯定不是环 44 | # 此时可能需要更新下v的low值 45 | elif w != parent: 46 | self._low[v] = min(self._low[v], self._low[w]) 47 | 48 | @property 49 | def result(self): 50 | return list(self._res) 51 | 52 | 53 | if __name__ == '__main__': 54 | filename = 'play_with_graph_algorithms/chapter08/g.txt' 55 | g = Graph(filename) 56 | find_cut_points = FindCutPoints(g) 57 | print(find_cut_points.result) 58 | 59 | filename = 'play_with_graph_algorithms/chapter08/g2.txt' 60 | g = Graph(filename) 61 | find_cut_points = FindCutPoints(g) 62 | print(find_cut_points.result) 63 | 64 | filename = 'play_with_graph_algorithms/chapter08/tree.txt' 65 | g = Graph(filename) 66 | find_cut_points = FindCutPoints(g) 67 | print(find_cut_points.result) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter08/g.txt: -------------------------------------------------------------------------------- 1 | 7 8 2 | 0 1 3 | 0 2 4 | 1 3 5 | 2 3 6 | 3 5 7 | 4 5 8 | 4 6 9 | 5 6 10 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter08/g2.txt: -------------------------------------------------------------------------------- 1 | 12 16 2 | 0 1 3 | 0 2 4 | 1 3 5 | 2 3 6 | 3 5 7 | 4 5 8 | 4 6 9 | 4 7 10 | 5 6 11 | 6 8 12 | 8 9 13 | 8 10 14 | 8 11 15 | 9 10 16 | 9 11 17 | 10 11 -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter08/tree.txt: -------------------------------------------------------------------------------- 1 | 7 6 2 | 0 1 3 | 0 3 4 | 1 6 5 | 2 3 6 | 2 5 7 | 3 4 8 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter09/__init___.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zwang96-dl/play-with-graph-algorithms/27606571a71d5516eaac2bfdb68b9e98e872639a/play_with_graph_algorithms/chapter09/__init___.py -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter09/g.txt: -------------------------------------------------------------------------------- 1 | 4 5 2 | 0 1 3 | 0 2 4 | 0 3 5 | 1 2 6 | 1 3 -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter09/g2.txt: -------------------------------------------------------------------------------- 1 | 20 30 2 | 0 1 3 | 0 4 4 | 0 13 5 | 1 2 6 | 1 11 7 | 2 9 8 | 2 3 9 | 3 4 10 | 3 7 11 | 4 5 12 | 5 6 13 | 5 14 14 | 6 7 15 | 6 16 16 | 7 8 17 | 8 9 18 | 8 17 19 | 9 10 20 | 10 11 21 | 10 18 22 | 11 12 23 | 12 13 24 | 12 19 25 | 13 14 26 | 14 15 27 | 15 16 28 | 15 19 29 | 16 17 30 | 17 18 31 | 18 19 -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter09/hamilton_loop.py: -------------------------------------------------------------------------------- 1 | from play_with_graph_algorithms.chapter02.adj_set import AdjSet as Graph 2 | 3 | 4 | class HamiltonLoop: 5 | 6 | def __init__(self, G): 7 | self._G = G 8 | self._visited = [False] * G.V 9 | self._pre = [0] * G.V 10 | self._end = -1 11 | self._dfs(0, 0, G.V) 12 | 13 | def _dfs(self, v, parent, left): 14 | self._visited[v] = True 15 | self._pre[v] = parent 16 | left -= 1 17 | if left == 0 and self._G.has_edge(v, 0): 18 | self._end = v 19 | return True 20 | 21 | for w in self._G.adj(v): 22 | if not self._visited[w]: 23 | if self._dfs(w, v, left): 24 | return True 25 | 26 | self._visited[v] = False 27 | 28 | return False 29 | 30 | def result(self): 31 | res = [] 32 | if self._end == -1: 33 | return res 34 | 35 | curr = self._end 36 | while curr != 0: 37 | res.append(curr) 38 | curr = self._pre[curr] 39 | res.append(0) 40 | 41 | return res[::-1] 42 | 43 | 44 | class HamiltonLoopV2: 45 | """With 'visited' bitwise optimization""" 46 | def __init__(self, G): 47 | self._G = G 48 | self._pre = [0] * G.V 49 | self._end = -1 50 | self._dfs(0, 0, 0, G.V) 51 | 52 | def _dfs(self, visited, v, parent, left): 53 | # visited at bit v is 0 54 | visited += (1 << v) 55 | self._pre[v] = parent 56 | left -= 1 57 | if left == 0 and self._G.has_edge(v, 0): 58 | self._end = v 59 | return True 60 | 61 | for w in self._G.adj(v): 62 | if (visited & (1 << w)) == 0: 63 | if self._dfs(visited, w, v, left): 64 | return True 65 | 66 | return False 67 | 68 | def result(self): 69 | res = [] 70 | if self._end == -1: 71 | return res 72 | 73 | curr = self._end 74 | while curr != 0: 75 | res.append(curr) 76 | curr = self._pre[curr] 77 | res.append(0) 78 | 79 | return res[::-1] 80 | 81 | 82 | if __name__ == '__main__': 83 | filename = 'play_with_graph_algorithms/chapter09/g.txt' 84 | graph = Graph(filename) 85 | hamilton_loop = HamiltonLoop(graph) 86 | print(hamilton_loop.result()) 87 | 88 | filename = 'play_with_graph_algorithms/chapter09/g2.txt' 89 | graph = Graph(filename) 90 | hamilton_loop = HamiltonLoop(graph) 91 | print(hamilton_loop.result()) 92 | 93 | filename = 'play_with_graph_algorithms/chapter09/g.txt' 94 | graph = Graph(filename) 95 | hamilton_loop_v2 = HamiltonLoopV2(graph) 96 | print(hamilton_loop_v2.result()) 97 | 98 | filename = 'play_with_graph_algorithms/chapter09/g2.txt' 99 | graph = Graph(filename) 100 | hamilton_loop_v2 = HamiltonLoopV2(graph) 101 | print(hamilton_loop_v2.result()) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter09/hamilton_path.py: -------------------------------------------------------------------------------- 1 | from play_with_graph_algorithms.chapter02.adj_set import AdjSet as Graph 2 | 3 | 4 | class HamiltonPath: 5 | 6 | def __init__(self, G, s): 7 | self._G = G 8 | self._s = s 9 | self._visited = [False] * G.V 10 | self._pre = [0] * G.V 11 | self._end = -1 12 | self._dfs(s, s, G.V) 13 | 14 | def _dfs(self, v, parent, left): 15 | self._visited[v] = True 16 | self._pre[v] = parent 17 | left -= 1 18 | if left == 0: 19 | self._end = v 20 | return True 21 | 22 | for w in self._G.adj(v): 23 | if not self._visited[w]: 24 | if self._dfs(w, v, left): 25 | return True 26 | 27 | self._visited[v] = False 28 | 29 | return False 30 | 31 | def result(self): 32 | res = [] 33 | if self._end == -1: 34 | return res 35 | 36 | curr = self._end 37 | while curr != self._s: 38 | res.append(curr) 39 | curr = self._pre[curr] 40 | res.append(self._s) 41 | 42 | return res[::-1] 43 | 44 | 45 | class HamiltonPathV2: 46 | 47 | def __init__(self, G, s): 48 | self._G = G 49 | self._s = s 50 | self._pre = [0] * G.V 51 | self._end = -1 52 | self._dfs(0, s, s, G.V) 53 | 54 | def _dfs(self, visited, v, parent, left): 55 | visited += (1 << v) 56 | self._pre[v] = parent 57 | left -= 1 58 | if left == 0: 59 | self._end = v 60 | return True 61 | 62 | for w in self._G.adj(v): 63 | if (visited & (1 << w)) == 0: 64 | if self._dfs(visited, w, v, left): 65 | return True 66 | 67 | return False 68 | 69 | def result(self): 70 | res = [] 71 | if self._end == -1: 72 | return res 73 | 74 | curr = self._end 75 | while curr != self._s: 76 | res.append(curr) 77 | curr = self._pre[curr] 78 | res.append(self._s) 79 | 80 | return res[::-1] 81 | 82 | 83 | if __name__ == '__main__': 84 | filename = 'play_with_graph_algorithms/chapter09/g.txt' 85 | graph = Graph(filename) 86 | hamilton_path = HamiltonPath(graph, 0) 87 | print(hamilton_path.result()) 88 | 89 | filename = 'play_with_graph_algorithms/chapter09/g2.txt' 90 | graph = Graph(filename) 91 | hamilton_path = HamiltonPath(graph, 1) 92 | print(hamilton_path.result()) 93 | 94 | filename = 'play_with_graph_algorithms/chapter09/g.txt' 95 | graph = Graph(filename) 96 | hamilton_path_v2 = HamiltonPathV2(graph, 0) 97 | print(hamilton_path_v2.result()) 98 | 99 | filename = 'play_with_graph_algorithms/chapter09/g2.txt' 100 | graph = Graph(filename) 101 | hamilton_path_v2 = HamiltonPathV2(graph, 1) 102 | print(hamilton_path_v2.result()) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter09/lc980_unique_path_III.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | class Solution: 4 | 5 | DIRECTIONS = [(0, 1), (1, 0), (0, -1), (-1, 0)] 6 | 7 | def uniquePathsIII(self, grid: List[List[int]]) -> int: 8 | # 遍历所有能走的点一次 9 | # 有多少种走法 10 | self._grid = grid 11 | self._R = len(grid) 12 | self._C = len(grid[0]) 13 | self._visited = [[False] * self._C for _ in range(self._R)] 14 | self._left = self._R * self._C 15 | 16 | for i in range(self._R): 17 | for j in range(self._C): 18 | if self._grid[i][j] == 1: 19 | self._start = i * self._C + j 20 | self._grid[i][j] = 0 21 | elif self._grid[i][j] == 2: 22 | self._end = i * self._C + j 23 | self._grid[i][j] = 0 24 | elif self._grid[i][j] == -1: 25 | self._left -= 1 26 | 27 | return self._dfs(self._start, self._left) 28 | 29 | def _dfs(self, v, left): 30 | x = v // self._C 31 | y = v % self._C 32 | self._visited[x][y] = True 33 | left -= 1 34 | 35 | if left == 0 and v == self._end: 36 | self._visited[x][y] = False 37 | return 1 38 | 39 | res = 0 40 | for dx, dy in self.DIRECTIONS: 41 | nextx = x + dx 42 | nexty = y + dy 43 | if self._in_area(nextx, nexty) and self._grid[nextx][nexty] == 0 and not self._visited[nextx][nexty]: 44 | res += self._dfs(nextx * self._C + nexty, left) 45 | 46 | self._visited[x][y] = False 47 | return res 48 | 49 | def _in_area(self, x, y): 50 | return x >= 0 and x < self._R and y >= 0 and y < self._C 51 | 52 | 53 | class SolutionV2: 54 | 55 | DIRECTIONS = [(0, 1), (1, 0), (0, -1), (-1, 0)] 56 | 57 | def uniquePathsIII(self, grid: List[List[int]]) -> int: 58 | # 遍历所有能走的点一次 59 | # 有多少种走法 60 | self._grid = grid 61 | self._R = len(grid) 62 | self._C = len(grid[0]) 63 | self._left = self._R * self._C 64 | # 第一个维度是visited有多少种状态的组合 65 | # 第二个维度是visited这个点有多少种选择 66 | self._memo = [ 67 | [-1] * (self._R * self._C) 68 | for _ in range(1 << self._R * self._C) 69 | ] 70 | 71 | for i in range(self._R): 72 | for j in range(self._C): 73 | if self._grid[i][j] == 1: 74 | self._start = i * self._C + j 75 | self._grid[i][j] = 0 76 | elif self._grid[i][j] == 2: 77 | self._end = i * self._C + j 78 | self._grid[i][j] = 0 79 | elif self._grid[i][j] == -1: 80 | self._left -= 1 81 | 82 | visited = 0 83 | return self._dfs(visited, self._start, self._left) 84 | 85 | def _dfs(self, visited, v, left): 86 | if self._memo[visited][v] != -1: 87 | return self._memo[visited][v] 88 | visited += (1 << v) 89 | left -= 1 90 | 91 | if left == 0 and v == self._end: 92 | self._memo[visited][v] = 1 93 | return 1 94 | 95 | x = v // self._C 96 | y = v % self._C 97 | res = 0 98 | for dx, dy in self.DIRECTIONS: 99 | nextx = x + dx 100 | nexty = y + dy 101 | next_ = nextx * self._C + nexty 102 | if self._in_area(nextx, nexty) and self._grid[nextx][nexty] == 0 and (visited & (1 << next_) == 0): 103 | res += self._dfs(visited, next_, left) 104 | 105 | self._memo[visited][v] = res 106 | return res 107 | 108 | def _in_area(self, x, y): 109 | return x >= 0 and x < self._R and y >= 0 and y < self._C 110 | 111 | 112 | if __name__ == '__main__': 113 | sol = Solution() 114 | grid = [[1,0,0,0],[0,0,0,0],[0,0,2,-1]] 115 | print(sol.uniquePathsIII(grid)) 116 | 117 | sol = SolutionV2() 118 | grid = [[1,0,0,0],[0,0,0,0],[0,0,2,-1]] 119 | print(sol.uniquePathsIII(grid)) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter10/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zwang96-dl/play-with-graph-algorithms/27606571a71d5516eaac2bfdb68b9e98e872639a/play_with_graph_algorithms/chapter10/__init__.py -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter10/euler_loop.py: -------------------------------------------------------------------------------- 1 | from copy import copy 2 | 3 | from play_with_graph_algorithms.chapter02.adj_set import AdjSet as Graph 4 | from play_with_graph_algorithms.chapter04.cc import CC 5 | 6 | 7 | class EulerLoop: 8 | def __init__(self, G): 9 | self._G = G 10 | 11 | def has_euler_loop(self): 12 | cc = CC(self._G) 13 | if cc.ccount > 1: 14 | return False 15 | 16 | for v in range(self._G.V()): 17 | if self._G.degree(v) % 2 != 0: 18 | return False 19 | 20 | return True 21 | 22 | def result(self): 23 | res = [] 24 | if not self.has_euler_loop: 25 | return res 26 | g = copy(self._G) 27 | 28 | stack = [] 29 | currv = 0 30 | stack.append(currv) 31 | 32 | while stack: 33 | if g.degree(currv) != 0: 34 | stack.append(currv) 35 | # 模拟一个iterator 36 | w = next(self._iter_next_adj(g.adj(currv))) 37 | g.remove_edge(currv, w) 38 | currv = w 39 | else: 40 | # 此时说明找到了一个环 41 | res.append(currv) 42 | currv = stack.pop() 43 | 44 | return res 45 | 46 | def _iter_next_adj(self, adj): 47 | yield from sorted(adj) 48 | 49 | 50 | if __name__ == '__main__': 51 | filename = 'play_with_graph_algorithms/chapter10/g.txt' 52 | g = Graph(filename) 53 | eluer_loop = EulerLoop(g) 54 | print(eluer_loop.result()) 55 | 56 | filename = 'play_with_graph_algorithms/chapter10/g2.txt' 57 | g = Graph(filename) 58 | eluer_loop = EulerLoop(g) 59 | print(eluer_loop.result()) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter10/g.txt: -------------------------------------------------------------------------------- 1 | 5 6 2 | 0 1 3 | 0 2 4 | 1 2 5 | 2 3 6 | 2 4 7 | 3 4 -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter10/g2.txt: -------------------------------------------------------------------------------- 1 | 11 15 2 | 0 1 3 | 0 3 4 | 1 2 5 | 1 4 6 | 1 5 7 | 2 5 8 | 3 4 9 | 4 5 10 | 4 6 11 | 5 7 12 | 6 7 13 | 7 8 14 | 7 9 15 | 8 10 16 | 9 10 -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter11/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zwang96-dl/play-with-graph-algorithms/27606571a71d5516eaac2bfdb68b9e98e872639a/play_with_graph_algorithms/chapter11/__init__.py -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter11/g.txt: -------------------------------------------------------------------------------- 1 | 7 12 2 | 0 1 2 3 | 0 3 7 4 | 0 5 2 5 | 1 2 1 6 | 1 3 4 7 | 1 4 3 8 | 1 5 5 9 | 2 4 4 10 | 2 5 4 11 | 3 4 1 12 | 3 6 5 13 | 4 6 7 -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter11/kruskal.py: -------------------------------------------------------------------------------- 1 | from play_with_graph_algorithms.chapter11.weighted_edge import WeightedEdge 2 | from play_with_graph_algorithms.chapter11.weighted_graph import WeightedGraph 3 | from play_with_graph_algorithms.chapter11.uf import UF 4 | from play_with_graph_algorithms.chapter04.cc import CC 5 | 6 | 7 | class Kruskal: 8 | 9 | def __init__(self, G): 10 | self._G = G 11 | self._mst = [] 12 | 13 | cc = CC(G) 14 | if (cc.ccount > 1): 15 | return 16 | 17 | # Kruskal 18 | edges = [] 19 | for v in range(G.V): 20 | for w in G.adj(v): 21 | if v < w: 22 | edges.append(WeightedEdge(v, w, G.get_weight(v, w))) 23 | 24 | edges = sorted(edges, key=lambda x: x.weight) 25 | uf = UF(G.V) 26 | for edge in edges: 27 | v = edge.v 28 | w = edge.w 29 | weight = edge.weight 30 | if not uf.is_connected(v, w): 31 | self._mst.append(edge) 32 | uf.union_elements(v, w) 33 | 34 | def result(self): 35 | return self._mst 36 | 37 | 38 | if __name__ == '__main__': 39 | filename = 'play_with_graph_algorithms/chapter11/g.txt' 40 | g = WeightedGraph(filename) 41 | kruskal = Kruskal(g) 42 | print(kruskal.result()) 43 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter11/prim.py: -------------------------------------------------------------------------------- 1 | from heapq import heappush 2 | from heapq import heappop 3 | 4 | from play_with_graph_algorithms.chapter11.weighted_graph import WeightedGraph 5 | from play_with_graph_algorithms.chapter11.weighted_edge import WeightedEdge 6 | from play_with_graph_algorithms.chapter04.cc import CC 7 | 8 | 9 | class Prim: 10 | 11 | def __init__(self, G): 12 | 13 | self._G = G 14 | self._mst = [] 15 | 16 | cc = CC(G) 17 | if (cc.ccount > 1): 18 | return 19 | 20 | visited = [False] * self._G.V 21 | visited[0] = True 22 | pq = [] 23 | 24 | for w in self._G.adj(0): 25 | heappush(pq, [self._G.get_weight(0, w), 0, w]) 26 | 27 | while pq: 28 | weight, v, w = heappop(pq) 29 | if visited[v] and visited[w]: 30 | continue 31 | self._mst.append(WeightedEdge(v, w, weight)) 32 | newv = w if visited[v] else v 33 | visited[newv] = True 34 | for w in self._G.adj(newv): 35 | if not visited[w]: 36 | heappush(pq, [self._G.get_weight(newv, w), newv, w]) 37 | 38 | def result(self): 39 | return self._mst 40 | 41 | 42 | if __name__ == "__main__": 43 | filename = 'play_with_graph_algorithms/chapter11/g.txt' 44 | g = WeightedGraph(filename) 45 | prim = Prim(g) 46 | print(prim.result()) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter11/uf.py: -------------------------------------------------------------------------------- 1 | class UFBase: 2 | def is_connected(self, p, q): 3 | raise NotImplementedError 4 | 5 | def union_elements(self, p, q): 6 | raise NotImplementedError 7 | 8 | def get_size(self): 9 | raise NotImplementedError 10 | 11 | 12 | # Sixth version - path compression 13 | # https://github.com/nicemayi/play-with-data-structures/blob/master/chapter_11_UnionFind/union_find6.py 14 | class UF(UFBase): 15 | def __init__(self, size): 16 | # rank[i]表示以i为根的树的层数(深度) 17 | self._rank = [1] * size 18 | self._parent = [i for i in range(size)] 19 | 20 | def get_size(self): 21 | return len(self._parent) 22 | 23 | def _find(self, p): 24 | if p < 0 or p >= len(self._parent): 25 | raise ValueError('p is out of bound.') 26 | if p != self._parent[p]: 27 | # 递归实现路径压缩 28 | # 全部深度为1 29 | self._parent[p] = self._find(self._parent[p]) 30 | return self._parent[p] 31 | 32 | def is_connected(self, p, q): 33 | return self._find(p) == self._find(q) 34 | 35 | def union_elements(self, p, q): 36 | p_root = self._find(p) 37 | q_root = self._find(q) 38 | if p_root == q_root: 39 | return 40 | # 根据两个元素所在树的rank不同判断合并方向 41 | # 将rank低的集合合并到rank高的集合上(merge) 42 | if self._rank[p_root] < self._rank[q_root]: 43 | self._parent[p_root] = q_root 44 | elif self._rank[p_root] > self._rank[q_root]: 45 | self._parent[q_root] = p_root 46 | else: 47 | self._parent[q_root] = p_root 48 | # 想象两个点(rank 1)的合并,肯定结果是一个是另个一个的孩子 49 | # 所以rank会加1 50 | self._rank[p_root] += 1 -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter11/weighted_edge.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | 3 | 4 | class WeightedEdge(namedtuple('WeightedEdge', ['v', 'w', 'weight'])): 5 | 6 | def __str__(self): 7 | return '({}-{}: {})'.format(self.v, self.w, self.weight) 8 | 9 | def __repr__(self): 10 | return self.__str__() -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter11/weighted_graph.py: -------------------------------------------------------------------------------- 1 | class WeightedGraph: 2 | 3 | """Suppose to use RB tree, but no TreeSet/TreeMap in vanilla Python, using 4 | dict instead. 5 | """ 6 | 7 | def __init__(self, filename): 8 | self._filename = filename 9 | lines = None 10 | with open(filename, 'r') as f: 11 | lines = f.readlines() 12 | if not lines: 13 | raise ValueError('Expected something from input file!') 14 | 15 | # lines[0] -> V E 16 | self._V, self._E = (int(i) for i in lines[0].split()) 17 | 18 | if self._V < 0: 19 | raise ValueError('V must be non-negative') 20 | 21 | if self._E < 0: 22 | raise ValueError('E must be non-negative') 23 | 24 | # size V list of dictionaries 25 | self._adj = [dict() for _ in range(self._V)] 26 | for each_line in lines[1:]: 27 | a, b, weight = (int(i) for i in each_line.split()) 28 | self.validate_vertex(a) 29 | self.validate_vertex(b) 30 | 31 | if a == b: 32 | raise ValueError('Self-Loop is detected!') 33 | 34 | if b in self._adj[a]: 35 | raise ValueError('Paralles edges are detected!') 36 | 37 | self._adj[a][b] = weight 38 | self._adj[b][a] = weight 39 | 40 | @property 41 | def V(self): 42 | return self._V 43 | 44 | @property 45 | def E(self): 46 | return self._E 47 | 48 | def has_edge(self, v, w): 49 | self.validate_vertex(v) 50 | self.validate_vertex(w) 51 | return w in self._adj[v] 52 | 53 | def adj(self, v): 54 | self.validate_vertex(v) 55 | return list(self._adj[v].keys()) 56 | 57 | def get_weight(self, v, w): 58 | if self.has_edge(v, w): 59 | return self._adj[v][w] 60 | raise ValueError('No edge {}-{}'.format(v, w)) 61 | 62 | def degree(self, v): 63 | return len(self.adj(v)) 64 | 65 | def remove_edge(self, v, w): 66 | self.validate_vertex(v) 67 | self.validate_vertex(w) 68 | if w in self._adj[v]: 69 | self._adj[v].pop(w) 70 | if v in self._adj[w]: 71 | self._adj[w].pop(v) 72 | 73 | def validate_vertex(self, v): 74 | if v < 0 or v >= self._V: 75 | raise ValueError('vertex ' + str(v) + ' is invalid') 76 | 77 | def __str__(self): 78 | res = ['V = {}, E = {}'.format(self._V, self._E)] 79 | for v in range(self._V): 80 | res.append( 81 | '{}: {}'.format( 82 | v, 83 | ' '.join('{}({})'.format(w, self._adj[v][w]) for w in self._adj[v]), 84 | ), 85 | ) 86 | return '\n'.join(res) 87 | 88 | def __repr__(self): 89 | return self.__str__() 90 | 91 | def __copy__(self): 92 | return WeightedGraph(self._filename) 93 | 94 | 95 | if __name__ == '__main__': 96 | filename = 'play_with_graph_algorithms/chapter11/g.txt' 97 | w_graph = WeightedGraph(filename) 98 | print(w_graph) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter12/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zwang96-dl/play-with-graph-algorithms/27606571a71d5516eaac2bfdb68b9e98e872639a/play_with_graph_algorithms/chapter12/__init__.py -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter12/bellman_ford.py: -------------------------------------------------------------------------------- 1 | from play_with_graph_algorithms.chapter11.weighted_graph import WeightedGraph 2 | 3 | 4 | MAX_INT = 2 ** 31 - 1 5 | 6 | class BellmanFord: 7 | 8 | def __init__(self, G, s): 9 | self._G = G 10 | self._G.validate_vertex(s) 11 | self._s = s 12 | self._dis = [MAX_INT] * self._G.V 13 | self._dis[0] = 0 14 | self._pre = [-1] * self._G.V 15 | self._has_negative_circle = False 16 | 17 | for _ in range(self._G.V): 18 | for v in range(self._G.V): 19 | for w in self._G.adj(v): 20 | if self._dis[v] == MAX_INT: 21 | continue 22 | new_dis = self._dis[v] + self._G.get_weight(v, w) 23 | if new_dis < self._dis[w]: 24 | # 在更新距离的时候更新pre数组 25 | self._pre[w] = v 26 | self._dis[w] = new_dis 27 | 28 | # 检查是否有负权环 29 | for v in range(self._G.V): 30 | for w in self._G.adj(v): 31 | if self._dis[v] == MAX_INT: 32 | continue 33 | new_dis = self._dis[v] + self._G.get_weight(v, w) 34 | if new_dis < self._dis[w]: 35 | self._has_negative_circle = True 36 | break 37 | 38 | def has_neg(self): 39 | return self._has_negative_circle 40 | 41 | def is_connected_to(self, v): 42 | self._G.validate_vertex(v) 43 | return self._dis[v] != MAX_INT 44 | 45 | def dist_to(self, v): 46 | self._G.validate_vertex(v) 47 | if self._has_negative_circle: 48 | raise ValueError('Exist negative circle.') 49 | return self._dis[v] 50 | 51 | def path(self, t): 52 | res = [] 53 | 54 | if not self.is_connected_to(t): 55 | return res 56 | 57 | curr = t 58 | while curr != self._s: 59 | res.append(curr) 60 | curr = self._pre[curr] 61 | res.append(self._s) 62 | return res[::-1] 63 | 64 | if __name__ == '__main__': 65 | filename = 'play_with_graph_algorithms/chapter12/g.txt' 66 | g = WeightedGraph(filename) 67 | bf = BellmanFord(g, s=0) 68 | 69 | if not bf.has_neg(): 70 | strings = [] 71 | for v in range(g.V): 72 | strings.append(bf.dist_to(v)) 73 | print(bf.path(v)) 74 | print(' '.join(str(i) for i in strings)) 75 | else: 76 | print('exist negative circle') -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter12/dijkstra.py: -------------------------------------------------------------------------------- 1 | from play_with_graph_algorithms.chapter11.weighted_graph import WeightedGraph 2 | 3 | 4 | MAX_VALUE = 2 ** 31 - 1 5 | 6 | class Dijkstra: 7 | 8 | def __init__(self, G, s): 9 | self._G = G 10 | self._G.validate_vertex(s) 11 | self._s = s 12 | 13 | self._dis = [MAX_VALUE - 1] * self._G.V 14 | self._dis[0] = 0 15 | # 是否已经确定了点是全局最小距离 16 | self._visited = [False] * self._G.V 17 | 18 | while True: 19 | currdis = MAX_VALUE - 1 20 | curr = -1 21 | # 第一段逻辑:找最小值 22 | # 第一次循环找到的点肯定是s 23 | for v in range(self._G.V): 24 | if not self._visited[v] and self._dis[v] < currdis: 25 | currdis = self._dis[v] 26 | curr = v 27 | if curr == -1: 28 | break 29 | # 第二段逻辑:确定curr点被访问过 30 | self._visited[curr] = True 31 | 32 | # 第三段逻辑:利用curr点来更新其相邻节点w与原点s的距离 33 | for w in self._G.adj(curr): 34 | if not self._visited[w]: 35 | if self._dis[curr] + self._G.get_weight(curr, w) < self._dis[w]: 36 | # dis[w]表示原点s到w的当前最短距离 37 | self._dis[w] = self._dis[curr] + self._G.get_weight(curr, w) 38 | 39 | def is_connected_to(self, v): 40 | self._G.validate_vertex(v) 41 | return self._visited[v] 42 | 43 | def dist_to(self, v): 44 | self._G.validate_vertex(v) 45 | return self._dis[v] 46 | 47 | 48 | if __name__ == '__main__': 49 | filename = 'play_with_graph_algorithms/chapter12/g.txt' 50 | g = WeightedGraph(filename) 51 | dijkstra = Dijkstra(g, s=0) 52 | 53 | strings = [] 54 | for v in range(g.V): 55 | strings.append(str(dijkstra.dist_to(v))) 56 | 57 | print(' '.join(strings)) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter12/dijkstra_opt.py: -------------------------------------------------------------------------------- 1 | from heapq import heappush 2 | from heapq import heappop 3 | 4 | from play_with_graph_algorithms.chapter11.weighted_graph import WeightedGraph 5 | 6 | 7 | MAX_VALUE = 2 ** 31 - 1 8 | 9 | class DijkstraOPT: 10 | 11 | def __init__(self, G, s): 12 | self._G = G 13 | self._G.validate_vertex(s) 14 | self._s = s 15 | 16 | self._dis = [MAX_VALUE - 1] * self._G.V 17 | self._dis[0] = 0 18 | # 是否已经确定了点是全局最小距离 19 | self._visited = [False] * self._G.V 20 | pq = [] 21 | heappush(pq, (0, s)) 22 | 23 | self._pre = [-1] * self._G.V 24 | self._pre[s] = s 25 | 26 | while pq: 27 | _, curr = heappop(pq) 28 | if self._visited[curr]: 29 | continue 30 | self._visited[curr] = True 31 | 32 | for w in self._G.adj(curr): 33 | if not self._visited[w]: 34 | new_dis = self._dis[curr] + self._G.get_weight(curr, w) 35 | if new_dis < self._dis[w]: 36 | # dis[w]表示原点s到w的当前最短距离 37 | self._dis[w] = new_dis 38 | self._pre[w] = curr 39 | heappush(pq, (self._dis[w], w)) 40 | 41 | def is_connected_to(self, v): 42 | self._G.validate_vertex(v) 43 | return self._visited[v] 44 | 45 | def dist_to(self, v): 46 | self._G.validate_vertex(v) 47 | return self._dis[v] 48 | 49 | def path(self, t): 50 | res = [] 51 | 52 | if not self.is_connected_to(t): 53 | return res 54 | 55 | curr = t 56 | while curr != self._s: 57 | res.append(curr) 58 | curr = self._pre[curr] 59 | res.append(self._s) 60 | return res[::-1] 61 | 62 | if __name__ == '__main__': 63 | filename = 'play_with_graph_algorithms/chapter12/g.txt' 64 | g = WeightedGraph(filename) 65 | dijkstra_opt = DijkstraOPT(g, s=0) 66 | 67 | strings = [] 68 | for v in range(g.V): 69 | strings.append(str(dijkstra_opt.dist_to(v))) 70 | print(' '.join(strings)) 71 | 72 | print(' -> '.join(str(i) for i in dijkstra_opt.path(3))) 73 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter12/floyd.py: -------------------------------------------------------------------------------- 1 | from play_with_graph_algorithms.chapter11.weighted_graph import WeightedGraph 2 | 3 | 4 | MAX_INT = 2 ** 31 - 1 5 | 6 | class Floyd: 7 | 8 | def __init__(self, G): 9 | self._G = G 10 | self._has_negative_cycle = False 11 | 12 | self._dis = [[MAX_INT] * self._G.V for _ in range(self._G.V)] 13 | for v in range(self._G.V): 14 | self._dis[v][v] = 0 15 | for w in self._G.adj(v): 16 | self._dis[v][w] = self._G.get_weight(v, w) 17 | 18 | for t in range(self._G.V): 19 | for v in range(self._G.V): 20 | for w in range(self._G.V): 21 | if self._dis[v][t] == MAX_INT or self._dis[t][w] == MAX_INT: 22 | continue 23 | if self._dis[v][t] + self._dis[t][w] < self._dis[v][w]: 24 | self._dis[v][w] = self._dis[v][t] + self._dis[t][w] 25 | 26 | for v in range(self._G.V): 27 | if self._dis[v][v] < 0: 28 | self._has_negative_cycle = True 29 | break 30 | 31 | def has_neg_cycle(self): 32 | return self._has_negative_cycle 33 | 34 | def is_connected_to(self, v, w): 35 | self._G.validate_vertex(v) 36 | self._G.validate_vertex(w) 37 | return self._dis[v][w] < MAX_INT 38 | 39 | def dist_to(self, v, w): 40 | self._G.validate_vertex(v) 41 | self._G.validate_vertex(w) 42 | return self._dis[v][w] 43 | 44 | 45 | if __name__ == '__main__': 46 | filename = 'play_with_graph_algorithms/chapter12/g.txt' 47 | g = WeightedGraph(filename) 48 | floyd = Floyd(g) 49 | 50 | if not floyd.has_neg_cycle(): 51 | for v in range(g.V): 52 | strings = [] 53 | for w in range(g.V): 54 | strings.append(str(floyd.dist_to(v, w))) 55 | print(' '.join(strings)) 56 | else: 57 | print('exist negative cycle') -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter12/g.txt: -------------------------------------------------------------------------------- 1 | 5 8 2 | 0 1 4 3 | 0 2 2 4 | 1 2 1 5 | 1 3 2 6 | 1 4 3 7 | 2 3 4 8 | 2 4 5 9 | 3 4 1 -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter12/g2.txt: -------------------------------------------------------------------------------- 1 | 5 8 2 | 0 1 -1 3 | 0 2 2 4 | 1 2 1 5 | 1 3 2 6 | 1 4 3 7 | 2 3 4 8 | 2 4 5 9 | 3 4 1 -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter13/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zwang96-dl/play-with-graph-algorithms/27606571a71d5516eaac2bfdb68b9e98e872639a/play_with_graph_algorithms/chapter13/__init__.py -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter13/bellman_ford.py: -------------------------------------------------------------------------------- 1 | from play_with_graph_algorithms.chapter13.weighted_graph import WeightedGraph 2 | from play_with_graph_algorithms.chapter12.bellman_ford import BellmanFord 3 | 4 | 5 | if __name__ == '__main__': 6 | filename = 'play_with_graph_algorithms/chapter13/wg.txt' 7 | g = WeightedGraph(filename, directed=True) 8 | bf = BellmanFord(g, s=0) 9 | 10 | if not bf.has_neg(): 11 | strings = [] 12 | for v in range(g.V): 13 | strings.append(bf.dist_to(v)) 14 | print(' '.join(str(i) for i in strings)) 15 | print(bf.path(1)) 16 | else: 17 | print('exist negative circle') -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter13/directed_circle_detection.py: -------------------------------------------------------------------------------- 1 | from play_with_graph_algorithms.chapter13.graph import Graph 2 | 3 | 4 | class DirectedCycleDetection: 5 | 6 | def __init__(self, G): 7 | if not G.is_directed(): 8 | raise ValueError('CircleDetection only works in undirected graph') 9 | self._G = G 10 | self._visited = [False] * G.V 11 | self._on_path = [False] * G.V 12 | self._has_cycle = False 13 | 14 | # 遍历所有的点,相当于遍历图中所有可能存在的联通块 15 | for v in range(G.V): 16 | if not self._visited[v]: 17 | if self._dfs(v, v): 18 | self._has_cycle = True 19 | break 20 | 21 | def _dfs(self, v, parent): 22 | self._visited[v] = True 23 | self._on_path[v] = True 24 | for w in self._G.adj(v): 25 | if not self._visited[w]: 26 | if self._dfs(w, v): 27 | return True 28 | # 说明此时找到一个环 29 | # 跟无向图不同,这里没有w != parent的判断 30 | # 因为有向图中,认为0-1和1-0是两条不同的路径(如果存在的话),是合法的 31 | elif self._on_path[w]: 32 | return True 33 | self._on_path[v] = False 34 | return False 35 | 36 | def has_cycle(self): 37 | return self._has_cycle 38 | 39 | 40 | if __name__ == '__main__': 41 | filename = 'play_with_graph_algorithms/chapter13/ug.txt' 42 | g = Graph(filename, directed=True) 43 | directed_cycle_detection = DirectedCycleDetection(g) 44 | print(directed_cycle_detection.has_cycle()) 45 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter13/directed_euler_loop.py: -------------------------------------------------------------------------------- 1 | from copy import copy 2 | 3 | from play_with_graph_algorithms.chapter13.graph import Graph 4 | from play_with_graph_algorithms.chapter04.cc import CC 5 | 6 | 7 | class DirectedEulerLoop: 8 | def __init__(self, G): 9 | if not G.is_directed(): 10 | raise ValueError('DirectedEulerLoop only works in directed graph') 11 | self._G = G 12 | 13 | def has_euler_loop(self): 14 | # cc = CC(self._G) 15 | # if cc.ccount > 1: 16 | # return False 17 | 18 | for v in range(self._G.V): 19 | if self._G.indegree(v) != self._G.outdegree(v): 20 | return False 21 | 22 | return True 23 | 24 | def result(self): 25 | res = [] 26 | if not self.has_euler_loop(): 27 | return res 28 | g = copy(self._G) 29 | 30 | stack = [] 31 | currv = 0 32 | stack.append(currv) 33 | 34 | while stack: 35 | if g.outdegree(currv) != 0: 36 | stack.append(currv) 37 | # 模拟一个iterator 38 | w = next(self._iter_next_adj(g.adj(currv))) 39 | g.remove_edge(currv, w) 40 | currv = w 41 | else: 42 | # 此时说明找到了一个环 43 | res.append(currv) 44 | currv = stack.pop() 45 | 46 | return res[::-1] 47 | 48 | def _iter_next_adj(self, adj): 49 | yield from sorted(adj) 50 | 51 | 52 | if __name__ == '__main__': 53 | filename = 'play_with_graph_algorithms/chapter13/ug.txt' 54 | g = Graph(filename, directed=True) 55 | directed_eluer_loop = DirectedEulerLoop(g) 56 | print(directed_eluer_loop.result()) 57 | 58 | filename = 'play_with_graph_algorithms/chapter13/ug2.txt' 59 | g = Graph(filename, directed=True) 60 | directed_eluer_loop = DirectedEulerLoop(g) 61 | print(directed_eluer_loop.result()) 62 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter13/floyd.py: -------------------------------------------------------------------------------- 1 | from play_with_graph_algorithms.chapter13.weighted_graph import WeightedGraph 2 | from play_with_graph_algorithms.chapter12.floyd import Floyd 3 | 4 | 5 | if __name__ == '__main__': 6 | filename = 'play_with_graph_algorithms/chapter13/wg.txt' 7 | g = WeightedGraph(filename, directed=True) 8 | floyd = Floyd(g) 9 | 10 | if not floyd.has_neg_cycle(): 11 | for v in range(g.V): 12 | strings = [] 13 | for w in range(g.V): 14 | strings.append(str(floyd.dist_to(v, w))) 15 | print(' '.join(strings)) 16 | else: 17 | print('exist negative cycle') 18 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter13/graph.py: -------------------------------------------------------------------------------- 1 | class Graph: 2 | 3 | """Suppose to use RB tree, but no TreeSet/TreeMap in vanilla Python, using 4 | set instead. 5 | 6 | Support both directed graph and indirected graph 7 | """ 8 | 9 | def __init__(self, filename, directed=False, reverse=False): 10 | self._filename = filename 11 | self._directed = directed 12 | self._reverse = reverse 13 | lines = None 14 | with open(filename, 'r') as f: 15 | lines = f.readlines() 16 | if not lines: 17 | raise ValueError('Expected something from input file!') 18 | 19 | # lines[0] -> V E 20 | self._V, self._E = (int(i) for i in lines[0].split()) 21 | 22 | if self._V < 0: 23 | raise ValueError('V must be non-negative') 24 | 25 | if self._E < 0: 26 | raise ValueError('E must be non-negative') 27 | 28 | # size V list of set 29 | self._adj = [set() for _ in range(self._V)] 30 | self._indegree = [0] * self._V 31 | self._outdegree = [0] * self._V 32 | 33 | for each_line in lines[1:]: 34 | a, b = (int(i) for i in each_line.split()) 35 | if self._reverse: 36 | a, b = b, a 37 | self.validate_vertex(a) 38 | self.validate_vertex(b) 39 | 40 | if a == b: 41 | raise ValueError('Self-Loop is detected!') 42 | 43 | if b in self._adj[a]: 44 | raise ValueError('Paralles edges are detected!') 45 | 46 | self._adj[a].add(b) 47 | 48 | if self._directed: 49 | self._outdegree[a] += 1 50 | self._indegree[b] += 1 51 | 52 | if not self._directed: 53 | self._adj[b].add(a) 54 | 55 | def reverse_graph(self): 56 | return Graph(filename=self._filename, directed=self._directed, reverse=True) 57 | 58 | @property 59 | def V(self): 60 | return self._V 61 | 62 | @property 63 | def E(self): 64 | return self._E 65 | 66 | def has_edge(self, v, w): 67 | self.validate_vertex(v) 68 | self.validate_vertex(w) 69 | return w in self._adj[v] 70 | 71 | def adj(self, v): 72 | self.validate_vertex(v) 73 | return self._adj[v] 74 | 75 | def degree(self, v): 76 | if self._directed: 77 | raise ValueError('degree only works on undirected graph') 78 | return len(self.adj(v)) 79 | 80 | def indegree(self, v): 81 | if not self._directed: 82 | raise ValueError('indegree only works in directed graph') 83 | self.validate_vertex(v) 84 | return self._indegree[v] 85 | 86 | def outdegree(self, v): 87 | if not self._directed: 88 | raise ValueError('outdegree only works in directed graph') 89 | self.validate_vertex(v) 90 | return self._outdegree[v] 91 | 92 | def remove_edge(self, v, w): 93 | self.validate_vertex(v) 94 | self.validate_vertex(w) 95 | if w in self._adj[v]: 96 | self._E -= 1 97 | if self._directed: 98 | self._outdegree[v] -= 1 99 | self._indegree[w] -= 1 100 | self._adj[v].remove(w) 101 | if not self._directed: 102 | self._adj[w].remove(v) 103 | 104 | def validate_vertex(self, v): 105 | if v < 0 or v >= self._V: 106 | raise ValueError('vertex ' + v + ' is invalid') 107 | 108 | def is_directed(self): 109 | return self._directed 110 | 111 | def __str__(self): 112 | res = ['V = {}, E = {}, directed = {}'.format(self._V, self._E, self._directed)] 113 | for v in range(self._V): 114 | res.append('{}: {}'.format(v, ' '.join(str(w) for w in self._adj[v]))) 115 | return '\n'.join(res) 116 | 117 | def __repr__(self): 118 | return self.__str__() 119 | 120 | def __copy__(self): 121 | return Graph(self._filename, self._directed, self._reverse) 122 | 123 | 124 | if __name__ == '__main__': 125 | filename = 'play_with_graph_algorithms/chapter13/ug.txt' 126 | g = Graph(filename, directed=True) 127 | 128 | for v in range(g.V): 129 | print(g.indegree(v), g.outdegree(v)) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter13/graph_bfs.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | from play_with_graph_algorithms.chapter13.graph import Graph 3 | from play_with_graph_algorithms.chapter05.graph_bfs import GraphBFS 4 | 5 | 6 | if __name__ == '__main__': 7 | filename = 'play_with_graph_algorithms/chapter13/ug.txt' 8 | g = Graph(filename, directed=True) 9 | graph_bfs = GraphBFS(g) 10 | print('BFS order : {}'.format(graph_bfs.order())) 11 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter13/graph_dfs.py: -------------------------------------------------------------------------------- 1 | from play_with_graph_algorithms.chapter13.graph import Graph 2 | from play_with_graph_algorithms.chapter03.graph_dfs import GraphDFS 3 | 4 | 5 | if __name__ == '__main__': 6 | filename = 'play_with_graph_algorithms/chapter13/ug.txt' 7 | g = Graph(filename, directed=True) 8 | graph_dfs = GraphDFS(g) 9 | 10 | print('DFS pre order: ', graph_dfs.pre_order) 11 | print('DFS post order: ', graph_dfs.post_order) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter13/scc.py: -------------------------------------------------------------------------------- 1 | from play_with_graph_algorithms.chapter13.graph import Graph 2 | from play_with_graph_algorithms.chapter13.graph_dfs import GraphDFS 3 | 4 | 5 | class SCC: 6 | 7 | def __init__(self, G, recursive=True): 8 | self._G = G 9 | self._visited = [-1] * G.V 10 | self._sccount = 0 11 | 12 | if not self._G.is_directed: 13 | raise ValueError('CC only works in directed graph') 14 | 15 | dfs = GraphDFS(self._G.reverse_graph()) 16 | order = dfs.post_order[::-1] 17 | 18 | # 遍历所有的点,相当于遍历图中所有可能存在的强联通块 19 | for v in order: 20 | if self._visited[v] == -1: 21 | if recursive: 22 | self._dfs_recursive(v, self._sccount) 23 | else: 24 | self._dfs_iteration(v, self._sccount) 25 | self._sccount += 1 26 | 27 | def _dfs_recursive(self, v, sccid): 28 | self._visited[v] = sccid 29 | for w in self._G.adj(v): 30 | if self._visited[w] == -1: 31 | self._dfs_recursive(w, sccid) 32 | 33 | def _dfs_iteration(self, v, sccid): 34 | """For preorder that's straight-forward by using one stack, 35 | but for postorder we need a augmented stack2 36 | """ 37 | stack = [v] 38 | # within one func call, all visited nodes should be the same sccid 39 | self._visited[v] = sccid 40 | while stack: 41 | curr = stack.pop() 42 | for w in self._G.adj(curr): 43 | if self._visited[w] == -1: 44 | stack.append(w) 45 | # the same sccid 46 | self._visited[w] = sccid 47 | 48 | def is_strongly_connected(self, v, w): 49 | self._G.validate_vertex(v) 50 | self._G.validate_vertex(w) 51 | return self._visited[v] == self._visited[w] 52 | 53 | @property 54 | def sccount(self): 55 | return self._sccount 56 | 57 | @property 58 | def components(self): 59 | res = [[] for _ in range(self._sccount)] 60 | for v in range(self._G.V): 61 | res[self._visited[v]].append(v) 62 | return res 63 | 64 | 65 | if __name__ == '__main__': 66 | filename = 'play_with_graph_algorithms/chapter13/ug4.txt' 67 | g = Graph(filename, directed=True) 68 | scc = SCC(g) 69 | 70 | print(scc.sccount) 71 | comp = scc.components 72 | for sccid in range(len(comp)): 73 | temp = ['{} : '.format(sccid)] 74 | for w in comp[sccid]: 75 | temp.append('{} '.format(w)) 76 | print(''.join(temp)) 77 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter13/topo_sort.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | from play_with_graph_algorithms.chapter13.graph import Graph 4 | 5 | 6 | class TopoSort: 7 | 8 | def __init__(self, G): 9 | if not G.is_directed(): 10 | raise ValueError('TopoSort only works in directed graph') 11 | 12 | self._G = G 13 | self._res = [] 14 | self._has_cycle = False 15 | 16 | indegrees = [0] * self._G.V 17 | queue = deque() 18 | 19 | for v in range(self._G.V): 20 | indegrees[v] = self._G.indegree(v) 21 | if indegrees[v] == 0: 22 | queue.append(v) 23 | 24 | while queue: 25 | curr = queue.popleft() 26 | self._res.append(curr) 27 | for next_ in self._G.adj(curr): 28 | indegrees[next_] -= 1 29 | if indegrees[next_] == 0: 30 | queue.append(next_) 31 | 32 | if len(self._res) != self._G.V: 33 | self._has_cycle = True 34 | self._res = [] 35 | 36 | def has_cycle(self): 37 | return self._has_cycle 38 | 39 | def result(self): 40 | return self._res 41 | 42 | 43 | if __name__ == '__main__': 44 | filename = 'play_with_graph_algorithms/chapter13/ug.txt' 45 | g = Graph(filename, directed=True) 46 | 47 | topo_sort = TopoSort(g) 48 | print(topo_sort.result()) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter13/topo_sort2.py: -------------------------------------------------------------------------------- 1 | from play_with_graph_algorithms.chapter13.directed_circle_detection import DirectedCycleDetection 2 | from play_with_graph_algorithms.chapter13.graph_dfs import GraphDFS 3 | from play_with_graph_algorithms.chapter13.graph import Graph 4 | 5 | 6 | class TopoSort2: 7 | 8 | def __init__(self, g): 9 | if not g.is_directed(): 10 | raise ValueError('TopoSort only works in directed graph') 11 | 12 | self._g = g 13 | self._res = [] 14 | self._has_cycle = DirectedCycleDetection(g).has_cycle() 15 | 16 | if self._has_cycle: 17 | return 18 | 19 | dfs = GraphDFS(g) 20 | self._res = dfs.post_order[::-1] 21 | 22 | if len(self._res) != self._g.V: 23 | self._has_cycle = True 24 | self._res = [] 25 | 26 | def has_cycle(self): 27 | return self._has_cycle 28 | 29 | def result(self): 30 | return self._res 31 | 32 | 33 | if __name__ == '__main__': 34 | filename = 'play_with_graph_algorithms/chapter13/ug.txt' 35 | g = Graph(filename, directed=True) 36 | 37 | topo_sort2 = TopoSort2(g) 38 | print(topo_sort2.result()) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter13/ug.txt: -------------------------------------------------------------------------------- 1 | 5 5 2 | 0 1 3 | 1 2 4 | 1 3 5 | 2 4 6 | 3 2 -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter13/ug2.txt: -------------------------------------------------------------------------------- 1 | 5 8 2 | 0 1 3 | 1 2 4 | 1 3 5 | 2 0 6 | 2 4 7 | 3 1 8 | 3 2 9 | 4 3 -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter13/ug3.txt: -------------------------------------------------------------------------------- 1 | 3 3 2 | 0 1 3 | 1 2 4 | 2 0 -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter13/ug4.txt: -------------------------------------------------------------------------------- 1 | 5 5 2 | 0 1 3 | 1 2 4 | 2 3 5 | 3 1 6 | 2 4 -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter13/weighted_graph.py: -------------------------------------------------------------------------------- 1 | class WeightedGraph: 2 | 3 | """Suppose to use RB tree, but no TreeSet/TreeMap in vanilla Python, using 4 | dict instead. 5 | 6 | Support both directed graph and indirected graph 7 | """ 8 | 9 | def __init__( 10 | self, 11 | filename=None, 12 | directed=False, 13 | is_redisual=False, 14 | empty_graph=False, 15 | V=None, 16 | ): 17 | if empty_graph is False: 18 | self._generate_graph_from_file( 19 | filename=filename, 20 | directed=directed, 21 | is_redisual=is_redisual, 22 | empty_graph=empty_graph, 23 | ) 24 | else: 25 | self._generate_empty_graph(directed, V) 26 | 27 | def _generate_empty_graph(self, directed, V): 28 | self._V = V 29 | self._directed = directed 30 | self._E = 0 31 | self._adj = [dict() for _ in range(self._V)] 32 | 33 | self._filename = None 34 | 35 | def _generate_graph_from_file( 36 | self, 37 | filename, 38 | directed=False, 39 | is_redisual=False, 40 | empty_graph=False, 41 | ): 42 | self._filename = filename 43 | self._directed = directed 44 | self._is_redisual = is_redisual 45 | lines = None 46 | with open(filename, 'r') as f: 47 | lines = f.readlines() 48 | if not lines: 49 | raise ValueError('Expected something from input file!') 50 | 51 | # lines[0] -> V e 52 | self._E = 0 53 | self._V, self._e = (int(i) for i in lines[0].split()) 54 | 55 | if self._V < 0: 56 | raise ValueError('V must be non-negative') 57 | 58 | if self._e < 0: 59 | raise ValueError('E must be non-negative') 60 | 61 | # size V list of dictionaries 62 | self._adj = [dict() for _ in range(self._V)] 63 | for each_line in lines[1:]: 64 | a, b, weight = (int(i) for i in each_line.split()) 65 | self.add_edge(a, b, weight) 66 | if self._is_redisual: 67 | self.add_edge(b, a, 0) 68 | 69 | def add_edge(self, a, b, weight): 70 | self.validate_vertex(a) 71 | self.validate_vertex(b) 72 | 73 | if a == b: 74 | raise ValueError('Self-Loop is detected!') 75 | 76 | # if b in self._adj[a]: 77 | # raise ValueError('Paralles edges are detected!') 78 | 79 | self._adj[a][b] = weight 80 | if not self._directed: 81 | self._adj[b][a] = weight 82 | 83 | self._E += 1 84 | 85 | @property 86 | def V(self): 87 | return self._V 88 | 89 | @property 90 | def E(self): 91 | return self._E 92 | 93 | def has_edge(self, v, w): 94 | self.validate_vertex(v) 95 | self.validate_vertex(w) 96 | return w in self._adj[v] 97 | 98 | def adj(self, v): 99 | self.validate_vertex(v) 100 | return list(self._adj[v].keys()) 101 | 102 | def get_weight(self, v, w): 103 | if self.has_edge(v, w): 104 | return self._adj[v][w] 105 | raise ValueError('No edge {}-{}'.format(v, w)) 106 | 107 | def set_weight(self, v, w, net_weight): 108 | if not self.has_edge(v, w): 109 | raise ValueError('No edge {}-{}'.format(v, w)) 110 | self._adj[v][w] = net_weight 111 | if not self._directed: 112 | self._adj[w][v] = net_weight 113 | 114 | # def degree(self, v): 115 | # return len(self.adj(v)) 116 | 117 | def remove_edge(self, v, w): 118 | self.validate_vertex(v) 119 | self.validate_vertex(w) 120 | if w in self._adj[v]: 121 | self._adj[v].pop(w) 122 | if not self._directed and v in self._adj[w]: 123 | self._adj[w].pop(v) 124 | 125 | def validate_vertex(self, v): 126 | if v < 0 or v >= self._V: 127 | raise ValueError('vertex ' + str(v) + ' is invalid') 128 | 129 | def is_directed(self): 130 | return self._directed 131 | 132 | def generate_redisual_graph(self, empty_graph=False): 133 | # redisual graph is definitely a directed graph 134 | if not empty_graph: 135 | return WeightedGraph(filename=self._filename, directed=True, is_redisual=True) 136 | return WeightedGraph(empty_graph=True, directed=True, is_redisual=True) 137 | 138 | def __str__(self): 139 | res = ['V = {}, E = {}, directed = {}'.format(self._V, self._E, self._directed)] 140 | for v in range(self._V): 141 | res.append( 142 | '{}: {}'.format( 143 | v, 144 | ' '.join('{}({})'.format(w, self._adj[v][w]) for w in self._adj[v]), 145 | ), 146 | ) 147 | return '\n'.join(res) 148 | 149 | def __repr__(self): 150 | return self.__str__() 151 | 152 | def __copy__(self): 153 | return WeightedGraph(self._filename) 154 | 155 | 156 | if __name__ == '__main__': 157 | filename = 'play_with_graph_algorithms/chapter13/wg.txt' 158 | w_graph = WeightedGraph(filename, directed=True) 159 | print(w_graph) 160 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter13/wg.txt: -------------------------------------------------------------------------------- 1 | 3 3 2 | 0 1 2 3 | 0 2 3 4 | 2 1 -6 -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter13/wg2.txt: -------------------------------------------------------------------------------- 1 | 3 3 2 | 1 0 2 3 | 0 2 3 4 | 2 1 -6 -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter14/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zwang96-dl/play-with-graph-algorithms/27606571a71d5516eaac2bfdb68b9e98e872639a/play_with_graph_algorithms/chapter14/__init__.py -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter14/baseball.txt: -------------------------------------------------------------------------------- 1 | 11 19 2 | 0 1 3 3 | 0 2 8 4 | 0 3 7 5 | 0 4 2 6 | 0 5 7 7 | 1 6 3 8 | 1 7 3 9 | 2 6 8 10 | 2 8 8 11 | 3 6 7 12 | 3 9 7 13 | 4 7 2 14 | 4 8 2 15 | 5 7 7 16 | 7 9 7 17 | 6 10 1 18 | 7 10 5 19 | 8 10 7 20 | 9 10 13 -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter14/max_flow.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | from play_with_graph_algorithms.chapter13.weighted_graph import WeightedGraph 4 | 5 | 6 | class MaxFlow: 7 | 8 | def __init__(self, network, s, t, empty_graph=False, directed=True, V=None): 9 | if not network.is_directed(): 10 | raise ValueError('MaxFlow only works in directed graph!') 11 | 12 | if network.V < 2: 13 | raise ValueError('The network should have at least two points') 14 | 15 | network.validate_vertex(s) 16 | network.validate_vertex(t) 17 | 18 | if s == t: 19 | raise ValueError('s and t should be different') 20 | 21 | self._network = network 22 | self._s = s 23 | self._t = t 24 | if not empty_graph: 25 | self._rG = self._network.generate_redisual_graph() 26 | else: 27 | # 生成残量图 28 | temp = [] 29 | for v in range(self._network.V): 30 | for w in self._network.adj(v): 31 | temp.append([w, v, 0]) 32 | for each in temp: 33 | self._network.add_edge(*each) 34 | self._rG = self._network 35 | 36 | self._max_flow = 0 37 | 38 | while True: 39 | aug_path = self._get_augmenting_path() 40 | if not aug_path: 41 | break 42 | else: 43 | f = 2 ** 31 - 1 44 | for i in range(1, len(aug_path)): 45 | v = aug_path[i - 1] 46 | w = aug_path[i] 47 | f = min(f, self._rG.get_weight(v, w)) 48 | 49 | self._max_flow += f 50 | for i in range(1, len(aug_path)): 51 | v = aug_path[i - 1] 52 | w = aug_path[i] 53 | 54 | # 不管正向边还是反向边,更新的方式都是一样的 55 | # if self._network.has_edge(v, w): 56 | # self._rG.set_weight(v, w, self._rG.get_weight(v, w) - f) 57 | # self._rG.set_weight(w, v, self._rG.get_weight(w, v) + f) 58 | # else: 59 | # self._rG.set_weight(w, v, self._rG.get_weight(w, v) + f) 60 | # self._rG.set_weight(v, w, self._rG.get_weight(v, w) - f) 61 | self._rG.set_weight(w, v, self._rG.get_weight(w, v) + f) 62 | self._rG.set_weight(v, w, self._rG.get_weight(v, w) - f) 63 | 64 | def result(self): 65 | return self._max_flow 66 | 67 | def flow(self, v, w): 68 | if not self._network.has_edge(v, w): 69 | raise ValueError('No edge {}-{}'.format(v, w)) 70 | return self._rG.get_weight(w, v) 71 | 72 | def _get_augmenting_path(self): 73 | q = deque() 74 | pre = [-1] * self._network.V 75 | 76 | q.append(self._s) 77 | pre[self._s] = self._s 78 | 79 | while q: 80 | curr = q.popleft() 81 | for next_ in self._rG.adj(curr): 82 | if pre[next_] == -1 and self._rG.get_weight(curr, next_) > 0: 83 | pre[next_] = curr 84 | q.append(next_) 85 | 86 | res = [] 87 | if pre[self._t] == -1: 88 | return res 89 | 90 | curr = self._t 91 | while curr != self._s: 92 | res.append(curr) 93 | curr = pre[curr] 94 | res.append(self._s) 95 | return res[::-1] 96 | 97 | 98 | if __name__ == "__main__": 99 | filename = 'play_with_graph_algorithms/chapter14/network.txt' 100 | network = WeightedGraph(filename, directed=True) 101 | 102 | maxflow = MaxFlow(network, 0, 3) 103 | print(maxflow.result()) 104 | for v in range(network.V): 105 | for w in network.adj(v): 106 | print('{}-{} : {} / {}'.format(v, w, maxflow.flow(v, w), network.get_weight(v, w))) 107 | 108 | print('=' * 30) 109 | 110 | filename = 'play_with_graph_algorithms/chapter14/network2.txt' 111 | network = WeightedGraph(filename, directed=True) 112 | 113 | maxflow = MaxFlow(network, 0, 3) 114 | print(maxflow.result()) 115 | for v in range(network.V): 116 | for w in network.adj(v): 117 | print('{}-{} : {} / {}'.format(v, w, maxflow.flow(v, w), network.get_weight(v, w))) 118 | 119 | filename = 'play_with_graph_algorithms/chapter14/baseball.txt' 120 | network = WeightedGraph(filename, directed=True) 121 | maxflow = MaxFlow(network, 0, 10) 122 | print(maxflow.result()) 123 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter14/network.txt: -------------------------------------------------------------------------------- 1 | 4 5 2 | 0 1 3 3 | 0 2 2 4 | 1 2 5 5 | 1 3 2 6 | 2 3 3 -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter14/network2.txt: -------------------------------------------------------------------------------- 1 | 6 9 2 | 0 1 9 3 | 0 3 9 4 | 1 2 8 5 | 1 3 10 6 | 2 5 10 7 | 3 2 1 8 | 3 4 3 9 | 4 2 8 10 | 4 5 7 -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter15/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zwang96-dl/play-with-graph-algorithms/27606571a71d5516eaac2bfdb68b9e98e872639a/play_with_graph_algorithms/chapter15/__init__.py -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter15/bipartite_matching.py: -------------------------------------------------------------------------------- 1 | from play_with_graph_algorithms.chapter04.bi_partite_detection import BiPartiteDetection 2 | from play_with_graph_algorithms.chapter13.weighted_graph import WeightedGraph 3 | from play_with_graph_algorithms.chapter13.graph import Graph 4 | from play_with_graph_algorithms.chapter14.max_flow import MaxFlow 5 | 6 | 7 | class BipartiteMatching: 8 | 9 | def __init__(self, G): 10 | bd = BiPartiteDetection(G) 11 | if bd.is_bi_partite() is False: 12 | raise ValueError('BipartiteMatching only works for bipartite graph') 13 | self._G = G 14 | colors = bd.colors() 15 | network = WeightedGraph(empty_graph=True, directed=True, V=G.V + 2) 16 | 17 | for v in range(self._G.V): 18 | # 将源点和汇点分别和二分图中不同的部分相连 19 | if colors[v] == 0: 20 | network.add_edge(G.V, v, 1) 21 | else: 22 | network.add_edge(v, G.V + 1, 1) 23 | 24 | # 将二分图两个部分相连 25 | for w in self._G.adj(v): 26 | if v < w: 27 | if colors[v] == 0: 28 | network.add_edge(v, w, 1) 29 | else: 30 | network.add_edge(w, v, 1) 31 | 32 | maxflow = MaxFlow( 33 | network, 34 | self._G.V, 35 | self._G.V + 1, 36 | empty_graph=True, 37 | directed=True, 38 | V=G.V + 2, 39 | ) 40 | self._max_matching = maxflow.result() 41 | 42 | def max_matching(self): 43 | return self._max_matching 44 | 45 | def is_perfect_matching(self): 46 | return self._max_matching * 2 == self._G.V 47 | 48 | 49 | if __name__ == "__main__": 50 | filename = 'play_with_graph_algorithms/chapter15/g.txt' 51 | g = Graph(filename) 52 | bm = BipartiteMatching(g) 53 | print(bm.max_matching(), bm.is_perfect_matching()) 54 | 55 | filename = 'play_with_graph_algorithms/chapter15/g2.txt' 56 | g = Graph(filename) 57 | bm = BipartiteMatching(g) 58 | print(bm.max_matching(), bm.is_perfect_matching()) 59 | -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter15/g.txt: -------------------------------------------------------------------------------- 1 | 8 6 2 | 0 4 3 | 0 6 4 | 1 4 5 | 2 6 6 | 3 5 7 | 3 7 -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter15/g2.txt: -------------------------------------------------------------------------------- 1 | 8 7 2 | 0 4 3 | 0 6 4 | 1 4 5 | 1 7 6 | 2 6 7 | 3 5 8 | 3 7 -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter15/hungarian.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | from play_with_graph_algorithms.chapter13.graph import Graph 4 | from play_with_graph_algorithms.chapter04.bi_partite_detection import BiPartiteDetection 5 | 6 | 7 | class Hungarian: 8 | 9 | def __init__(self, G, use_dfs=False): 10 | bd = BiPartiteDetection(G) 11 | if bd.is_bi_partite() is False: 12 | raise ValueError('Hungarian only works for bipartite graph') 13 | 14 | self._G = G 15 | self._maxmatching = 0 16 | self._matching = [-1] * self._G.V 17 | 18 | colors = bd.colors() 19 | for v in range(self._G.V): 20 | # 找到了左侧一个未匹配的点 21 | if colors[v] == 0 and self._matching[v] == -1: 22 | # bfs/dfs定义是从v出发,寻找是否(bool)有增广路径 23 | if use_dfs: 24 | self._visited = [False] * self._G.V 25 | find_aug_path = self._dfs(v) 26 | else: 27 | find_aug_path = self._bfs(v) 28 | if find_aug_path: 29 | self._maxmatching += 1 30 | 31 | def _dfs(self, v): 32 | self._visited[v] = True 33 | for u in self._G.adj(v): 34 | # 此时u是右半部分的点 35 | if self._visited[u] is False: 36 | self._visited[u] = True 37 | # self._matching[u]一定是左半部分的点 38 | # 返回值表示能不能找到增广路径 39 | if self._matching[u] == -1 or self._dfs(self._matching[u]): 40 | self._matching[v] = u 41 | self._matching[u] = v 42 | return True 43 | return False 44 | 45 | def _bfs(self, v): 46 | q = deque() 47 | pre = [-1] * self._G.V 48 | 49 | q.append(v) 50 | pre[v] = v 51 | while q: 52 | # curr一定需要是二分图左侧的边 53 | cur = q.popleft() 54 | # 由于G是一个二分图,而且curr是在左侧 55 | # 所以next_一定是右侧的点 56 | for next_ in self._G.adj(cur): 57 | if pre[next_] == -1: 58 | # self._matching[next_]是左侧的点 59 | if self._matching[next_] != -1: 60 | pre[next_] = cur 61 | pre[self._matching[next_]] = next_ 62 | q.append(self._matching[next_]) 63 | else: 64 | pre[next_] = cur 65 | aug_path = self._get_aug_path(pre, v, next_) 66 | for i in range(0, len(aug_path), 2): 67 | self._matching[aug_path[i]] = aug_path[i + 1] 68 | self._matching[aug_path[i + 1]] = aug_path[i] 69 | i += 2 70 | return True 71 | return False 72 | 73 | 74 | def _get_aug_path(self, pre, start, end): 75 | res = [] 76 | cur = end 77 | while cur != start: 78 | res.append(cur) 79 | cur = pre[cur] 80 | res.append(start) 81 | return res 82 | 83 | def maxmatching(self): 84 | return self._maxmatching 85 | 86 | 87 | if __name__ == "__main__": 88 | print('bfs:') 89 | filename = 'play_with_graph_algorithms/chapter15/g.txt' 90 | g = Graph(filename) 91 | hungarian = Hungarian(g) 92 | print(hungarian.maxmatching()) 93 | 94 | filename = 'play_with_graph_algorithms/chapter15/g2.txt' 95 | g = Graph(filename) 96 | hungarian = Hungarian(g) 97 | print(hungarian.maxmatching()) 98 | 99 | print('dfs:') 100 | filename = 'play_with_graph_algorithms/chapter15/g.txt' 101 | g = Graph(filename) 102 | hungarian = Hungarian(g, use_dfs=True) 103 | print(hungarian.maxmatching()) 104 | 105 | filename = 'play_with_graph_algorithms/chapter15/g2.txt' 106 | g = Graph(filename) 107 | hungarian = Hungarian(g, use_dfs=True) 108 | print(hungarian.maxmatching()) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter15/lcp4.py: -------------------------------------------------------------------------------- 1 | from play_with_graph_algorithms.chapter15.bipartite_matching import BipartiteMatching 2 | from play_with_graph_algorithms.chapter13.weighted_graph import WeightedGraph 3 | from play_with_graph_algorithms.chapter15.hungarian import Hungarian 4 | 5 | 6 | def domino(n, m, broken): 7 | board = [[0] * m for _ in range(n)] 8 | for i, j in broken: 9 | board[i][j] = 1 10 | # 由于Graph并没有实现重载 11 | # 这里使用WeightedGraph实现的重载 12 | g = WeightedGraph(empty_graph=True, V=n * m) 13 | for i in range(n): 14 | for j in range(m): 15 | if j + 1 < m and board[i][j] == 0 and board[i][j + 1] == 0: 16 | g.add_edge(i * m + j, i * m + (j + 1), 1) 17 | if i + 1 < n and board[i][j] == 0 and board[i + 1][j] == 0: 18 | g.add_edge(i * m + j, (i + 1) * m + j, 1) 19 | 20 | bm = BipartiteMatching(g) 21 | return bm.max_matching() 22 | 23 | 24 | def domino_hungarian(n, m, broken, use_dfs=False): 25 | board = [[0] * m for _ in range(n)] 26 | for i, j in broken: 27 | board[i][j] = 1 28 | # 由于Graph并没有实现重载 29 | # 这里使用WeightedGraph实现的重载 30 | g = WeightedGraph(empty_graph=True, V=n * m) 31 | for i in range(n): 32 | for j in range(m): 33 | if j + 1 < m and board[i][j] == 0 and board[i][j + 1] == 0: 34 | g.add_edge(i * m + j, i * m + (j + 1), 1) 35 | if i + 1 < n and board[i][j] == 0 and board[i + 1][j] == 0: 36 | g.add_edge(i * m + j, (i + 1) * m + j, 1) 37 | 38 | hungarian = Hungarian(g, use_dfs=use_dfs) 39 | return hungarian.maxmatching() 40 | 41 | 42 | if __name__ == "__main__": 43 | print('Maxflow:') 44 | print(domino(2, 3, broken=[[1, 0], [1, 1]])) 45 | print(domino(3, 3, broken=[])) 46 | 47 | print('Hungarian BFS:') 48 | print(domino_hungarian(2, 3, broken=[[1, 0], [1, 1]])) 49 | print(domino_hungarian(3, 3, broken=[])) 50 | 51 | print('Hungarian DFS:') 52 | print(domino_hungarian(2, 3, broken=[[1, 0], [1, 1]], use_dfs=True)) 53 | print(domino_hungarian(3, 3, broken=[], use_dfs=True)) -------------------------------------------------------------------------------- /play_with_graph_algorithms/chapter15/weighted_graph.py: -------------------------------------------------------------------------------- 1 | class WeightedGraph: 2 | 3 | """Suppose to use RB tree, but no TreeSet/TreeMap in vanilla Python, using 4 | dict instead. 5 | 6 | Support both directed graph and indirected graph 7 | """ 8 | 9 | def __init__(self, filename, directed=False, is_redisual=False): 10 | self._filename = filename 11 | self._directed = directed 12 | self._is_redisual = is_redisual 13 | lines = None 14 | with open(filename, 'r') as f: 15 | lines = f.readlines() 16 | if not lines: 17 | raise ValueError('Expected something from input file!') 18 | 19 | # lines[0] -> V e 20 | self._E = 0 21 | self._V, self._e = (int(i) for i in lines[0].split()) 22 | 23 | if self._V < 0: 24 | raise ValueError('V must be non-negative') 25 | 26 | if self._e < 0: 27 | raise ValueError('E must be non-negative') 28 | 29 | # size V list of dictionaries 30 | self._adj = [dict() for _ in range(self._V)] 31 | for each_line in lines[1:]: 32 | a, b, weight = (int(i) for i in each_line.split()) 33 | self.add_edge(a, b, weight) 34 | if self._is_redisual: 35 | self.add_edge(b, a, 0) 36 | 37 | def add_edge(self, a, b, weight): 38 | self.validate_vertex(a) 39 | self.validate_vertex(b) 40 | 41 | if a == b: 42 | raise ValueError('Self-Loop is detected!') 43 | 44 | if b in self._adj[a]: 45 | raise ValueError('Paralles edges are detected!') 46 | 47 | self._adj[a][b] = weight 48 | if not self._directed: 49 | self._adj[b][a] = weight 50 | 51 | self._E += 1 52 | 53 | @property 54 | def V(self): 55 | return self._V 56 | 57 | @property 58 | def E(self): 59 | return self._E 60 | 61 | def has_edge(self, v, w): 62 | self.validate_vertex(v) 63 | self.validate_vertex(w) 64 | return w in self._adj[v] 65 | 66 | def adj(self, v): 67 | self.validate_vertex(v) 68 | return list(self._adj[v].keys()) 69 | 70 | def get_weight(self, v, w): 71 | if self.has_edge(v, w): 72 | return self._adj[v][w] 73 | raise ValueError('No edge {}-{}'.format(v, w)) 74 | 75 | def set_weight(self, v, w, net_weight): 76 | if not self.has_edge(v, w): 77 | raise ValueError('No edge {}-{}'.format(v, w)) 78 | self._adj[v][w] = net_weight 79 | if not self._directed: 80 | self._adj[w][v] = net_weight 81 | 82 | # def degree(self, v): 83 | # return len(self.adj(v)) 84 | 85 | def remove_edge(self, v, w): 86 | self.validate_vertex(v) 87 | self.validate_vertex(w) 88 | if w in self._adj[v]: 89 | self._adj[v].pop(w) 90 | if not self._directed and v in self._adj[w]: 91 | self._adj[w].pop(v) 92 | 93 | def validate_vertex(self, v): 94 | if v < 0 or v >= self._V: 95 | raise ValueError('vertex ' + str(v) + ' is invalid') 96 | 97 | def is_directed(self): 98 | return self._directed 99 | 100 | def generate_redisual_graph(self): 101 | # redisual graph is definitely a directed graph 102 | return WeightedGraph(self._filename, directed=True, is_redisual=True) 103 | 104 | def __str__(self): 105 | res = ['V = {}, E = {}, directed = {}'.format(self._V, self._E, self._directed)] 106 | for v in range(self._V): 107 | res.append( 108 | '{}: {}'.format( 109 | v, 110 | ' '.join('{}({})'.format(w, self._adj[v][w]) for w in self._adj[v]), 111 | ), 112 | ) 113 | return '\n'.join(res) 114 | 115 | def __repr__(self): 116 | return self.__str__() 117 | 118 | def __copy__(self): 119 | return WeightedGraph(self._filename) 120 | 121 | 122 | if __name__ == '__main__': 123 | filename = 'play_with_graph_algorithms/chapter13/wg.txt' 124 | w_graph = WeightedGraph(filename, directed=True) 125 | print(w_graph) --------------------------------------------------------------------------------