您的位置:首页 > 其它

从原理看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即为所求最小生成树

示例代码:

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,就是刷新优先队列时得不到最小的值…博主找了一天也没找到问题,有发现原因的朋友请在底下留言,跪谢,呜呜呜…
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: