無向圖
使用的數據結構:
- 鄰接表:使用一個以頂點爲索引的列表數組 – 每個元素都是和該頂點相鄰的頂點列表
- 空間和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;
}
}
}
}