文章目錄
一.圖論與圖
1.什麼是圖論
圖論(英語:Graph theory),是組合數學的一個分支,和其他數學分支,如羣論、矩陣論、拓撲學有着密切關係。
2.什麼是圖
圖是圖論的主要研究對象。圖是由若干給定的頂點及連接兩頂點的邊所構成的圖形,這種圖形通常用來描述某些事物之間的某種特定關係。頂點用於代表事物,連接兩頂點的邊則用於表示兩個事物間具有這種關係。
3.經典圖論問題和應用
1.經典問題
- 四色問題
- 柯尼斯堡七橋問題
- 最小生成樹問題
- 路徑問題
2.應用
4.經典圖論算法
- Prim算法
- Krusk算法
- Dijkstra算法
- Bellman-Ford算法
二.圖的分類
1.無向圖
無向圖指的是圖的邊沒有方向的分別,通常可以認爲無向圖實質上是同時擁有兩個方向的有向圖。
2.有向圖
有向圖指的是圖的邊有方向的分別。
3.無權圖
無權圖指的是圖的邊不帶權重。
4.帶權圖
帶權圖指的是圖的邊擁有權重。
5.稀疏圖
稀疏圖指的是對於頂點總數來說,每個頂點的邊遠少於頂點總數。
6.稠密圖與完全圖
當大部分頂點的邊數接近於頂點總數時可被稱爲稠密圖;當每個頂點的邊都鏈接了其餘的節點,則被稱爲完全圖。
三.圖的連通性
1.連通性
上圖中,擁有三個圖的集合,它們之間並不是聯通的,但是自身卻是連通的。所以圖具有連通性。
2.帶環邊與平行邊
對於簡單圖來說,頂點與頂點之間只含有一條邊;而對於複雜圖來說,頂點之間的邊有可能會形成自環或平行邊:
自環邊可以用來表示一個導向自身的路徑,而平行邊則可以表示導向其他頂點的多個路徑。
四.圖的表示
1.鄰接矩陣
我們可以使用一個 V 乘 V 的布爾
二維數組來表示圖。 當頂點 v 和頂點 w 之間有相連接的邊
時, 定義 v 行 w 列的元素值爲 true, 否則爲
false。
- Graph1
/**
* @Auther: ARong
* @Date: 2020/2/29 11:34 上午
* @Description: 使用鄰接矩陣實現的圖結構
*/
public class Graph1 {
private int n; // 頂點數
private int m; // 邊數
private boolean directed; // 是否爲有向圖
private boolean[][] graph; // 二維boolean數組表示的圖結構
public Graph1(int n, boolean directed) {
assert n >= 0;
this.n = n;
this.directed = directed;
this.m = 0;
graph = new boolean[n][n];
}
/*
* @Author ARong
* @Description 向節點間添加一條邊
* @Date 2020/2/29 11:39 上午
* @Param [v, m]
* @return void
**/
public void addEdge(int v, int m) {
assert v >= 0 && v < n;
assert m >= 0 && m < n;
// 如果有這條邊則不添加
if (hasEdge(v, m)) {
return;
}
graph[v][m] = true;
// 如果是無向圖,則還需要在另一方添加關係
if (!directed) {
graph[m][v] = true;
}
// 邊數增加
m++;
}
/*
* @Author ARong
* @Description 判斷v,m是否存在邊
* @Date 2020/2/29 11:42 上午
* @Param [v, m]
* @return boolean
**/
private boolean hasEdge(int v, int m) {
return graph[v][m];
}
}
使用鄰接矩陣來表示圖有許多缺點,例如:
- 空間需要提前聲明,如果圖的節點很多,那麼需要聲明的空間太大,且不能動態拓展
- 不能表示平行邊和自環關係
2.鄰接表
我們可以使用一個以頂點爲索引的列表數組, 其中的每個元素都是和該頂點相鄰的頂點列表。
使用鄰接表可以優化圖的空間大小,但是一些操作的時間會比鄰接矩陣實現的圖耗費時間多。
- Graph2
/**
* @Auther: ARong
* @Date: 2020/2/29 11:55 上午
* @Description: 使用鄰接表實現的圖結構
*/
public class Graph2 {
private int n; // 頂點數
private int m; // 邊數
private boolean directed; // 是否爲有向圖
private List<List<Integer>> graph; // 二維鏈表
public Graph2(int n, boolean directed) {
assert n >= 0;
this.n = n;
this.directed = directed;
this.m = 0;
graph = new ArrayList<>();
for (int i = 0; i < n; i++) {
// 初始化
graph.add(new ArrayList<>());
}
}
/*
* @Author ARong
* @Description 向v,m之間添加一條邊
* @Date 2020/2/29 12:01 下午
* @Param [v, m]
* @return void
**/
public void addEdge(int v, int m) {
assert v >= 0 && v < n;
assert m >= 0 && m < n;
// 如果有這條邊則不添加
if (hasEdge(v, m)) {
return;
}
graph.get(v).add(m);
// 如果是無向圖,則還需要在另一方添加關係
if (!hasEdge(v, m) && !directed) {
graph.get(m).add(n);
}
// 邊數增加
m++;
}
/*
* @Author ARong
* @Description 判斷m,v是否存在邊
* @Date 2020/2/29 11:58 上午
* @Param [v, m]
* @return boolean
**/
private boolean hasEdge(int v, int m) {
List<Integer> list = graph.get(v);
for (Integer node : list) {
if (node == m) {
return true;
}
}
return false;
}
}
五.圖的基本算法
0.基本代碼框架
以下是無向圖結構的基本代碼框架,一下算法實現基於該代碼框架
/**
* @Auther: ARong
* @Date: 2020/3/1 11:27 上午
* @Description: 使用鄰接表實現的無向圖
*/
public class UndirectedGraph {
// 頂點數量
private int vertexNum;
// 頂點邊數
private int edgeNum;
// 鄰接表
private List<List<Integer>> adj;
// 頂點信息
private List<String> vertexInfo;
/*
* @Author ARong
* @Description 傳入節點信息構造圖基本數據
* @Date 2020/3/1 11:29 上午
* @Param [vertexInfo]
* @return
**/
public UndirectedGraph(List<String> vertexInfo) {
this.vertexInfo = vertexInfo;
this.vertexNum = vertexInfo.size();
// 初始化圖
adj = new ArrayList<>();
for (int i = 0; i < vertexNum; i++) {
// 構造每一個頂點的連接對象
adj.add(new LinkedList<>());
}
}
public UndirectedGraph(List<String> vertexInfo, int[][] edges) {
this(vertexInfo);
// 額外構造邊信息
for (int[] twoVertex : edges) {
addEdge(twoVertex[0], twoVertex[1]);
}
}
public int vertexNum() {
return vertexNum;
}
public int edgeNum() {
return edgeNum;
}
/*
* @Author ARong
* @Description 添加邊
* @Date 2020/3/1 11:31 上午
* @Param [i, j]
* @return void
**/
public void addEdge(int i, int j) {
adj.get(i).add(j);
adj.get(j).add(i);
edgeNum++;
}
// 不需要set,所以不用返回List,返回可迭代對象就夠了
public Iterable<Integer> adj(int i) {
return adj.get(i);
}
/*
* @Author ARong
* @Description 獲取第i個頂點的數據
* @Date 2020/3/1 11:32 上午
* @Param [i]
* @return java.lang.String
**/
public String getVertexInfo(int i) {
return vertexInfo.get(i);
}
/*
* @Author ARong
* @Description 獲取第i個頂點的邊數
* @Date 2020/3/1 11:32 上午
* @Param [i]
* @return int
**/
public int degree(int i) {
return adj.get(i).size();
}
/*
* @Author ARong
* @Description 獲取頂點中最大的邊數
* @Date 2020/3/1 11:33 上午
* @Param []
* @return int
**/
public int maxDegree() {
int max = 0;
for (int i = 0;i < vertexNum;i++) {
if (degree(i) > max) {
max = degree(i);
}
}
return max;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(vertexNum).append("個頂點, ").append(edgeNum).append("條邊:\n");
for (int i = 0; i < vertexNum; i++) {
sb.append(i).append(": 【").append(adj.get(i)).append("】\n");
}
return sb.toString();
}
}
1.深度優先遍歷
/*
* @Author ARong
* @Description 對圖進行深度優先遍歷
* @Date 2020/3/1 11:45 上午
* @Param []
* @return java.util.List<java.lang.Integer>
**/
private void dfs(int i, boolean[] visited) {
visited[i] = true;
// 獲取到i所連接的節點列表
Iterator<Integer> iterator = adj(i).iterator();
while (iterator.hasNext()) {
Integer v = iterator.next();
if (!visited[v]) {
// v沒有被訪問過,繼續搜索
dfs(v, visited);
}
}
}
2.求解連通量
/*
* @Author ARong
* @Description 獲取圖的連通分量
* @Date 2020/3/1 12:23 下午
* @Param []
* @return java.util.List<java.lang.Integer>
**/
public int ccount() {
int ccount = 0;
// 初始化訪問數組,默認都是未訪問過
boolean[] visited = new boolean[vertexNum];
// 獲取圖的鄰接表
for (int i = 0; i < adj.size(); i++) {
if (!visited[i]) {
// 在未訪問過的節點開始dfs
dfs(i, visited);
ccount++;
}
}
return ccount;
}
3.判斷兩個頂點是否連通
/*
* @Author ARong
* @Description 判斷兩個節點是否相互連通
* @Date 2020/3/1 12:37 下午
* @Param [a, b]
* @return boolean
**/
private boolean isConnected(int a, int b) {
// 先對a進行深度優先遍歷,標示遍歷過的節點,若此時b沒有被標示到說明不連通
boolean[] visited = new boolean[vertexNum];
dfs(a, visited);
return visited[b];
}
4.尋找連通路徑
/*
* @Author ARong
* @Description 找到a與b之間的一條連通路徑
* @Date 2020/3/2 9:49 上午
* @Param [a, b]
* @return java.util.List<java.lang.Integer>
**/
public List<Integer> findPath(int a, int b) {
// 判斷是否連通
if (!isConnected(a, b)) {
return null;
}
// 連通,通過dfs搜索,並使用數組記錄路徑
HashMap<Integer, Integer> map = new HashMap<>();
boolean[] visited = new boolean[vertexNum];
dfs(a, b, visited, map);
// 將路徑重現
System.out.println(map.toString());
LinkedList<Integer> res = new LinkedList<>();
while (b != a) {
res.offerFirst(b);
b = map.get(b);
}
res.offerFirst(a);
return res;
}
/*
* @Author ARong
* @Description dfs搜索a到b的路徑
* @Date 2020/3/2 9:56 上午
* @Param [a, b, visited, list]
* @return void
**/
private void dfs(int a, int b, boolean[] visited, HashMap<Integer, Integer> map) {
// dfs終止條件,找到b
if (a == b) {
return;
}
visited[a] = true;
Iterator<Integer> iterator = adj(a).iterator();
while (iterator.hasNext()) {
Integer next = iterator.next();
// 繼續dfs
if(!visited[next]) {
// 記錄來時路徑
map.put(next, a);
dfs(next, b, visited, map);
}
}
}
6.廣度優先遍歷
/*
* @Author ARong
* @Description 廣度優先遍歷
* @Date 2020/3/2 11:46 上午
* @Param [i, visited]
* @return void
**/
public void bfs(int i, boolean[] visited) {
LinkedList<Integer> queue = new LinkedList<>();
queue.offer(i);
visited[i] = true;
while (!queue.isEmpty()) {
Integer poll = queue.poll();
// 訪問標記
Iterator<Integer> iterator = adj(poll).iterator();
while (iterator.hasNext()) {
// 獲取poll的全部連接的頂點,繼續bfs
Integer next = iterator.next();
if (!visited[next]) {
visited[next] = true;
queue.offer(next);
}
}
}
}
7.尋找最短路徑
/*
* @Author ARong
* @Description 獲取a和b之間的最短路徑
* @Date 2020/3/2 11:57 上午
* @Param [a, b]
* @return java.util.List<java.lang.Integer>
**/
public List<Integer> findShortestPath(int a, int b) {
// 判斷是否連通
if (!isConnected(a, b)) {
return null;
}
// 連通,通過dfs搜索,並使用數組記錄路徑
HashMap<Integer, Integer> from = new HashMap<>();
boolean[] visited = new boolean[vertexNum];
bfs(a, b, visited, from);
// 將路徑重現
LinkedList<Integer> res = new LinkedList<>();
while (b != a) {
res.offerFirst(b);
b = from.get(b);
}
res.offerFirst(a);
return res;
}
/*
* @Author ARong
* @Description 廣度優先遍歷尋找最短路徑
* @Date 2020/3/2 11:57 上午
* @Param [a, b, visited, from]
* @return void
**/
private void bfs(int a, int b, boolean[] visited, HashMap<Integer, Integer> from) {
LinkedList<Integer> queue = new LinkedList<>();
queue.offer(a);
visited[a] = true;
while (!queue.isEmpty()) {
Integer poll = queue.poll();
// 訪問標記
Iterator<Integer> iterator = adj(poll).iterator();
while (iterator.hasNext()) {
// 獲取poll的全部連接的頂點,繼續bfs
Integer next = iterator.next();
if (!visited[next]) {
// 記錄來時路徑
from.put(next, poll);
// 判斷是否找到
if (next == b) {
return;
}
visited[next] = true;
queue.offer(next);
}
}
}
}