深度優先搜索與廣度優先搜索
圖論基礎知識
圖是由一組頂點和一組能夠將兩個頂點相連的邊組成的(可以無邊,但是至少包含一個頂點):
- 一組頂點: 通常用V(vertex)表示頂點集合
- 一組邊: 通常用E(edge)表示邊集合
圖的基本術語解釋
[外鏈圖片轉存失敗(img-SuflOrhS-1566875437171)(https://github.com/PandaHigh/image/blob/master/baseMean.png?raw=true)]
- 度 : 某個頂點的度數即爲與其相連的邊的總數
- 入度 : 存在有向圖中, 所有的指向某個頂點的邊的總數
- 出度 : 存在有向圖中, 某個頂點指向其他頂點的邊的總數
- 連通圖 : 從任意一個頂點都存在一條路徑到達另一個任意頂點,稱這幅圖是連通圖.
- 連通分量 : 圖中的連通圖數量(連通圖之間互無關聯)
- 二分圖 : 二分圖是一種能夠將所有頂點分爲兩部分的圖,
圖的分類
[外鏈圖片轉存失敗(img-D96KImJe-1566875437172)(https://github.com/PandaHigh/image/blob/master/DirectedGraphAndUndirectedGgraph.png?raw=true)]
圖可以分爲無向圖和有向圖:
- 無向圖: 在無向圖中的邊僅僅是兩個頂點之間的連接
- 有向圖: 在有向圖中的邊是有方向的,指的是箭頭尾端的頂點指向箭頭頂端的頂點, 無向圖是特殊的有向圖
[外鏈圖片轉存失敗(img-Vka0oFVs-1566875437173)(https://github.com/PandaHigh/image/blob/master/weightGraph.png?raw=true)]
圖可以分爲有權圖和無權圖: - 無權圖: 每條邊均沒有權重,也可以理解爲每條邊的權重爲1.
- 有權圖: 每條邊都有相應的權重(weight), 無權圖是特殊的有權圖.
[外鏈圖片轉存失敗(img-r8s4gSHR-1566875437173)(https://github.com/PandaHigh/image/blob/master/connectedGraph.png?raw=true)]
圖還可以分爲連通圖和非連通圖:
- 連通圖: 所有的頂點都有路徑相連 (一個擁有v個頂點的連通圖(即每個頂點都有聯繫)中,至少擁有v-1條邊)
- 非連通圖: 至少存在某兩個頂點沒有路徑相連
圖的表示方法
鄰接矩陣
使用一個V乘以V的布爾矩陣, 當頂點v和頂點w之間有相連接的邊時,定義布爾矩陣的第v行第w列的元素值爲true,頂點與頂點之間沒有相連接則定義爲false.
鄰接鏈表
使用一個以頂點爲索引的列表數組,對於數組的每個位置都存儲着一條與該頂點相連接的頂點構成的鏈表
例如在無向無權圖中的:
在無向有權圖中 :
在有向無權圖中:
鄰接矩陣與鄰接矩陣對比:
- 鄰接矩陣由於沒有相連的邊也佔有空間,因此存在浪費空間的問題,而鄰接鏈表則比較合理地利用空間
- 鄰接鏈表比較耗時,犧牲很大的時間來查找,因此比較耗時,而鄰接矩陣法相比鄰接鏈表法來說,時間複雜度低,空間複雜度高。
圖的遍歷
深度優先搜索 (Depth First Search, DFS)
基本思路 : 深度優先搜索遍歷圖的方法是,從圖中某個頂點出發
- 訪問指定的起始頂點
- 若當前訪問的頂點的鄰接頂點有未被訪問的,則任選一個頂點訪問之; 反之, 退回到發現當前訪問節點的那條邊的起始節點; 直到與起始頂點相通的全部頂點都訪問完畢.
[外鏈圖片轉存失敗(img-XdljOIIF-1566875437176)(https://github.com/PandaHigh/image/blob/master/depthFirstSearch.png?raw=true)]
使用深度優先搜索查找圖中的路徑:
/** 深度優先搜索查找圖中的路徑 */
public class DepthFirstPaths {
private boolean[] marked;//這個頂點是否已被訪問
private int[] edgeTo;//從起點到一個頂點的已知路徑上的最後一個頂點
private int s;//搜索起點
public DepthFirstPaths(Graph G, int s){
marked=new boolean[G.V()];
edgeTo=new int[G.V()];
this.s=s;
dfs(G,s);
}
/**
* 深度優先搜索
* @param G 要搜索的圖
* @param v 搜索頂點
*/
private void dfs(Graph G,int v){
marked[v]=true;
for (int w : G.adj(v)) {
if(!marked[w]){
edgeTo[w]=v;
dfs(G,w);
}
}
}
/**
* 是否有路徑可以到達v
* @param v 要到達的頂點
* @return 有路徑可以到達v爲true,反之爲false
*/
public boolean hasPathTo(int v){
return marked[v];
}
/**
* 從起點s到達v頂點的路徑
* @param v 要到達的頂點
* @return 路徑
*/
public Iterable<Integer> pathTo(int v){
if(!hasPathTo(v)){
return null;//沒有找到頂點v
}
Stack<Integer> path = new Stack<>();//從終點v到起點s挨個壓入棧中
for(int x=v;x!=s;x=edgeTo[x]){
path.push(x);
}
path.push(s);
return path;
}
}
廣度優先搜索 (Breadth First Search, BFS)
基本思路 : 廣度優先搜索,維護了一條隊列
- 先將指定的搜索頂點加入到隊列中
- 然後從隊列中取出一個頂點,將其標記爲已被訪問
- 得到該頂點的所有鄰接頂點並將其中沒有被訪問過的頂點加入到隊列中去
- 轉入步驟2繼續從隊列取出頂點,直到隊列中不存在任何頂點.
[外鏈圖片轉存失敗(img-IiMeoKDz-1566875437177)(https://github.com/PandaHigh/image/blob/master/breadthFirstSearch.png?raw=true)]
使用廣度優先搜索查找圖中頂點路徑 :
/** 廣度優先搜索查找圖中的路徑 */
public class BreadthFirstPaths {
private boolean [] marked;//這個頂點是否已被訪問
private int[] edgeTo;//從起點到一個頂點的已知路徑上的最後一個頂點
private int s;//搜索起點
public BreadthFirstPaths(Graph G,int s){
marked=new boolean[G.V()];
edgeTo=new int[G.V()];
this.s=s;
bfs(G,s);
}
/**
* 廣度優先搜索
* @param G 要搜索的圖
* @param s 搜索起點
*/
private void bfs(Graph G,int s){
Queue<Integer> queue = new Queue<>();
marked[s]=true;
queue.enqueue(s);
while(!queue.isEmpty()){
int v=queue.dequeue();
for (Integer w : G.adj(v)) {
if(!marked[w]) {
marked[w] = true;
edgeTo[w] = v;
queue.enqueue(w);
}
}
}
}
/**
* 從起點s到達v頂點的路徑
* @param v 要到達的頂點
* @return 路徑
*/
public Iterable<Integer> pathTo(int v){
if(!hasPathTo(v)){
return null;
}
Stack<Integer> path = new Stack<Integer>();
for(int x=v;x!=s;x=edgeTo[x]){
path.push(x);
}
path.push(s);
return path;
}
}
深度優先搜索就像是一個人在走迷宮,不撞南牆不回頭
而廣度優先搜索就像是一組警察在搜查犯人,同時往不同的方向搜尋.