无向图
使用的数据结构:
- 邻接表:使用一个以顶点为索引的列表数组 – 每个元素都是和该顶点相邻的顶点列表
- 空间和V+E成正比
- 添加一条条边所需的时间为常数
- 遍历顶点v的所有相邻顶点所需的时间和v的度数成正比
基本数据结构:
public class Graph {
private final int V;
private int E;
private Bag<Integer>[] adj; //邻接表
public Graph(int V) {
this.V = V;
this.E = 0;
// 充当一条链表,存储相邻的节点
Bag<Integer>[] bags = (Bag<Integer>[]) new Bag[V];
for (int i = 0; i < V; i++) {
adj[i] = new Bag<>();
}
}
public int E() {
return E;
}
public int V() {
return V;
}
public void addEdge(int v, int w) {
adj[v].add(w);
adj[w].add(v);
E++;
}
// 遍历相邻的节点
public Iterable<Integer> adj(int v) {
return adj[v];
}
}
深度优先搜索(DFS)
核心方法:访问一个顶点时
- 将它标记为已访问
- 递归地访问它的所有没有被标记过的邻近顶点
- DFS中每条边总会被访问量词,且在第二次时会发现这个顶点已经被标记过
public class DepthFirstPaths {
private boolean[] marked;
// 比如需要从3开始遍历到4,5;5找到后返回到,再从3到4
// 那么edgeTo[5] = 3; edgeTo[4] = 3; -- 都是从3到这两个点
private int[] edgeTo; // 从起点到一个顶点的已知路径上的最后一个顶点
private final int s;
public DepthFirstPaths(Graph G, int s) {
marked = new boolean[G.V()];
edgeTo = new int[G.V()];
this.s = s;
dfs(G, s);
}
private void dfs(Graph G, int v) {
// 访问到该顶点,置为true
marked[v] = true;
for (int w : G.adj(v)) {
// 如果没有访问过
if (!marked[w]) {
// 将路径添加到数组中
edgeTo[w] = v;
dfs(G, v);
}
}
}
public boolean hasPathTo(int v) {
return marked[v];
}
public Iterable<Integer> pathTo(int v) {
if(!hasPathTo(v)) return null;
Stack<Integer> path = new Stack<>();
// 根据路径点,依次入栈,最后将起点入栈,后进先出弹出。得到路径
for (int i = v; i != s ; i = edgeTo[i]) {
path.push(i);
}
path.push(s);
return path;
}
}
DFS应用:检测连通分量:
// 记录连通分量
public class CC {
private boolean[] marked;
// 记录顶点所属的连通分量的id
private int[] id;
private int count;
public CC(Graph G) {
marked = new boolean[G.V()];
id = new int[G.V()];
for (int i = 0; i < G.V(); i++) {
if (!marked[i]) {
// 执行完一次DFS,遍历这个点可达的所有顶点。
// 这些顶点构成了一个连通分量
dfs(G, i);
count++;
}
}
}
private void dfs(Graph G, int s) {
marked[s] = true;
id[s] = count;
for (int w : G.adj(s)) {
if (!marked[s]) {
dfs(G, w);
}
}
}
public boolean connected(int v, int w){
// 如果所属的连通分量id相同,则证明v和w相互连通
return id[v] == id[w];
}
}
广度优先搜索 – BFS
比喻:广度优先搜索就像是一组人在一起朝各个方向走这座迷宫。
通过栈(LIFO)来描述DFS,用队列(FIFO)来描述BFS
算法:
- 取队列中的一下一个顶点并标记它
- 将于v相邻的所有未被标记过的顶点加入队列,并标记
- 注意入队的顺序是有严格要求的,因为是将结点的邻接结点进行的入队
private void bfs(Graph G, int s) {
// 用LinkedList来实现队列操作
Queue<Integer> queue = new LinkedList<>();
marked[s] = true;
// 加入队首
queue.offer(s);
while (!queue.isEmpty()) {
// 取出队尾元素
int v = queue.poll();
for (int w : G.adj(v)) {
// 将所有未被标记的元素加入队列
if (!marked[w]) {
marked[w] = true;
queue.offer(w);
edgeTo[w] = v;
}
}
}
}