文章目录
4.4 最小支撑树(Minimum-cost Spanning Tree)
概念Prim算法——从点出发
算法步骤示例代码实现 Kruskal算法——从边出发
算法步骤示例代码实现 4.4 最小支撑树(Minimum-cost Spanning Tree) 概念
定义
给定一个连通无向图G,且它的每条边均有相应的长度或权值,则MST是一个包括G中的所有顶点及其边子集的图,边的子集满足下列条件:
这个子集中所有边的权之和为所有子集中最小的
子集中的边能够保证图是连通的
环性质
环性质
假设T是一个有权无向图G=(V,E)的MST,如果选择一条属于E,但不属于T的边e加入到MST,从而使T形成一个环时,那么这个环中的任意一条边f都洞足如下关系
weight(f) <= weight(e)
分割性质
设集合U和W是图G=(V,E)的顶点集合的两个子集,这两个顶点子集将图分成了两部分,其中e是所有能够连接两个部分中权最小的边,那么e将是MST的一条边
Prim算法——从点出发 算法步骤
1️⃣ 选择图中的任意一个顶点N开始,初始化MST为N
2️⃣ 计算MST中每个顶点到不在MST中的每个顶点之间的距离
3️⃣ 选择这些距离中最小的那条边,并将这条边中的不在MST中的顶点加入到MST中
4️⃣ 重复步骤2和3,直到没有可以加入到MST中的顶点为止
示例 代码实现
相邻矩阵实现带权无向图
public class GraphMatrix { //用相邻矩阵实现无向图 private ArrayList V;//顶点 private int E;//边数 private int[][] matrix;//相邻矩阵 private int[] mark;//判定一个结点是否被访问过 public static final int VISITED = -2;//已经访问 public static final int UNVISITED = -3;//未访问 public static final int UNConNECTED = 1000;//未访问 public GraphMatrix(int n) { matrix = new int[n][n]; mark = new int[n]; for (int i = 0; i < n; i++) { mark[i] = UNVISITED; for (int j = 0; j < n; j++) { matrix[i][j] = UNCONNECTED; } } V = new ArrayList(n); } public void insertVertex(String vertex) { V.add(vertex); } public void insertEdge(int v1, int v2, int weight) { matrix[v1][v2] = weight; matrix[v2][v1] = weight; E++;//边数+1 } public int getWeight(int v1, int v2) { return matrix[v1][v2]; } public int getEdgeNum() { return E; } public int getVertexNum() { return V.size(); } public String getVertexValue(int index) { return V.get(index); } public int getFirst(int v) { for (int i = 0; i < V.size(); i++) { if (matrix[v][i] > 0) { return i; } } return -1; } public int getNext(int v1, int v2) { for (int i = v2 + 1; i < V.size(); i++) { if (matrix[v1][i] > 0) { return i; } } return -1; } public boolean isEdge(int v1, int v2) { return isLegalIndex(v1) && isLegalIndex(v2) && matrix[v1][v2] > 0; } public boolean isLegalIndex(int v) { return v >= 0 && v < V.size(); } public void DFS() { for (int i = 0; i < getVertexNum(); i++) { setMark(i, UNVISITED); }//初始化所有顶点 for (int i = 0; i < getVertexNum(); i++) { if (getMark(i) == UNVISITED) { DFSHelp(i); } } } public void DFSHelp(int v) { System.out.print(V.get(v) + " "); setMark(v, VISITED); for (int edge = getFirst(v); isEdge(v, edge); edge = getNext(v, edge)) { if (getMark(edge) == UNVISITED) { DFSHelp(edge); } } } public void BFS() { for (int i = 0; i < getVertexNum(); i++) { setMark(i, UNVISITED); }//初始化所有顶点 for (int i = 0; i < getVertexNum(); i++) { if (getMark(i) == UNVISITED) { BFSHelp(i); } } } public void BFSHelp(int v) { LQueue queue = new LQueue<>(); queue.enqueue(v); setMark(v, VISITED); while (!queue.isEmpty()) { int temp = queue.dequeue(); System.out.print(V.get(temp) + " "); for (int index = getFirst(temp); isEdge(temp, index); index = getNext(temp, index)) { if (getMark(index) == UNVISITED) { queue.enqueue(index); setMark(index, VISITED); } } } } public void setMark(int v, int val) { mark[v] = val; } public int getMark(int v) { return mark[v]; } //打印邻接矩阵 public void print() { for (int[] edge : matrix) { System.out.println(Arrays.toString(edge)); } } public static void main(String[] args) { //定义图的所有顶点 String[] vertexs = {"A", "B", "C", "D", "E", "F"}; //创建图 GraphMatrix graph = new GraphMatrix(vertexs.length); //添加顶点到图中 for (String vertex : vertexs) { graph.insertVertex(vertex); } //添加边到图中 graph.insertEdge(0, 2, 1); graph.insertEdge(0, 4, 1); graph.insertEdge(1, 2, 1); graph.insertEdge(1, 5, 1); graph.insertEdge(2, 3, 1); graph.insertEdge(2, 5, 1); graph.insertEdge(3, 5, 1); graph.insertEdge(4, 5, 1); graph.print(); graph.DFS(); System.out.println(); graph.BFS(); }}
public class Prim { private GraphMatrix originalGraph;//用相邻矩阵实现的原图 private ArrayList N;//不在MST集合中的元素下标 private ArrayList S;//在MST集合中的元素下标 private int vertexNum;//顶点数 public void createOriginalGraph(GraphMatrix originalGraph) { this.originalGraph = originalGraph; this.vertexNum = originalGraph.getVertexNum(); this.N = new ArrayList<>(vertexNum); for (int i = 0; i < vertexNum; i++) { N.add(i); } this.S = new ArrayList<>(0); } public void showOriginalGraph() { originalGraph.print(); } public void prim(int start) { int v1 = start; int v2 = originalGraph.getFirst(v1); originalGraph.setMark(v1, GraphMatrix.VISITED); S.add(v1);//加入MST N.remove(v1);//同时从N中移除 for (int i = 0; i < vertexNum - 1; i++) { int miniWeight = GraphMatrix.UNCONNECTED; for (int j = 0; j < S.size(); j++) {//从MST中的顶点 for (int k = 0; k < N.size(); k++) {//到非MST中的顶点 if (originalGraph.getMark(S.get(j)) == GraphMatrix.VISITED && originalGraph.getMark(N.get(k)) == GraphMatrix.UNVISITED && originalGraph.getWeight(S.get(j), N.get(k)) < miniWeight) { //只需要看从已访问结点到未访问结点的边 miniWeight = originalGraph.getWeight(S.get(j), N.get(k)); v1 = S.get(j); v2 = N.get(k); } } } System.out.println(originalGraph.getVertexValue(v1) + "-" + miniWeight + "->" + originalGraph.getVertexValue(v2)); //更新 S.add(v2);//加入MST N.remove((Object) v2);//同时从N中移除 originalGraph.setMark(v2, GraphMatrix.VISITED); } } public static void main(String[] args) { //定义图的所有顶点 String[] vertexs = {"A", "B", "C", "D", "E", "F"}; //创建图 GraphMatrix graph = new GraphMatrix(vertexs.length); //添加顶点到图中 for (String vertex : vertexs) { graph.insertVertex(vertex); } //添加边到图中 graph.insertEdge(0, 2, 7); graph.insertEdge(0, 4, 9); graph.insertEdge(1, 2, 5); graph.insertEdge(1, 5, 6); graph.insertEdge(2, 3, 1); graph.insertEdge(2, 5, 2); graph.insertEdge(3, 5, 2); graph.insertEdge(4, 5, 1); Prim test = new Prim(); test.createOriginalGraph(graph); test.showOriginalGraph(); test.prim(0); }}
[1000, 1000, 7, 1000, 9, 1000][1000, 1000, 5, 1000, 1000, 6][7, 5, 1000, 1, 1000, 2][1000, 1000, 1, 1000, 1000, 2][9, 1000, 1000, 1000, 1000, 1][1000, 6, 2, 2, 1, 1000]A-7->CC-1->DC-2->FF-1->EC-5->B
Kruskal算法——从边出发 算法步骤
1️⃣设连通网 N = (V, E ),令最小生成树初始状态为只有 n 个顶点而无边的非连通图 T=(V, { }),每个顶点自成一个连通分量。
2️⃣在 E 中选取代价最小的边,若该边依附 的顶点落在 T 中不同的连通分量上(即: 不能形成环),则将此边加入到 T 中;否 则,舍去此边,选取下一条代价最小的边。
3️⃣ 依此类推,直至 T 中所有顶点都在同一 连通分量上为止。
示例 代码实现
1️⃣ 利用并查集树判断结点是否连通,以及连通两个非连通分量
public class GTNodeA { private int parent;//父结点下标 private Object element;//存储元素内容 public GTNodeA() { this(null, -1); } public GTNodeA(Object element) { this(element, -1); } public GTNodeA(Object element, int parent) { this.element = element; this.parent = parent; } public int getParent() { return parent; } public int setParent(int parent) { return this.parent = parent; } public Object getElement() { return element; } public void setElement(Object element) { this.element = element; }}public class UnionFindTree { //用树实现并查集 public GTNodeA[] set; public UnionFindTree(GTNodeA[] set) { this.set = set; } public int find(int i) { GTNodeA current = set[i]; if (current.getParent()<0) return i; return current.setParent(find(current.getParent())); }//使用路劲压缩法:在查找某个元素是否属于某个集合时,将该结点到根结点路径上 // 所有结点的父指针全部改为指向根结点,这种方式可以产生极浅的树 public void union(int i, int j) { int root1 = find(i); int num1 = set[root1].getParent(); int root2 = find(j); int num2 = set[root2].getParent(); if (num1 <= num2) { set[root1].setParent(num1+num2); set[root2].setParent(root1); //将其中结点数少的一棵树的根结点的父结点设置为 //结点数多的一棵树的根结点 } else { set[root2].setParent(num1+num2); set[root1].setParent(root2); //将其中结点数少的一棵树的根结点的父结点设置为 //结点数多的一棵树的根结点 } }//使用了重量平衡原则 public boolean isConnected(int i,int j){ return find(i)==find(j); } public void print() { for (int i = 0; i < set.length; i++) { System.out.print(set[i].getElement() + " "); } } public static void main(String[] args) { GTNodeA node_A = new GTNodeA("A"); GTNodeA node_C = new GTNodeA("C"); GTNodeA node_H = new GTNodeA("H"); GTNodeA node_K = new GTNodeA("K"); GTNodeA node_E = new GTNodeA("E"); GTNodeA node_B = new GTNodeA("B"); GTNodeA node_D = new GTNodeA("D"); GTNodeA node_F = new GTNodeA("F"); GTNodeA node_J = new GTNodeA("J"); GTNodeA node_L = new GTNodeA("L"); GTNodeA node_N = new GTNodeA("N"); GTNodeA node_M = new GTNodeA("M"); GTNodeA node_I = new GTNodeA("I"); GTNodeA node_G = new GTNodeA("G"); GTNodeA[] test = {node_A, node_B, node_C, node_D, node_E, node_F, node_G, node_H, node_I, node_J, node_K, node_L, node_M, node_N}; UnionFindTree testTree = new UnionFindTree(test); System.out.print("initialized forest: "); testTree.print(); System.out.println(); System.out.println("find(1): " + testTree.find(1)); System.out.println("find(3): " + testTree.find(3)); System.out.println("find(4): " + testTree.find(4)); testTree.union(0, 4); testTree.union(0, 1); testTree.union(0, 3); System.out.println("union(0,4), union(0,1), union(0,3)"); System.out.println("find(1): " + testTree.find(1)); System.out.println("find(3): " + testTree.find(3)); System.out.println("find(4): " + testTree.find(4)); }}
2️⃣ 利用最小优先队列优化边的查找与删除
public class Edge implements Comparable { private int v1;//起点 private int v2;//终点 private int weight;//权重 public Edge(int v1, int v2) { this.v1 = v1; this.v2 = v2; } public Edge(int v1, int v2, int weight) { this.v1 = v1; this.v2 = v2; this.weight = weight; } public int getV1() { return v1; } public void setV1(int v1) { this.v1 = v1; } public int getV2() { return v2; } public void setV2(int v2) { this.v2 = v2; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } @Override public int compareTo(@NotNull Edge edge) { if (this.weight < edge.weight) { return -1; } else if (this.weight == edge.weight) { return 0; } else { return 1; } } @Override public String toString() { return "Edge{" + v1 + "->" + v2 + " weight=" + weight + '}'; }}public class MinPriorityQueue> { //最小优先队列 private static final int DEFAULT_CAPACITY = 10;//默认大小 private int currentSize;//当前堆的大小 private T[] array;//堆数组 public MinPriorityQueue() { this.array = (T[]) new Comparable[DEFAULT_CAPACITY + 1]; currentSize = 0; } public MinPriorityQueue(int size) { this.array = (T[]) new Comparable[size + 1]; currentSize = 0; } public MinPriorityQueue(T[] array) { this.array = (T[]) new Comparable[array.length + 1]; for (int i = 0; i < array.length; i++) { this.array[i + 1] = array[i]; }//从1开始算 currentSize = array.length; buildHeap(); } public void insert(T element) { try { if (isFull()) { throw new Exception("Array is full!"); } int temp = ++currentSize; array[temp] = element;//将element放在数组最后 while ((temp != 1) && (array[temp].compareTo(array[getParent(temp)]) < 0)) { swap(array, temp, getParent(temp)); temp = getParent(temp);//如果比父结点的值小则于其交换 }//注意根结点的下标为1,不是0 } catch (Exception e) { e.printStackTrace(); } } public T findMin() { return array[1]; } public void deleteMin() { try { if (isEmpty()) { throw new Exception("Array is empty!"); } else { swap(array, 1, currentSize--);//将堆顶元素放到最后,同时删除 if (currentSize != 0) siftDown(1); } } catch (Exception e) { e.printStackTrace(); } } private void siftDown(int pos) { try { if (pos < 0 || pos > currentSize) { throw new Exception("Illegal position!"); } while (!isLeaf(pos)) { int j = getLeft(pos); if ((j < currentSize) && (array[j].compareTo(array[j + 1])) > 0) j++; //跟子树中最小的值交换 if (array[pos].compareTo(array[j]) < 0) return; //当前值已经比子树中的值都小,则返回 swap(array, pos, j);//交换 pos = j; } } catch (Exception e) { e.printStackTrace(); } } public void print() { for (int i = 1; i <= currentSize; i++) { System.out.print(array[i] + " "); } } public void heapSort() { while (currentSize != 0) { System.out.print(findMin() + " "); deleteMin(); } } public boolean isEmpty() { return currentSize == 0; } public boolean isFull() { return currentSize == array.length - 1; } private boolean isLeaf(int i) { return i > currentSize / 2; } private void buildHeap() { for (int i = currentSize / 2; i > 0; i--) { siftDown(i);//对每个非叶子结点进行下沉操作 //从右到左,从下到上 } } private int getLeft(int i) { return 2 * i; } private int getRight(int i) { return 2 * i + 1; } private int getParent(int i) { return i / 2; } private void swap(T[] array, int x, int y) { T temp = array[y]; array[y] = array[x]; array[x] = temp; return; } public static void main(String[] args) throws Exception { Edge edge1 = new Edge(1, 2, 1); Edge edge2 = new Edge(1, 2, 2); Edge edge3 = new Edge(1, 2, 6); Edge edge4 = new Edge(1, 2, 4); Edge edge5 = new Edge(1, 2, 5); Edge edge6 = new Edge(1, 2, 7); Edge edge7 = new Edge(1, 2, 3);// Integer[] elements = {1, 2, 6, 4, 5, 7, 3};// MinPriorityQueue test = new MinPriorityQueue(elements); Edge[] edges = {edge1, edge2, edge3, edge4, edge5, edge6, edge7}; MinPriorityQueue test = new MinPriorityQueue(edges); test.print(); System.out.println(); test.heapSort(); System.out.println(); test.print(); }}
3️⃣ 算法具体实现
public class Kruskal { private ArrayList MST;//最小生成树中的所有边 private UnionFindTree ufTree;//并查集树,放索引 private MinPriorityQueue allEdges;//图中所有的边 private GraphMatrix originalGraph;//用相邻矩阵实现的原图 private int vertexNum;//结点数 public Kruskal(GraphMatrix originalGraph) { this.originalGraph = originalGraph; this.vertexNum = originalGraph.getVertexNum(); } public void generateMST() { this.MST = new ArrayList();//初始化MST GTNodeA[] nodes = new GTNodeA[vertexNum]; for (int i = 0; i < vertexNum; i++) { nodes[i] = new GTNodeA(i); } this.ufTree = new UnionFindTree(nodes);//初始化并查集 this.allEdges = new MinPriorityQueue<>(originalGraph.getEdgeNum());//初始化优先队列 for (int i = 0; i < vertexNum; i++) { for (int j = i + 1; j < vertexNum; j++) { if (originalGraph.getWeight(i, j) != GraphMatrix.UNCONNECTED) { allEdges.insert(new Edge(i, j, originalGraph.getWeight(i, j))); } } }//将所有的边加入优先队列中 while (!allEdges.isEmpty() && MST.size() < vertexNum - 1) { Edge e = allEdges.findMin(); allEdges.deleteMin(); int v1 = e.getV1(); int v2 = e.getV2(); //判断v1和v2是否已经连通 if (ufTree.isConnected(v1, v2)) continue; ufTree.union(v1, v2);//不连通则连接 MST.add(e);//将边并入MST } } public ArrayList getMST() { return MST; } public void printMST() { for (Edge edge : MST) { System.out.println(originalGraph.getVertexValue(edge.getV1()) + "-" + originalGraph.getWeight(edge.getV1(), edge.getV2()) + "->" + originalGraph.getVertexValue(edge.getV2())); } } public static void main(String[] args) { String[] vertexs = {"A", "B", "C", "D", "E", "F"}; //创建图 GraphMatrix graph = new GraphMatrix(vertexs.length); //添加顶点到图中 for (String vertex : vertexs) { graph.insertVertex(vertex); } //添加边到图中 graph.insertEdge(0, 2, 7); graph.insertEdge(0, 4, 9); graph.insertEdge(1, 2, 5); graph.insertEdge(1, 5, 6); graph.insertEdge(2, 3, 1); graph.insertEdge(2, 5, 2); graph.insertEdge(3, 5, 2); graph.insertEdge(4, 5, 1); Kruskal test = new Kruskal(graph); test.generateMST(); test.printMST(); }}
C-1->DE-1->FC-2->FB-5->CA-7->C