筆記來源:中國大學MOOC王道考研
一、概念
-
連通圖:圖中任意兩點都是連通的,那麼圖被稱作連通圖
-
生成樹:連通圖包含全部頂點的一個極小連通子圖
-
最小生成樹:在含有n個頂點的帶權無向連通圖中選擇n-1條邊,構成一棵極小連通子圖,並使該連通子圖中n-1條邊上權值之和達到最小,則稱其爲連通網的最小生成樹(不一定唯一)。
- 性質1:不一定唯一
- 性質2:如果所有邊的權重都不相同,則一定唯一
- 性質3:如果連通圖只有n-1條邊,則最小生成樹就是它本身
- 性質4:最小生成樹的邊數爲n-1
二、算法
2.1 Prim算法
步驟如下:
-
初始化,取任意頂點加入結果樹:
-
加入A相鄰的且不在結果樹中,並且是最小權值的點C
-
加入與A,C相鄰的且不在結果樹中,並且是最小權值的點B(BC最小)
-
重複上述步驟,直到所有頂點都進入結果樹:
java代碼實現如下:
我們需要用兩個數組來實現過程:
- min_weight[n]:當前結果樹到所有頂點的最短距離
- adjvex[n]:adjvex[C]=0,代表C是通過A加入結果樹的(0是A的下標)
/*
* 首先我們給出圖的存儲結構
*/
package MST;
import java.util.List;
public class Graph {
/*
* 點的存儲
*/
private List<String> vex;
/*
* 邊的存儲
*/
private int edges[][];
public Graph(List<String> vex, int[][] edges) {
this.vex = vex;
this.edges = edges;
}
public List<String> getVex() {
return vex;
}
public void setVex(List<String> vex) {
this.vex = vex;
}
public int[][] getEdges() {
return edges;
}
public void setEdges(int edges[][]) {
this.edges = edges;
}
public int getVexNum() {
return vex.size();
}
public int getEdgeNum() {
return edges.length;
}
}
然後初始化圖:
public class Prime {
int m = Integer.MAX_VALUE;
int[][] edges = {
{0, 3, 1, m, 4},
{3, 0, 2, m, m},
{1, 2, 0, 5, 6},
{m, m, 5, 0, m},
{4, m, 6, m, 0},
};
//打印最小生成樹
void MST_Prime(Graph G) {
int vexNum = G.getVexNum();//節點個數
int[] min_weight = new int[vexNum];//當前結果樹到所有頂點的最短距離
int[] adjvex = new int[vexNum];//adjvex[C]=0,代表C是通過A加入結果樹的(0是A的下標)
/*初始化兩個輔助數組*/
for(int i = 0; i < vexNum; i++) {
min_weight[i] = (G.getEdges())[0][i];//第一個頂點到其餘頂點的距離
adjvex[i]=0;
}
int min_edg;//當前挑選的最小權值
int min_vex = 0;//最小權值對應的節點下標
/*循環剩餘n-1個點*/
for(int i = 1; i < vexNum; i++) {
min_edg = Integer.MAX_VALUE;
for(int j = 1; j < vexNum; j++) {
if(min_weight[j]!=0 && min_weight[j] < min_edg) {
//尋找還沒有被挑選進來的,最小權重的點
min_edg = min_weight[j];
min_vex = j;
}
}
min_weight[min_vex] = 0;//納入結果樹
/*修改對應輔助數組的值*/
for(int j = 0; j < vexNum; j++) {
if(min_weight[j]!=0 && (G.getEdges())[min_vex][j]<min_weight[j] && (G.getEdges())[min_vex][j]>0) {
min_weight[j] = (G.getEdges())[min_vex][j];
adjvex[j]=min_vex;
}
}
int pre = adjvex[min_vex];
int end = min_vex;
System.out.println("("+G.getVex().get(pre)+","+G.getVex().get(end)+")");
}
}
//初始化圖
Graph init() {
List<String> vex=new ArrayList<String>();
vex.add("A");
vex.add("B");
vex.add("C");
vex.add("D");
vex.add("E");
Graph graph = new Graph(vex, edges);
return graph;
}
public static void main(String[] args) {
Prime prime = new Prime();
Graph graph = prime.init();
prime.MST_Prime(graph);
}
}
打印結果如下:
(A,C)
(C,B)
(A,E)
(C,D)
2.2 Kruskal算法
步驟如下:
-
每個頂點都是獨立的樹
-
挑選最短的邊AC,加入邊集中
-
依次加入BC,AB,但是AB構成了迴路,捨棄
-
重複直到取了n-1條邊
java代碼實現如下:
使用 並查集、堆排序、kruskal算法
引用並查集博客:Java實現並查集
//首先我們實現並查集(用來判斷是否構成迴路--是否屬於一個並查集)
public class UnionFindSet {
//查詢樹的根
public static int find(int x, int [] par){
if(par[x] == x){
return x;
}else{
//壓縮路徑,第二次查詢可以直接返回x的根而不用遞歸
return par[x] = find(par[x], par);
}
}
//合併
public static void unite(int x, int y, int [] par, int [] rank){
x = find(x, par);
y = find(y, par);
if(x == y){
return ;
}
if(rank[x] < rank[y]){
par[x] = y;
}else{
par[y] = x;
if(rank[x] == rank[y]) rank[x]++;
}
}
//判斷x和y是否屬於同一個集合
public static boolean same(int x, int y, int [] par){
return find(x, par) == find(y, par);
}
}
然後實現堆排序(稍作修改):
堆排序參考這篇博客:Java實現堆排序和圖解
public class HeapSort {
public static void sort(Edge[] arr){
//1.構建大頂堆
for(int i=arr.length/2-1;i>=0;i--){
//從第一個非葉子結點從下至上,從右至左調整結構
adjustHeap(arr,i,arr.length);
}
//2.調整堆結構+交換堆頂元素與末尾元素
for(int j=arr.length-1;j>0;j--){
swap(arr,0,j);//將堆頂元素與末尾元素進行交換
adjustHeap(arr,0,j);//重新對堆進行調整
}
}
/**
* 調整大頂堆(僅是調整過程,建立在大頂堆已構建的基礎上)
* @param arr
* @param i
* @param length
*/
public static void adjustHeap(Edge[] arr,int i,int length){
Edge temp = arr[i];//先取出當前元素i
for(int k=i*2+1;k<length;k=k*2+1){//從i結點的左子結點開始,也就是2i+1處開始
if(k+1<length && arr[k].weight<arr[k+1].weight){//如果左子結點小於右子結點,k指向右子結點
k++;
}
if(arr[k].weight >temp.weight){//如果子節點大於父節點,將子節點值賦給父節點(不用進行交換)
arr[i] = arr[k];
i = k;
}else{
break;
}
}
arr[i] = temp;//將temp值放到最終的位置
}
/**
* 交換元素
* @param arr
* @param a
* @param b
*/
public static void swap(Edge[] arr,int a ,int b){
Edge temp=arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
}
最後我們實現Kruskal算法:
package MST;
import java.util.ArrayList;
import java.util.List;
public class Kruskal {
int m = Integer.MAX_VALUE;
int[][] arr = {
{0, 3, 1, m, 4},
{3, 0, 2, m, m},
{1, 2, 0, 5, 6},
{m, m, 5, 0, m},
{4, m, 6, m, 0},
};
Graph init() {
List<String> vex=new ArrayList<String>();
vex.add("A");
vex.add("B");
vex.add("C");
vex.add("D");
vex.add("E");
Graph graph = new Graph(vex, arr);
return graph;
}
//kruskal算法
void MST_Kruskal(Graph G, Edge[] edges, int[] parents, int[] rank) {
HeapSort.sort(edges);//堆排序
for(int i = 0; i < G.getEdgeNum(); i++) {
if(!UnionFindSet.same(edges[i].a, edges[i].b, parents)) {
UnionFindSet.unite(edges[i].a, edges[i].b, parents, rank);
System.out.println("("+G.getVex().get(edges[i].a)+","+G.getVex().get(edges[i].b)+")");
}
}
}
public static void main(String[] args) {
Kruskal kruskal = new Kruskal();
Graph graph = kruskal.init();
int[] parents = {0,1,2,3,4};
int[] rank = {1,1,1,1,1};
Edge[] edges = new Edge[10];
int index = 0;
for(int i = 0; i < 5;i++) {
for(int j=0;j<i;j++) {
edges[index] = new Edge();
edges[index].weight = kruskal.arr[i][j];
edges[index].a = i;
edges[index++].b = j;
}
}
kruskal.MST_Kruskal(graph, edges, parents, rank);
}
}
輸出結構爲:
(C,A)
(C,B)
(E,A)
(D,C)