从原理看Kruskal与Prim求最小生成树
2016-04-26 20:50
447 查看
从原理看Kruskal与Prim求最小生成树
最小生成树:连接图中所有节点,使各边权重之和最小的树(因为权重之和最小,所以无环)。
Kruskal算法与Prim算法都是运用的贪心算法的思想进行求解,贪心算法是每步都取最优,最后使整体最优。其中,如何判断是不是最优很关键,其次,如何证明贪心的整体最优是算法正确性的保证。接下来,我将从上述两点来剖析这两个算法。
基本定义:无向图G=(V,E)的一个切割(S,V-S)是集合V的一个划分,如果一条边(u,v)属于E,并且一个端点位于集合S,另一个端点位于V-S,则称该条边横跨切割(S,V-S)。如果集合A是边的集合,不存在横跨该切割的边,则称该切割尊重集合A。在横跨一个切割的所有边中,权重最小的变称为轻量级边。
基本定理:设图G=(V,E),集合A为E的子集,且包括在图G的最小生成树中,设(S,V-S)是图G中尊重A的任意一个切割,又设(u,v)是横跨切割(S,V-S)的一条轻量级边。那么边(u,v)对于集合A是安全的(可以添加到集合A中)。
证明:在包含A的最小生成树T中,从端点u到端点v的路径再加上边(u,v)会形成一个环。这时,我们去掉T中横跨(S,S-V)的边,再添加上(u,v),显然当前的树仍是连通的,并且因为(u,v)是轻量级边,只会小于等于去掉的边,因此得到的新树的权重和小于等于T,因此这棵新树T’也是最小生成树。因为去掉的边不属于A(因为切割尊重A,而去掉的边横跨切割),所以A属于T’,并且(u,v)也属于T’,所以(u,v)对集合A是安全的。
重要推论:(概括就是图Ga=(V,A)中,连通分量之间的轻量级边对A是安全的)设图G=(V,E),集合A为E的子集,且包括在图G的最小生成树中,设C=(Vc,Ec)是森林Ga=(V,A)中的一个连通分量。如果边(u,v)是连接C和Ga中某个其他连通分量的一条轻量级边,则(u,v)对于集合A是安全的。
证明:根据刚才的定理,我们只需证明那条轻量级边(u,v)是横跨尊重A的分割即可。因为(u,v)连接(V,A)中的两个不同的连通分量,所以按照(Vc,V-Vc)进行切割是尊重A的。为什么呢?假设不尊重A,则A中有至少一条边横跨分割,连接了这个连通分量和其它边,那么这个连通分量显然不成立了,因此按照(Vc,V-Vc)进行切割是尊重A的,所以(u,v)是横跨切割的轻量级边,由定理得推论成立。
Kruskal算法:
简要概述:每次加入到集合A中的边总是权重最小且连接两个不同连通分量的边。
基本步骤:
1.将图G=(V,E)中所有的边按照权重由小到大进行排列;
2.设A=空,按照排列顺序,对每条边进行判断,如果边(u,v)连接的是(V,A)中的两棵不同的树(即不同的连通分量),则将边(u,v)添加到A中
3.重复步骤2,直到将所有边进行遍历
4.得到的集合A即为所求最小生成树
示例代码:
示例问题:
Prim算法:
简要概述:每次加入到集合A中的边总是邻接集合A且权重最小、连接两个不同连通分量的边(即将Kruskal中的选择范围缩小到了集合A的邻接边)。
基本步骤:
1.将选取的根节点设置距离(key)为0,其他节点距离(key)为无穷;
2.将所有节点添加到队列Q中;
3.弹出Q中距离最小的节点u,遍历与u相连的节点v,对于属于Q并且(u,v)权重小于v.key的节点v,设置v.key=(u,v)的权重;
4.重复步骤3直到Q为空。
Prim算法有一个bug,就是刷新优先队列时得不到最小的值…博主找了一天也没找到问题,有发现原因的朋友请在底下留言,跪谢,呜呜呜…
最小生成树:连接图中所有节点,使各边权重之和最小的树(因为权重之和最小,所以无环)。
Kruskal算法与Prim算法都是运用的贪心算法的思想进行求解,贪心算法是每步都取最优,最后使整体最优。其中,如何判断是不是最优很关键,其次,如何证明贪心的整体最优是算法正确性的保证。接下来,我将从上述两点来剖析这两个算法。
基本定义:无向图G=(V,E)的一个切割(S,V-S)是集合V的一个划分,如果一条边(u,v)属于E,并且一个端点位于集合S,另一个端点位于V-S,则称该条边横跨切割(S,V-S)。如果集合A是边的集合,不存在横跨该切割的边,则称该切割尊重集合A。在横跨一个切割的所有边中,权重最小的变称为轻量级边。
基本定理:设图G=(V,E),集合A为E的子集,且包括在图G的最小生成树中,设(S,V-S)是图G中尊重A的任意一个切割,又设(u,v)是横跨切割(S,V-S)的一条轻量级边。那么边(u,v)对于集合A是安全的(可以添加到集合A中)。
证明:在包含A的最小生成树T中,从端点u到端点v的路径再加上边(u,v)会形成一个环。这时,我们去掉T中横跨(S,S-V)的边,再添加上(u,v),显然当前的树仍是连通的,并且因为(u,v)是轻量级边,只会小于等于去掉的边,因此得到的新树的权重和小于等于T,因此这棵新树T’也是最小生成树。因为去掉的边不属于A(因为切割尊重A,而去掉的边横跨切割),所以A属于T’,并且(u,v)也属于T’,所以(u,v)对集合A是安全的。
重要推论:(概括就是图Ga=(V,A)中,连通分量之间的轻量级边对A是安全的)设图G=(V,E),集合A为E的子集,且包括在图G的最小生成树中,设C=(Vc,Ec)是森林Ga=(V,A)中的一个连通分量。如果边(u,v)是连接C和Ga中某个其他连通分量的一条轻量级边,则(u,v)对于集合A是安全的。
证明:根据刚才的定理,我们只需证明那条轻量级边(u,v)是横跨尊重A的分割即可。因为(u,v)连接(V,A)中的两个不同的连通分量,所以按照(Vc,V-Vc)进行切割是尊重A的。为什么呢?假设不尊重A,则A中有至少一条边横跨分割,连接了这个连通分量和其它边,那么这个连通分量显然不成立了,因此按照(Vc,V-Vc)进行切割是尊重A的,所以(u,v)是横跨切割的轻量级边,由定理得推论成立。
Kruskal算法:
简要概述:每次加入到集合A中的边总是权重最小且连接两个不同连通分量的边。
基本步骤:
1.将图G=(V,E)中所有的边按照权重由小到大进行排列;
2.设A=空,按照排列顺序,对每条边进行判断,如果边(u,v)连接的是(V,A)中的两棵不同的树(即不同的连通分量),则将边(u,v)添加到A中
3.重复步骤2,直到将所有边进行遍历
4.得到的集合A即为所求最小生成树
示例代码:
import java.util.ArrayList; import java.util.Scanner; public class Main{ public static void main(String[] args){ /** * 输入方式: * 第一行输入节点数 * 后面按照邻接矩阵的方式进行输入,因为是无向图,因此输入半个矩阵即可,对于无法连接到的节点,权重为-1 * (节点编号从0开始) */ Scanner input = new Scanner(System.in); int amount = input.nextInt(); Node[] nodes = new Node[amount]; ArrayList<Line> lines = new ArrayList<>(); for(int i = 0; i < amount; ++i){ nodes[i] = new Node(i,i); } for(int i = amount;i > 1; --i){ for(int j = 0; j < i - 1; ++j){ int weight = input.nextInt(); if(weight != -1){ lines.add(new Line(amount - i,amount - i + 1 + j,weight)); } } } /** * 按照快速排序对lines进行从小到大排序 */ quickSort(lines,0,lines.size() - 1); /** * 选取连接两棵树的边 */ ArrayList<Integer> treeNode = new ArrayList<>(); ArrayList<Line> answer = new ArrayList<>(); for(Line item:lines){ if(nodes[item.getNodeNo1()].getRespresentation() != nodes[item.getNodeNo2()].getRespresentation()){ int geter = nodes[item.getNodeNo1()].getRespresentation(); int setter = nodes[item.getNodeNo2()].getRespresentation(); for(Node node:nodes){ if(node.getRespresentation() == geter) node.setRespresentation(setter); } answer.add(item); } } for(int i = 0; i < answer.size(); ++i){ System.out.println(answer.get(i)); } } private static int partation(ArrayList<Line> lines,int start,int end){ int i,j; i = start; j = start - 1; int target = lines.get(end).getWeight(); for(;i < end; ++i){ if(lines.get(i).getWeight() < target){ ++j; Line.swap(lines.get(i), lines.get(j)); } } ++j; Line.swap(lines.get(end), lines.get(j)); return j; } public static void quickSort(ArrayList<Line> lines,int start,int end){ if(start < end){ int p = partation(lines, start, end); quickSort(lines,p + 1,end); quickSort(lines,start,p - 1); } } } class Node{ private int no; private int respresentation; public Node(int no,int respresentation) { super(); this.no = no; this.respresentation = respresentation; } public int getNo() { return no; } public void setNo(int no) { this.no = no; } public int getRespresentation() { return respresentation; } public void setRespresentation(int respresentation) { this.respresentation = respresentation; } } class Line{ private int nodeNo1; private int nodeNo2; private int weight; public Line(int nodeNo1, int nodeNo2, int weight) { super(); this.nodeNo1 = nodeNo1; this.nodeNo2 = nodeNo2; this.weight = weight; } public Line(Line line) { super(); this.nodeNo1 = line.getNodeNo1(); this.nodeNo2 = line.getNodeNo2(); this.weight = line.getWeight(); } public int getNodeNo1() { return nodeNo1; } public void setNodeNo1(int nodeNo1) { this.nodeNo1 = nodeNo1; } public int getNodeNo2() { return nodeNo2; } public void setNodeNo2(int nodeNo2) { this.nodeNo2 = nodeNo2; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } public void setInstance(Line line){ this.nodeNo1 = line.getNodeNo1(); this.nodeNo2 = line.getNodeNo2(); this.weight = line.getWeight(); } static void swap(Line a,Line b){ Line temp = new Line(a); a.setInstance(b); b.setInstance(temp); } @Override public String toString() { return "Line [nodeNo1=" + nodeNo1 + ", nodeNo2=" + nodeNo2 + ", weight=" + weight + "]"; } }
示例问题:
输入: 9 4 -1 -1 -1 -1 -1 8 -1 8 -1 -1 -1 -1 11 -1 7 -1 4 -1 -1 2 9 14 -1 -1 -1 10 -1 -1 -1 2 -1 -1 1 6 7 输出: Line [nodeNo1=6, nodeNo2=7, weight=1] Line [nodeNo1=5, nodeNo2=6, weight=2] Line [nodeNo1=2, nodeNo2=8, weight=2] Line [nodeNo1=0, nodeNo2=1, weight=4] Line [nodeNo1=2, nodeNo2=5, weight=4] Line [nodeNo1=2, nodeNo2=3, weight=7] Line [nodeNo1=1, nodeNo2=2, weight=8] Line [nodeNo1=3, nodeNo2=4, weight=9]
Prim算法:
简要概述:每次加入到集合A中的边总是邻接集合A且权重最小、连接两个不同连通分量的边(即将Kruskal中的选择范围缩小到了集合A的邻接边)。
基本步骤:
1.将选取的根节点设置距离(key)为0,其他节点距离(key)为无穷;
2.将所有节点添加到队列Q中;
3.弹出Q中距离最小的节点u,遍历与u相连的节点v,对于属于Q并且(u,v)权重小于v.key的节点v,设置v.key=(u,v)的权重;
4.重复步骤3直到Q为空。
import java.util.ArrayList; import java.util.PriorityQueue; import java.util.Scanner; public class Main { public static void main(String[] args){ /** * 输入方式: * 先输入节点的数量 * 后面输入图的邻接矩阵 * 最后输入指定的根节点的No */ Scanner input = new Scanner(System.in); int amount = input.nextInt(); Node[] nodes = new Node[amount]; int[][] weight = new int[amount][amount]; for(int i = 0; i < amount; ++i){ for(int j = 0; j < amount; ++j){ weight[i][j] = input.nextInt(); } nodes[i] = new Node(i); } int rootNo = input.nextInt(); /** * 将根节点的key设置为0,这样第一次循环先从根节点开始 */ nodes[rootNo].setKey(0); /** * 设置一个最小优先队列Q,将所有当前结点添加到Q中 * 设置一个集合A来保存已经添加的节点 */ PriorityQueue<Node> Q = new PriorityQueue<>(); for(int i = 0; i < amount; ++i) Q.add(nodes[i]); ArrayList<Node> A = new ArrayList<>(); /** * 开始进行循环 * 每次在Q中挑选key最小的节点,添加到A中,修改Q的邻接节点的key * 直到Q中不再含有节点 */ while(!Q.isEmpty()){ Node u = Q.poll(); /** * 刷新优先队列的排序 */ Q.add(u); u = Q.poll(); A.add(u); int no = u.getNo(); for(int i = 0; i < weight[no].length; ++i){ if(weight[no][i] != -1 && i != no && Q.contains(nodes[i]) && weight[no][i] < nodes[i].getKey()){ nodes[i].setKey(weight[no][i]); nodes[i].setParent(u); } } } /** * 输出结果 */ for(int i = 0; i < A.size(); ++i){ System.out.println(A.get(i)); } } } class Node implements Comparable<Node>{ private int no; private Node parent = null; private int key = 1000000000; public int getNo() { return no; } public void setNo(int no) { this.no = no; } public int getKey() { return key; } public void setKey(int key) { this.key = key; } public Node getParent() { return parent; } public void setParent(Node parent) { this.parent = parent; } public Node(int no) { super(); this.no = no; } @Override public String toString() { return "Node [no=" + no + ", parent=" + (parent == null?null:parent.getNo()) + ", key=" + key + "]"; } @Override public int compareTo(Node o) { return key - o.getKey(); } }
Prim算法有一个bug,就是刷新优先队列时得不到最小的值…博主找了一天也没找到问题,有发现原因的朋友请在底下留言,跪谢,呜呜呜…
相关文章推荐
- 读写锁
- SpringMVC之HiddenHttpMethodFilter 过滤器
- 笔试题之Java基础部分
- 用两个栈实现队列的操作
- 后台json数据为空,解析 出现空指针异常
- leetcode 043 Multiply Strings
- 装苹果
- crontab使用
- Exercise(7):和尚挑水
- LeeCode-Two Sum
- 最大公约数的实现
- 67. Add Binary
- android 多个EditText光标的问题
- 谈谈getElementsByClassName()
- Markdown 新手指南
- 1049 最大子段和
- MFC、VC++综合作业题
- 开源项目circular-progress-button源码解析
- LeeCode-Rotate Array
- CCSprite的使用方法