标题：kruskal算法
Kruskal算法
算法定义克鲁斯卡尔算法
假设 WN=(V,{E}) 是一个含有 n 个顶点的连通网，则按照克鲁斯卡尔算法构造最小生成树的过程为：先构造一个只含 n 个顶点，而边集为空的子图，若将该子图中各个顶点看成是各棵树上的根结点，则它是一个含有 n 棵树的一个森林。之后，从网的边集 E 中选取一条权值最小的边，若该条边的两个顶点分属不同的树，则将其加入子图，也就是说，将这两个顶点分别所在的两棵树合成一棵树；反之，若该条边的两个顶点已落在同一棵树上，则不可取，而应该取下一条权值最小的边再试之。依次类推，直至森林中只有一棵树，也即子图中含有 n-1条边为止。举例描述克鲁斯卡尔算法(Kruskal's algorithm)是两个经典的最小生成树算法的较为简单理解的一个。这里面充分体现了贪心算法的精髓。大致的流程可以用一个图来表示。这里的图的选择借用了Wikipedia上的那个。非常清晰且直观。
首先第一步，我们有一张图，有若干点和边
如下图所示：
  .
.
.
.
.
.
第一步我们要做的事情就是将所有的边的长度排序，用排序的结果作为我们选择边的依据。这里再次体现了贪心算法的思想。资源排序，对局部最优的资源进行选择。
排序完成后，我们率先选择了边AD。这样我们的图就变成了
  .
.
.
.
.
.
第二步，在剩下的边中寻找。我们找到了CE。这里边的权重也是5
  .
.
.
.
.
.
依次类推我们找到了6,7,7。完成之后，图变成了这个样子。
  .
.
.
.
.
.
下一步就是关键了。下面选择那条边呢？ BC或者EF吗？都不是，尽管现在长度为8的边是最小的未选择的边。但是现在他们已经连通了（对于BC可以通过CE,EB来连接，类似的EF可以通过EB,BA,AD,DF来接连）。所以我们不需要选择他们。类似的BD也已经连通了（这里上图的连通线用红色表示了）。
最后就剩下EG和FG了。当然我们选择了EG。最后成功的图就是下图：
  .
.
.
.
.
.
到这里所有的边点都已经连通了，一个最小生成树构建完成。
代码实现
伪代码MST-KRUSKAL(G，w) 
  C代码实现/* Kruskal.c
Copyright (c) 2002,2006 by ctu_85
All Rights Reserved.
*/
/* I am sorry to say that the situation of unconnected graph is not concerned */
#include "stdio.h"
#define maxver 10
#define maxright 100
int G[maxver][maxver],record=0,touched[maxver][maxver];
int circle=0;
int FindCircle(int,int,int,int);
int main()
{
int path[maxver][2],used[maxver][maxver];
int i,j,k,t,min=maxright,exsit=0;
int v1,v2,num,temp,status=0;
restart:
printf("Please enter the number of vertex(s) in the graph:\n");
scanf("%d",&num);
if(num>maxver||num<0)
{
printf("Error!Please reinput!\n");
goto restart;
}
for(j=0;j<num;j++)
for(k=0;k<num;k++)
{
if(j==k)
{
G[j][k]=maxright;
used[j][k]=1;
touched[j][k]=0;
}
else
if(j<k)
{
re:
printf("Please input the right between vertex %d and vertex %d,if no edge exists please input -1:\n",j+1,k+1）;
scanf("%d",&temp);
if(temp>=maxright||temp<-1）
{
printf("Invalid input!\n");
goto re;
}
if(temp==-1）
temp=maxright;
G[j][k]=G[k][j]=temp;
used[j][k]=used[k][j]=0;
touched[j][k]=touched[k][j]=0;
}
}
for(j=0;j<num;j++)
{
path[j][0]=0;
path[j][1]=0;
}
for(j=0;j<num;j++)
{
status=0;
for(k=0;k<num;k++)
if(G[j][k]<maxright)
{
status=1;
break;
}
if(status==0)
break;
}
for(i=0;i<num-1&&status;i++)
{
for(j=0;j<num;j++)
for(k=0;k<num;k++)
if(G[j][k]<min&&!used[j][k])
{
v1=j;
v2=k;
min=G[j][k];
}
if(!used[v1][v2])
{
used[v1][v2]=1;
used[v2][v1]=1;
touched[v1][v2]=1;
touched[v2][v1]=1;
path[i][0]=v1;
path[i][1]=v2;
for(t=0;t<record;t++)
FindCircle(path[t][0],path[t][0],num,path[t][0]);
if(circle)
{/*if a circle exsits,roll back*/
circle=0;
i--;
exsit=0;
touched[v1][v2]=0;
touched[v2][v1]=0;
min=maxright;
}
else
{
record++;
min=maxright;
}
}
}
if(!status)
printf("We cannot deal with it because the graph is not connected!\n");
else
{
for(i=0;i<num-1;i++)
printf("Path %d:vertex %d to vertex %d\n",i+1,path[i][0]+1,path[i][1]+1）;
}
return 1;
}
int FindCircle(int start,int begin,int times,int pre)
{ /* to judge whether a circle is produced*/
int i;
for(i=0;i<times;i++)
if(touched[begin]==1）
{
if(i==start&&pre!=start)
{
circle=1;
return 1;
break;
}
else
if(pre!=i)
FindCircle(start,i,times,begin);
else
continue;
}
return 1;
}matlab代码实现function Kruskal(w,MAX)
%此程序为最小支撑树的Kruskal算法实现
%w为无向图的距离矩阵，故为对称矩阵
%MAX为距离矩阵中∞的实际输入值
%时间：2011年6月22日0:07:53
len=length(w); %图的点数
edge=zeros(len*(len-1）,3）; %用于存储图中的边
count=1; %图中的边数
for i=1:len-1 %循环距离矩阵，记录存储边
for j=i+1:len
if w(i,j)~=MAX
edge(count,1）=w(i,j);
edge(count,2）=i;
edge(count,3）=j;
count=count+1;
end
end
end
edge=edge（1:count-1,:); %去掉无用边
[tmp,index]=sort(edge(:,1）); %所有边按升序排序
i=3; %其实测试边数为3条（3条以下无法构成圈，即无需检测）
while 1
x=findcycle(edge(index（1:i),:),len); %检测这些边是否构成圈
if x 
index(i)=0; %若构成圈，则将该边对应的index项标记为0，以便除去
else
i=i+1; %若没有构成圈，则i加1，加入下一边检测
end
index=index(index>0); %将构成圈的边从index中除去
if i==len
break; %找到符合条件的点数减一条的边，即找到一个最小支撑树
end
end
index=index（1:len-1）; %截短index矩阵，保留前len-1项
%%%%%%%%%%%% 结果显示 %%%%%%%%%%%%%
s=sprintf('\n\t%s\t%s\t %s\t','边端点','距离','是否在最小支撑树');
for i=1:count-1
edge_tmp=edge(i,:);
if ~isempty(find(index==i,1）) 
s_tmp=sprintf('\n \t (%d,%d)\t %d\t %s\t',edge_tmp（2）,edge_tmp（3）,edge_tmp（1）,'√');
else
s_tmp=sprintf('\n \t (%d,%d)\t %d\t %s\t',edge_tmp（2）,edge_tmp（3）,edge_tmp（1）,'×');
end
s=strcat(s,s_tmp);
end
disp(s);
end
function isfind=findcycle(w,N)
%本程序用于判断所给的边能否构成圈：有圈，返回1；否则返回0
%w：输入的边的矩阵
%N：原图的点数
%原理：不断除去出现次数小于2的端点所在的边，最后观察是否有边留下
len=length(w(:,1）);
index=1:len;
while 1
num=length(index); %边数
p=zeros（1,N); %用于存储各点的出现的次数（一条边对应两个端点）
for i=1:num %统计各点的出现次数
p(w(index(i),2）)=p(w(index(i),2）)+1;
p(w(index(i),3）)=p(w(index(i),3）)+1;
end
index_tmp=zeros（1,num); %记录除去出现次数小于2的端点所在的边的边的下标集合
discard=find(p<2）; %找到出现次数小于2的端点
count=0; %记录剩余的边数
for i=1:num
%判断各边是否有仅出现一次端点——没有，则记录其序号于index_tmp
if ~(~isempty(find(discard==w(index(i),2）,1）) || ~isempty(find(discard==w(index(i),3）,1）))
count=count+1;
index_tmp(count)=index(i); 
end
end
if num==count %当没有边被被除去时，循环停止
index=index_tmp（1:count); %更新index
break;
else
index=index_tmp（1:count); %更新index
end
end
if isempty(index) %若最后剩下的边数为0，则无圈
isfind=0;
else
isfind=1;
end
end
% 
% a =[
% 0 3 2 3 100 100 100
% 3 0 2 100 100 100 6
% 2 2 0 3 100 1 100
% 3 100 3 0 5 100 100
% 100 100 100 5 0 4 6
% 100 100 1 100 4 0 5
% 100 6 100 6 100 5 0];
% 
% Kruskal(a,100）pascal代码实现{
最小生成树的Kruskal算法。
Kruskal算法基本思想：
每次选不属于同一连通分量（保证不生成圈）且边权值最小的顶点，将边加入MST，并将所在的2个连通分量合并，直到只剩一个连通分量
排序使用Quicksort(O(eloge))
检查是否在同一连通分量用Union-Find，每次Find和union运算近似常数
Union-Find使用rank启发式合并和路径压缩
总复杂度O(eloge)=O(elogv) （因为e<n(n-1）/2）
}
const
maxn=100;
maxe=maxn*maxn;
type
edge=record
a,b :integer; //边的2个顶点
len :integer; //边的长度
end;
var
edges :array[0..maxe]of edge; //保存所有边的信息
p,r :array[0..maxn]of integer; //p保存i的父亲节点，r用来实现Union-Find的rank启发式
n,e :integer; //n为顶点数，e为边数
procedure swap(a,b:integer); //交换
begin
edges[0]:=edges[a];
edges[a]:=edges[b];
edges[b]:=edges[0];
end;
procedure quicksort(l,r:integer); //快速排序
var
x,i,j :integer;
begin
x:=edges[random(r-l+1）+l].len;
i:=l;j:=r;
repeat
while edges[i].len<x do inc(i);
while edges[j].len>x do dec(j);
if i<=j then
begin
swap(i,j);
inc(i);dec(j);
end
until i>j;
if l<j then quicksort(l,j);
if i<r then quicksort(i,r);
end;
procedure init;
var
i :integer;
begin
assign(input,'g.ini');reset(input);
readln(n,e);
for i:=1 to e do readln(edges[i].a,edges[i].b,edges[i].len); //从文件读入图的信息
for i:=1 to n do p[i]:=i; //初始化并查集
randomize;
quicksort（1,e); //使用快速排序将边按权值从小到大排列
end;
function find(x:integer):integer; //并查集的Find，用来判断2个顶点是否属于一个连通分量
begin
if x<>p[x] then p[x]:=find(p[x]);
find:=p[x]
end;
procedure union(a,b:integer); //如果不属于且权值最小则将2个顶点合并到一个连通分量
var
t :integer;
begin
a:=find(a);b:=find(b);
if r[a]>r[b] then begin t:=a;a:=b;b:=t end;
if r[a]=r[b]then inc(r[a]);
p[a]:=b;
end;
procedure kruskal; //主过程
var
en :integer; //en为当前边的编号
count :integer; //统计进行了几次合并。n-1次合并后就得到最小生成树
tot :integer; //统计最小生成树的边权总和
begin
count:=0;en:=0; tot:=0;
while count<n-1 do
begin
inc(en);
with edges[en] do
begin
if find(a)<>find(b) then
begin
union(a,b);
writeln(a,'--',b,':',len);
inc(tot,len);
inc(count);
end;
end;
end;
writeln('Total Length=',tot)
end;
{===========main==========}
begin
init;
kruskal;
end.
例题详见 vijos p1045 Kerry 的电缆网络
type
rec=record
x,y:longint;
cost:real;
end;
var
f:array[1..1000000] of rec;
s,ans:real;
i,n,m,k,dad:longint;
father:array[1..1000000] of longint;
procedure kp(l,r:longint);
var
i,j:longint;
xx:real;
y:rec;
begin
i:=l;
j:=r;
xx:=f[(i+j) div 2].cost;
repeat
while xx>f[i].cost do inc(i);
while xx<f[j].cost do dec(j);
if i<=j then
begin
y:=f[i];
f[i]:=f[j];
f[j]:=y;
inc(i);
dec(j);
end;
until i>j;
if i<r then kp(i,r);
if l<j then kp(l,j);
end;
function find(x:longint):longint;
begin
if father[x]=x then exit(x);
father[x]:=find(father[x]);
exit(father[x]);
end; 
procedure union(x,y:longint;j:real);
begin
x:=find(x);
y:=find(y);
if x<>y then
begin
father[y]:=x;
ans:=ans+j;
inc(k);
end;
end;
begin
readln(s);
readln(n);
m:=0;
while not eof do
begin
inc(m);
with f[m] do
readln(x,y,cost);
end; 
if m<n-1 then
begin
writeln('Impossible');
exit;
end;
for i:=1 to n do
father[i]:=i;
kp（1,m);
k:=0;
for i:=1 to m do
begin
if k=n-1 then break;
union(f[i].x,f[i].y,f[i].cost);
end;
if k<n-1 then
begin
writeln('Impossible');
exit;
end;
if ans>s then writeln('Impossible') else
writeln('Need ',ans:0:2,' miles of cable');
end. 
Kruskal算法适用于边稀疏的情形，而Prim算法适用于边稠密的情形
其它最小生成树算法
c++代码实现
#include <stdio.h>
#include <stdlib.h>
#include <vector>
using namespace std;
#define MAXNUM_VERTEX 20 //最多有 20个顶点
#define MAXNUM_EDGE 21
typedef struct
{
int v,w; 
int weight;
}Edge;
typedef struct
{
int vertex[MAXNUM_VERTEX]; 
Edge edge[MAXNUM_EDGE];
int num_vertex,num_edge;
}Graph;
Graph graph; //声明为全局变量
bool search_vertex(int ver)
{
for(int i=0; i<graph.num_vertex; i++)
if( ver == graph.vertex[i] )
return 1;
printf("the vertex %d you input is not existed! \n",ver);
return 0;
}
void create_graph()
{
//输入顶点信息
printf("input the number of vertex in the graph\n");
scanf(" %d",&graph.num_vertex);
printf("input the vertex of graph,like 1,2\n");
for(int i=0; i<graph.num_vertex; i++)
scanf(" %d",&graph.vertex[i]);
//输入边信息
printf("input the number of edge in the graph\n");
scanf(" %d",&graph.num_edge);
printf("intput the edge of graph and the weight of line,like 1-2 5\n");
for(int j=0; j<graph.num_edge; j++)
{
p1:int a,c,d;
char b;
scanf(" %d %c %d %d",&a,&b,&c,&d);
if( search_vertex(a) && search_vertex(c) )
{
graph.edge[j].v=a;
graph.edge[j].w=c;
graph.edge[j].weight=d;
}
else 
goto p1;
}
}
void sort_by_weight()
{
for(int i=1; i<graph.num_edge; i++)
{
Edge temp=graph.edge[i];
for(int j=i-1; j>=0 && graph.edge[j].weight>temp.weight; j--)
graph.edge[j+1]=graph.edge[j];
graph.edge[j+1]=temp;
}
}
/*不相交集处理函数*/
inline void makeset(vector<int> & array)
{
for(int i=0; i<array.size(); i++)
array[i]=i;
}
int findset(vector<int> & parent,int i)
{
if(i != parent[i])
i=parent[i];
return i;
}
inline void merge(vector<int> & parent,int i,int j)
{
parent[i]=j;
}
/*不相交集处理函数*/
void kruskal()
{
int num_ver=graph.num_vertex;
int count=0;
vector<int> parent_ver;
parent_ver.resize(num_ver);
/*核心部分是用不相交集合成树*/
makeset(parent_ver);
printf("the edge of min tree as follow: \n");
for( int i=0; i<num_ver && count<num_ver-1; i++ ) //count表示合并（merge）的次数num_ver-1次
{
int m=graph.edge[i].v;
int n=graph.edge[i].w;
int w=graph.edge[i].weight; 
if( findset(parent_ver,m) != findset(parent_ver,n)) //当属于不同的集合时，则将该边添加进树中
{
printf("(%d %d) %d\n",m,n,w);
merge(parent_ver,m,n);
count++;
}
}
}
void print_edge()
{
printf("the graph you input as follow: \n");
for(int i=0; i<graph.num_edge; i++)
printf("%d-%d the weight is: %d\n",graph.edge[i].v,graph.edge[i].w,graph.edge[i].weight);
}
void main()
{
create_graph();
sort_by_weight();
print_edge();
kruskal();
}
java代码实现
import java.util.ArrayList;
import java.util.Collections;
class Bian implements Comparable // 两点之间的加权边
{
private int first,second;// 表示一条边的两个节点
private int value;// 权值
public Bian(int first,int second,int value) {
this.first = first;
this.second = second;
this.value = value;
}
public int getFirst() {
return first;
}
public int getSecond() {
return second;
}
public int getValue() {
return value;
}
@Override
public int compareTo(Object arg0) {
return value > ((Bian) arg0).value 1
: (value == ((Bian) arg0).value 0 : -1）;
}
@Override
public String toString() {
return "Bian [first=" + first + ",second=" + second + ",value="
+ value + "]";
}
}
class ShuZu {
static ArrayList<ArrayList> list = new ArrayList<ArrayList>();// 存放每一个数组中的节点的数组
static ArrayList<ArrayList> bianList = new ArrayList<ArrayList>();// 对应存放数组中的边的数组
public static void check(Bian b)// 检查在哪个数组中
{
if (list.size() == 0) {
ArrayList<Integer> sub = new ArrayList<Integer>();
sub.add(b.getFirst());
sub.add(b.getSecond());
list.add(sub);
ArrayList<Bian> bian = new ArrayList<Bian>();
bian.add(b);
bianList.add(bian);
return;
}
int first = b.getFirst();
int shuyu1 = -1;
int second = b.getSecond();
int shuyu2 = -1;
for (int i = 0; i < list.size(); i++)// 检查两个节点分别属于哪个数组
{
for (int m = 0; m < list.get(i).size(); m++) {
if (first == (Integer) list.get(i).get(m))
shuyu1 = i;
if (second == (Integer) list.get(i).get(m))
shuyu2 = i;
}
}
if (shuyu1 == -1 && shuyu2 == -1）// 表示这两个节点都没有需要新加入
{
ArrayList<Integer> sub = new ArrayList<Integer>();
sub.add(b.getFirst());
sub.add(b.getSecond());
list.add(sub);
ArrayList<Bian> bian = new ArrayList<Bian>();
bian.add(b);
bianList.add(bian);
}
if (shuyu1 == -1 && shuyu2 != -1）// 表示有一个点已经在数组中只把另一个加入就可以了
{
list.get(shuyu2).add(first);
bianList.get(shuyu2）.add(b);
}
if (shuyu2 == -1 && shuyu1 != -1）// 表示有一个点已经在数组中只把另一个加入就可以了
{
list.get(shuyu1).add(second);
bianList.get(shuyu1).add(b);
}
if (shuyu1 == shuyu2 && shuyu1 != -1）// 表述两个在同一个组中 会形成环
{
}
if (shuyu1 != shuyu2 && shuyu1 != -1 && shuyu2 != -1）// 表示两个点在不同的组中 需要合并
{
for (int i = 0; i < list.get(shuyu2）.size(); i++) {
list.get(shuyu1）.add(list.get(shuyu2）.get(i));
}
list.remove(shuyu2）;
for (int i = 0; i < bianList.get(shuyu2）.size(); i++) {
bianList.get(shuyu1）.add(bianList.get(shuyu2）.get(i));
}
bianList.get(shuyu1）.add(b);
bianList.remove(shuyu2）;
}
}
public static void show() {
for (int i = 0; i < bianList.get(0).size(); i++)
System.out.println(bianList.get(0).get(i));
}
}
public class Test {
public static void main(String[] args) {
ArrayList<Bian> l = new ArrayList<Bian>();
l.add(new Bian（1,3,1）);
l.add(new Bian（1,2,6）);
l.add(new Bian（1,4,5）);
l.add(new Bian（2,3,5）);
l.add(new Bian（2,5,3）);
l.add(new Bian（3,4,5）);
l.add(new Bian（3,5,6）);
l.add(new Bian（3,6,4）);
l.add(new Bian（4,6,2）);
l.add(new Bian（5,6,6）);
Collections.sort(l);
// for(int i = 0 ;i<l.size();i++)
// System.out.println(l.get(i));
for (int i = 0; i < l.size(); i++)
ShuZu.check(l.get(i));
ShuZu.show();
}
}