无向图的遍历
DFS
- 算法思想
深度优先搜索思想:假设初始状态是图中所有顶点均未被访问,则从某个顶点v出发,首先访问该顶点,然后依次从它的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和v有路径相通的顶点都被访问到。若此时尚有其他顶点未被访问到,则另选一个未被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。
- 算法特点:栈
深度优先搜索是一个递归的过程。首先,选定一个出发点后进行遍历,如果有邻接的未被访问过的节点则继续前进。若不能继续前进,则回退一步再前进,若回退一步仍然不能前进,则连续回退至可以前进的位置为止。重复此过程,直到所有与选定点相通的所有顶点都被遍历。
深度优先搜索是递归过程,带有回退操作,因此需要使用栈
存储访问的路径信息。当访问到的当前顶点没有可以前进的邻接顶点时,需要进行出栈操作,将当前位置回退至出栈元素位置,直到栈为空。
BFS
- 算法思想
广度优先搜索思想:从图中某顶点v出发,在访问了v之后依次访问v的各个未曾访问过的邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,并使得“先被访问的顶点的邻接点先于后被访问的顶点的邻接点被访问,直至图中所有已被访问的顶点的邻接点都被访问到。如果此时图中尚有顶点未被访问,则需要另选一个未曾被访问过的顶点作为新的起始点,重复上述过程,直至图中所有顶点都被访问到为止。
- 算法特点: 队列
广度优先搜索类似于树的层次遍历,是按照一种由近及远的方式访问图的顶点。在进行广度优先搜索时需要使用队列
存储顶点信息,每出队一个顶点,则将它的所有邻接的未入队的点放入队列,直到队列为空。
最小生成树
- 生成树是加权图中包含所有顶点的无环连通子图,显然生成树只存在于连通图中,最小生成树(MST)是权值最小的生成树;
- Prim 和 Kruskal 只能用于无向加权图中,用于生成MST,当存在相同权重的边时,MST可能不唯一。
Prim算法
Prim算法的基本思想是贪心算法,通过不断搜索最小横切边实现,算法实现的关键是用“优先队列”这种数据结构来表示横切边,其中又分为以下两种:
1. 延时实现 – 优先队列
2. 即时实现 – 索引优先队列
Kruskal算法
Kruskal算法的基本思想是先对边按照权值升序排序,每次取出最短的(借助于优先队列)然后借助UF算法
不断加边并合并,直至形成生成树;E条边算法时间复杂度为ElogE
实现
/*
加权无向图,示例和代码不做特殊说明都是在连通图下,不适用于包含多个极大连通子图的图
*/
import java.util.*;
/**
* 无向图的遍历
*/
public class UndirectedGraph {
// 这里为了简单,用0开始连续的数字代表顶点(顶点ID),实际的实现中应该设计成类 Node 的形式
private final int V; // 顶点数目
private int E; // 边的数目
private boolean[] marked; // 用于遍历时标记用
private HashMap<Integer, TreeSet<Edge>> adj; // 邻接表用Hash表实现,key=Node_ID, value=相邻的边组成的链表(这里的实现是红黑二叉树)
public UndirectedGraph(int v) {
this.V = v;
this.E = 0;
this.adj = new HashMap<>();
// 初始化哈希表
for (int i = 0; i < V; i++) {
adj.put(i,new TreeSet<>());
}
}
public int getV() {
return V;
}
public int getE() {
return E;
}
public void addEdge(Edge e) {
int from = e.from;
int to = e.to;
adj.get(from).add(e);
adj.get(to).add(e);
E++;
}
// 返回 v 点所有的连接边,Iterable<Edge>方便遍历
Iterable<Edge> adj(int v) {
return adj.get(v);
}
// 返回加权无向图中所有的边
public Iterable<Edge> edges() {
TreeSet<Edge> edges = new TreeSet<>();
for (Map.Entry<Integer,TreeSet<Edge>> entry : adj.entrySet())
edges.addAll(entry.getValue());
return edges;
}
/*
无向图的连通性
*/
// DFS
public Queue<Integer> depthFirstSearch() {
marked = new boolean[V]; // 每次遍历之前懒惰初始化,每次都保证初始全是false
LinkedList<Integer> outQueue = new LinkedList<>(); // 这个队列是为了方便输出
dfs(0, outQueue);
return outQueue;
}
private void dfs(int root, LinkedList<Integer> outQueue) {
outQueue.offer(root); // offer <-> poll
marked[root] = true;
for (Edge edge : this.adj(root)) {
int other = edge.getOther(root);
if (!marked[other])
dfs(other, outQueue);
}
}
// BFS
public Queue<Integer> breadthFirstSearch() {
marked = new boolean[V]; // 每次遍历之前懒惰初始化,每次都保证初始全是false
LinkedList<Integer> outQueue = new LinkedList<>(); // 这个队列是为了方便输出
LinkedList<Integer> levelQueue = new LinkedList<>(); // 这个队列为了执行层次遍历逻辑
levelQueue.offer(0); // 加入起点
marked[0] = true; // 标记起点为已入队点
while (!levelQueue.isEmpty()) {
Integer node = levelQueue.pop();
outQueue.offer(node); // 从层次队列中删除,并添加到输出队列中
for (Edge edge : this.adj(node)) {
int other = edge.getOther(node);
if (!marked[other