1.圖的幾個概念
(1)連通圖:在無向圖中,若任意兩個頂點vi與vj都有路徑相通,則稱該無向圖爲連通圖
(2)強連通圖:在有向圖中,若任意兩個頂點vi與vj都有路徑相通,則稱該有向圖爲強連通圖
(3)連通網:在連通圖中,若圖的邊具有一定的意義,每一條邊都對應着一個數值,稱爲權,權代表着連接兩個頂點的代價,稱這種連通圖叫做連通網
(4)生成樹:一個連通圖的生成樹是指一個連通子圖,它含有圖中全部 n 個頂點,但只有足以構成一棵樹的n-1條邊。一棵有n個頂點的生成樹有且僅有 n-1 條邊,如果生成樹中再添加一條邊,則必定成環
(5)最小生成樹:在連通網的所有生成樹中,所有邊的代價和最小的生成樹,稱爲最小生成樹(Minimum Cost Spanning Tree),簡稱 MST
2.並查集的基本介紹
需要詳情,自行百度
// 初始化的模板
int[] pre = new int[n];
for(int i = 0; i < n; i++) {
pre[i] = i;
}
// 查詢的模板(含路徑壓縮)
int find(int x){
if(pre[x] == x) {
return x;
}
// 遞歸
return pre[x] = find(pre[x]);
}
// 合併的模板
void merge(int x, int y){
int fx = find(x),
int fy = find(y);
if(fx != fy) {
pre[fx] = fy;
}
}
3.克魯斯卡爾(Kruskal)算法的概述
用於求解圖的最小生成樹
貪心策略:每次都選擇權值最小的邊作爲最小生成樹的邊
4.克魯斯卡爾(Kruskal)算法的基本思路
(1)構造只有 n 個(圖 graph 的頂點集合的大小)頂點的森林,即構造一個數組結構的並查集,初始化爲各個頂點的終點爲頂點自身
(2)把圖 graph 中頂點連通的邊按照權值從小到大進行排序
(3)按邊的權值從小到大取出來,加入到森林中,並使森林中不產生迴路,直至森林變成一棵樹爲止
(4)此時最小生成樹有 n 個頂點,n-1 條邊,並且 n-1 條邊不構成迴路
加入邊需要注意的事項:
a.判斷待加入的邊 edge ,加入到當前子圖後是否會構成迴路,如果構成迴路,則取出下一條權值較小的邊繼續判斷,
b.如果不構成迴路,則加入當前邊 edge 到當前子圖中,逐漸構成最小生成樹
c.構成迴路的判斷標準:當前待加入的邊所對應的兩個頂點在當前子圖中的終點是否相同
d.終點的理解:在當前子圖中,與待加入的頂點連通的最後頂點(並查集的查詢操作)
比如:已知 A->B 連通(A 的終點爲 B),B->C 連通(B 的終點爲 C),則通過 並查集 可知 A 的終點爲 C(A->C 連通) (可理解爲終點的傳遞性)
注意:在使用普里姆算法(Prim)構造最小生成樹的過程中,最小生成樹必定不會構成迴路,因爲頂點 ui 是從已經被訪問過的頂點集合 U 中獲取到的頂點,頂點 vj 是從未被訪問過的頂點集合 V-U(差集) 中獲取到的頂點,(ui,vj)構成的邊加入到最小生成樹中必定不會構成迴路,但是使用 克魯斯卡爾算法(Kruskal) 構造最小生成樹時,添加邊到最小生成樹中,存在構成迴路的可能,因此要判斷加入的邊是否會構成迴路,使用 並查集 判斷是否會構成迴路
5.克魯斯卡爾(Kruskal)算法的代碼實現
package com.zzb.algorithm.kruskal;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Comparator;
/**
* @Auther: Administrator
* @Date: 2020/3/31 22:08
* @Description: 克魯斯卡爾算法:求加權連通圖的最小生成樹的算法
* 貪心策略:每次都選擇權值最小的邊作爲最小生成樹的邊
*/
public class Kruskal {
public static void main(String[] args) {
// 頂點值數組
String[] vertexArray = {"A", "B", "C", "D", "E", "F", "G"};
// 鄰接矩陣
final int N = Integer.MAX_VALUE/2; // 表示不可以連接
int[][] matrix = new int[vertexArray.length][vertexArray.length];
matrix[0]=new int[]{N,5,7,N,N,N,2};
matrix[1]=new int[]{5,N,N,9,N,N,3};
matrix[2]=new int[]{7,N,N,N,8,N,N};
matrix[3]=new int[]{N,9,N,N,N,4,N};
matrix[4]=new int[]{N,N,8,N,N,5,4};
matrix[5]=new int[]{N,N,N,4,5,N,6};
matrix[6]=new int[]{2,3,N,N,4,6,N};
// 創建圖對象
Graph graph = new Graph(vertexArray, matrix);
// 構造最小生成樹
Edge[] edgeArrayOfMST = graph.createMST();
// 查看詳情
int sum = 0;
for(int i = 0; i < edgeArrayOfMST.length; i++) {
System.out.println(edgeArrayOfMST[i]);
sum += edgeArrayOfMST[i].getWeight();
}
System.out.println("總權值 == " + sum);
/*
邊 <A,G> 權值 2
邊 <B,G> 權值 3
邊 <D,F> 權值 4
邊 <E,G> 權值 4
邊 <E,F> 權值 5
邊 <A,C> 權值 7
總權值 == 25*/
}
}
/**
* 圖類
*/
class Graph implements Serializable {
private static final long serialVersionUID = 7611879468252933629L;
// 存儲圖中各個頂點的集合
private String[] vertexArray;
// 存儲圖中各條邊的鄰接矩陣
private int[][] edgeArray;
// 記錄圖中各個頂點的終點,用於判斷是否構成迴路
// 數組下標代表當前頂點在 vertexArray 中的下標(對應),數組的值代表當前頂點的終點的下標(並查集的使用)
private int[] endPointArray;
/**
* 構造器初始化
*
* @param vertexArray 頂點
* @param edgeArray 連接矩陣
*/
public Graph(String[] vertexArray, int[][] edgeArray) {
this.vertexArray = vertexArray;
this.edgeArray = edgeArray;
// 記錄圖中各個頂點的終點,用於判斷是否構成迴路
// 數組下標代表當前頂點在 vertexArray 中的下標(對應),數組的值代表當前頂點的終點的下標(並查集的使用)
// 初始化各個頂點的終點爲自身(並查集的初始化)
endPointArray = new int[this.vertexArray.length];
for(int i = 0; i < endPointArray.length; i++) {
endPointArray[i] = i;
}
}
/**
* 構造最小生成樹
*
* @return 以邊的數組方式保存最小生成樹
*/
public Edge[] createMST() {
// 克魯斯卡爾算法
Edge[] edgeArrayOfMST = kruskal(this);
return edgeArrayOfMST;
}
/**
* 克魯斯卡爾算法
* 貪心策略:每次都選擇權值最小的邊作爲最小生成樹的邊
*
* @param graph 由哪個圖來構造最小生成樹
* @return 以邊的數組方式保存最小生成樹
*/
private Edge[] kruskal(Graph graph) {
// 以邊的數組方式保存最小生成樹
int index = 0;
Edge[] edgeArrayOfMST = new Edge[graph.getVertexArray().length - 1];
// graph 的兩點之間連通的邊封裝成 Edge 對象
// graph 的鄰接矩陣的邊取值要麼是具體的權值,要麼就是代表不連通的值 Integer.MAX_VALUE/2
ArrayList<Edge> edgeList = new ArrayList<>();
for(int i = 0; i < graph.getEdgeArray().length; i++) {
for(int j = i+1; j < graph.getEdgeArray()[0].length; j++) {
if(graph.getEdgeArray()[i][j] != Integer.MAX_VALUE/2) {
edgeList.add(new Edge(graph.vertexArray[i], graph.vertexArray[j], graph.getEdgeArray()[i][j]));
}
}
}
// 按邊的權值從小到大排序
edgeList.sort(new Comparator<Edge>() {
@Override
public int compare(Edge o1, Edge o2) {
if(o1.getWeight() < o2.getWeight()) {
return -1;
}else if(o1.getWeight() > o2.getWeight()) {
return 1;
}else {
return 0;
}
}
});
int startIndex; // 一條邊的開始點的索引
int endIndex; // 一條邊的結束點的索引
int endPointOfStartIndex; // 一條邊的開始點的終點
int endPointOfEndIndex; // 一條邊的結束點的終點
// 按邊的權值從小到大取出來,加入到最小生成樹中
for(Edge edge : edgeList) {
// 判斷待加入的邊 edge ,加入到當前子圖後是否會構成迴路,如果構成迴路,則取出下一條權值較小的邊繼續判斷,
// 如果不構成迴路,則加入當前邊 edge 到當前子圖中,逐漸構成最小生成樹
// 構成迴路的判斷標準:當前待加入的邊所對應的兩個頂點在當前子圖中的終點是否相同
// 終點的理解:在當前子圖中,與待加入的頂點連通的最後頂點(並查集的查詢操作)
// 比如:A->B 連通(A 的終點爲 B),B->C 連通(B 的終點爲 C),則通過 並查集 可知 A 的終點爲 C (可理解爲終點的傳遞性)
startIndex = graph.getIndexOfVertex(edge.getStart()); // 頂點
endIndex = graph.getIndexOfVertex(edge.getEnd()); // 頂點
endPointOfStartIndex = graph.getIndexOfEndPoint(startIndex); // 終點
endPointOfEndIndex = graph.getIndexOfEndPoint(endIndex); // 終點
if(endPointOfStartIndex != endPointOfEndIndex) {
edgeArrayOfMST[index] = edge;
index++;
// 設置 endPointOfStartIndex 的終點爲 endPointOfEndIndex
graph.merge( endPointOfStartIndex, endPointOfEndIndex);
}
}
// 返回最小生成樹
return edgeArrayOfMST;
}
/**
* 獲取某個頂點的終點(並查集的查詢操作)
*
* @param index 某個頂點在圖的頂點集合 vertexArray 中的索引
* @return 某個頂點的終點
*/
private int getIndexOfEndPoint(int index) {
if(this.endPointArray[index] == index) {
return index;
}
// 遞歸
return this.endPointArray[index] = getIndexOfEndPoint(this.endPointArray[index]);
}
/**
* 設置 endPointOfStartIndex 的終點爲 endPointOfEndIndex(並查集的合併操作)
*
* @param endPointOfStartIndex 開始點
* @param endPointOfEndIndex 結束點
*/
private void merge(int endPointOfStartIndex, int endPointOfEndIndex) {
this.endPointArray[endPointOfStartIndex] = endPointOfEndIndex;
}
/**
* 獲取各個頂點所對應的索引
*
* @param vertex 頂點所在集合的值
* @return 獲取各個頂點所對應的索引
*/
private int getIndexOfVertex(String vertex) {
for(int i = 0; i < this.vertexArray.length; i++) {
if(vertex.equals(this.vertexArray[i])) {
return i;
}
}
return -1;
}
public String[] getVertexArray() {
return vertexArray;
}
public void setVertexArray(String[] vertexArray) {
this.vertexArray = vertexArray;
}
public int[][] getEdgeArray() {
return edgeArray;
}
public void setEdgeArray(int[][] edgeArray) {
this.edgeArray = edgeArray;
}
}
/**
* 邊類
*/
class Edge implements Serializable {
private static final long serialVersionUID = 5009546370782229661L;
// 邊的開始點
private String start;
// 邊的結束點
private String end;
// 邊的權值
private int weight;
/**
* 構造器初始化
*
* @param start 邊的開始點
* @param end 邊的結束點
* @param weight 邊的權值
*/
public Edge(String start, String end, int weight) {
this.start = start;
this.end = end;
this.weight = weight;
}
@Override
public String toString() {
return "邊 <" + this.getStart() + "," + this.getEnd() + "> " + "權值 " + this.getWeight();
}
public String getStart() {
return start;
}
public void setStart(String start) {
this.start = start;
}
public String getEnd() {
return end;
}
public void setEnd(String end) {
this.end = end;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
}