圖系列(二)圖的遍歷與拓撲排序
昂,還是希望多一點人看我的博客!好了,接下來是重中之重圖的遍歷。圖遍歷是很多其他算法的基礎,比如Dijkstra算法。
圖的遍歷
廣度優先遍歷
廣度優先遍歷的關鍵是需要藉助一個隊列。代碼如下:
class Solution {
List<Integer>[] adjs;
public void bfs(int n, int[][] edges) {
// 初始化鄰接鏈表
// ...
Queue<Integer> q = new LinkedList<>();
q.add(0); // 假設起點從0開始, 通過廣度優先遍歷可以遍歷所有元素。
while(!q.isEmpty()) {
int i = q.poll();
for (Integer adj : adjs[i]) {
q.add(adj);
}
}
}
}
這裏做了好幾個假設: 1. 起點從0開始;2. 從0可以遍歷到所有元素。相應的,爲了解除這兩個假設,首先,我們可以從特定的點開始,例如入度爲0的點;其次,使用一個數組,存儲每個點的訪問狀態,如果元素還沒有訪問,下一輪繼續訪問。舉個栗子:
class Solution {
List<Integer>[] adjs;
boolean[] alreadyVisit;
public void bfs(int n, int[][] edges) {
// 初始化鄰接鏈表
// ...
alreadyVisit = new boolean[n];
Queue<Integer> q = new LinkedList<>();
for(int i = 0 ; i < n;i++) {
if (!alreadyVisit[i]) {
q.add(i);
while (!q.isEmpty()) {
int head = q.poll();
alreadyVisit[head] = true;
for (Integer adj : adjs[head]) {
if (!alreadyVisit[adj]) {
q.add(adj);
}
}
}
}
}
}
}
參見leetcode題目: 課程表 。
深度優先遍歷
深度優先遍歷,主要利用遞歸。當然,我們不能夠一直避開環的問題。這裏介紹《算法與數據結構》中典型的處理方式—— 塗色。沒有訪問過的元素塗成白色,已經訪問過的元素塗成黑色,正在訪問,在同一條遞歸調用中的元素塗成灰色。代碼如下:
class Solution {
private List<Integer>[] adjs;
private Color[] colors;
public boolean containsLoop(int n, int[][] edges) {
// 初始化
adjs = new List[n];
colors = new Color[n];
for(int i = 0; i < n; i++) {
adjs[i] = new LinkedList<>();
colors[i] = Color.WHITE;
}
for(int i = 0 ; i<edges.length;i++) {
int[] edge = edges[i];
int from = edge[0];
int to = edge[1];
adjs[from].add(to);
}
for (int i = 0; i < n;i++) {
if (colors[i] == Color.WHITE) {
if(dfsVisit(i)) return true;
}
}
return false;
}
// 判斷是否存在環,存在立即返回true
// 不存在返回false。
private boolean dfsVisit(int i) {
colors[i] = Color.GRAY;
for (Integer adj: adjs[i]) {
if (colors[adj] == Color.GRAY) return true;
if (colors[adj] == Color.WHITE) {
if (dfsVisit(adj)) return true;
}
}
colors[i] = Color.BLACK;
return false;
}
private enum Color {
WHITE, GRAY, BLACK
}
}
其實,蠻簡單的,多些幾遍就會了。