实验五 图论——桥问题
实验目的
(1) 掌握图的连通性。
(2) 掌握并查集的基本原理和应用。
实验内容
- 桥的定义
在图论中,一条边被称为“桥”代表这条边一旦被删除,这张图的连通块数量会增加。等价地说,一条边是一座桥当且仅当这条边不在任何环上。一张图可以有零或多座桥。
- 求解问题
找出无向图中的所有桥 - 算法
基准算法+高效算法
实验过程
1. 基准算法
先记录图的初始连通分量,然后对逐条边删去,再计算连通分量是否变化,若变化这说明是桥。
具体的实现思路:逐条边删去,并以其中一个顶点作为起点进行DFS,若DFS能够经过另一个顶点,说明该边不是桥,若否则为桥。
数据结构:邻接表
同实验三的选择思路,避免发生大数据时矩阵运算导致的内存不足;同时也方便我们进行DFS搜索,减少访问相连边的时间。
伪代码:
1. #基准法
2. #遍历每一条边:
3. #删除该边
4. #以其中一个为顶点进行DFS/BFS
5. #判断另一个顶点是否被访问
6. #若未访问:
7. #标记为桥
2. 优化算法
数据结构:
将图以边的形式保存在列表中进行遍历
2.1 基准法+并查集
使用一个数组记录每个顶点的根节点,最后计算数组中的根节点数目即是连通子图的数量
#关键代码
1. def connected(p,q):
2. global count
3. if find(p) == find(q):
4. return True
5. else:
6. return False
7.
8. def find(p):
9. global count
10. while (p != id[p]):
11. p = id[p]
12. return p
13.
14. def union(p,q):
15. global count
16. idq = find(q)
17. idp = find(p)
18. if not connected(p,q):
19. id[idp]=idq
20. count -=1
2.1 基准法+并查集优化
由于改变某棵子树的根节点时,我们初始是默认为统一修改左子树节点的根节点,但是此时可能存在两棵树大小悬殊的问题(如图1)。因此我们添加一个列表用于记录每个顶点树的大小,在需要修改根节点时,对大小进行判断,修改树规模较小的子树。
1. def union(p,q):
2. global count
3. idq = find(q)
4. idp = find(p)
5. if not connected(p,q):
6. if (sz[idp] < sz[idq]):
7. id[idp] = idq
8. sz[idq] += sz[idp]
9. else:
10. id[idq] = idp
11. sz[idp] += sz[idq]
12. count -=1
2.3 基准法+并查集+DFS除环
由于能构成环的边一定不可能是桥,因此我们可以使用dfs找出图中所有的基础环,将这些边删除,减少基准法遍历的边数。
1. def dfs_cycle(graph,trace,start):
2. # 如果start在trace中,则返回环
3. if start in trace:
4. index = trace.index(start)
5. tmp = [str(i) for i in trace[index:]]
6. ans.add( str(' '.join(tmp)))
7. #print(trace[index:])
8. return
9. # 深度优先递归递归
10. for i in graph[start]:
11. dfs(graph,trace,i)
2.4 基准法+并查集+生成树(Prim算法)
一个连通图的生成树是一个极小的连通子图,它包含图中全部的n个顶点,但只有构成一棵树的n-1条边。生成树当中不存在环;移除生成树中的任意一条边都会导致图的不连通,且在生成树中添加一条边会构成环。
因此,桥一定会在生成树上,这其实也是一种减少遍历次数的方法。但是由于这种方法会保留环的某几条边,与我们上一个去除所有构成环的边算法相比不算太优越。
3. DFS除环
DFS除环其实不需要与并查集合并,直接除掉不在环上的就是桥。但是由于我一开始DFS除桥代码编写没有除彻底,因此将其和并查集合并起来,又进行了一次探索。
但实际上是可以直接使用DFS除环查找桥。