最小生成树(Kruskal算法)
Kruskal算法是一种用来查找最小生成树的算法,由Joseph Kruskal在1956年发表。这是我关于这个算法的小总结。以下图为例,进行求解。
Kruskal算法是通过先求出最小的连通分支,再判断连通分支的端顶点<u,v>的连通分支是否一致(设用find(int t)求出顶点t的连通分支),若相同,则继续运行,若不同,则将更新两点的连通分支,将顶点u的连通分支修改为顶点v的连通分支(设用union(int u,int v)将顶点u,v进行连接),且将所求的连通分支集合进行更新。
1.可用一个EdgeCode类用来存储连通分支的两个端顶点<u,v>、权重weight和一个与其他连通分支比较权重并返回情况的方法(可用Comparable的compareTo()方法)。
public static class EdgeCode implements Comparable{
int u,v;
int weight;
public EdgeCode(int wweight){
weight=wweight;
}
public EdgeCode(int uu,int vv,int wweight){
u=uu;
v=vv;
weight=wweight;
}
public int compareTo(Object x){
int xWeight=((EdgeCode)x.)weight;
if(weight<xWeight){
return -1;
}
else if(weight==xWeight){
return 0;
}
else{
return 1;
}
}
}
2.find(int t)和union(int u,int v)方法可用并查集(Union-Find)来实现,建一个FastUnionFind类,其中除了属性u,v外,还需要有属性n记录顶点个数和数组code[]记录每个顶点所属的连通分支(即记录该顶点指向的最终顶点)。
public static class FastUnionFind{
int u,v;
int n;
int[] code;
public FastUnionFind(int nn){
n=nn;
code=new int[n+1];
for(int i=1;i<=n;i++){
code[i]=i;//初始化,将顶点指向自身,即最终顶点为源顶点
}
}
public int find(int t){//查找
while(t!=code[t]){//因为最终顶点的源顶点跟最终顶点都是自身,所以循环求解,直到源顶点跟最终顶点相等
t=code[t];
}
public int union(int u,int v){//连通
int uPID=find(u);//用uPID记录u的最终顶点
int vPID=find(v);//用vPID记录v的最终顶点
if(uPID==vPID){
return;
}
for(int i=1;i<=n;i++){
if(code[i]==uPID){//更新最终顶点,将最终顶点为uPID的顶点的最终顶点都修改为vPID
code[i]=vPID;
}
}
}
}
3.因为是要求最小生成树,所以需要求权重最小的连通分支,我们可选择用最小堆(MinHeap)来求。
用initialize(Comparable[] a,int length)来建立初始堆;
用adjustHeap(Comparable[] a,int i,int length)来建立最小堆;
用removeMin()将根节点与数组尾顶点进行调换,相当于将根顶点输出;
用returnMinHeap()将Comparable[] a进行降序排序。
public static class MinHeap{
int length;
Comparable[] a;
public void initialize(Comparable[] aa,int llength){
a=aa;
length=llength;
for(int i=a.length/2;i>0;i--){
adjustHeap(a,i,length);
}
}
public void adjustHeap(Comparable[] a,int i,int length){
Comparable temp=a[i];
for(int k=2*i;k<length;k=2*k){
if(k+1<length && a[k].comparaTo(a[k+1])>0){
k++;
}
if(a[k].compareTo(temp)<0){
swap(a,i,k);
i=k;
}
else{
break;
}
}
}
public void swap(Comparable[] a,int i,int j){
Comparable temp=a[i];
a[i]=a[j];
a[j]=temp;
}
public void removeMin(){
if(length>0){
length=length-1;
swap(a,0,length);
adjustHeap(a,0,length);
}
}
public void returnMinHeap(){
for(int i=1;i<a.length;i++){
removeMin();
}
}
}
4.到最后将所求的连通分支集合(设为EdgeCode[] t)的长度与顶点个数n相比较,若t.length==n-1时,则所求的连通图有最小生成树,若无,则无解,可用布尔变量返回是否有解。设kruskal(EdgeCode[] a,EdgeCode[] t,int e,int n)方法中的e为连通分支个数,n为顶点个数。
public static boolean kruskal(EdgeCode[] a,EdgeCode[] t,int e,int n){
int k=0;
MinHeap H=new MinHeap();
H.initialize(a,a.length);//建立初始最小堆
FastUnionFind U=new FastUnionFind(n);
while(e>0 && k<n-1){
EdgeCode temp=a[1];//因为此时还未调用removeMin()方法,根节点还是此时的权重最小的连通分支
H.removeMin();
int uPID=U.find(temp.u);
int vPID=U.find(temp.v);
if(uPID!=vPID){
t[k++]=temp;//若两顶点的所属的连通分支(即最终顶点)不同,则加入所求连通分支集合
U.union(uPID,vPID);
}
e--;
}
return k==n-1;
}
5.进行最终测试,所求的图如图所示:
源程序代码:
import java.util.Arrays;
public class Kruskal {
public static class EdgeCode implements Comparable{
int weight;
int u,v;
public EdgeCode(int wweight) {
weight=wweight;
}
public EdgeCode(int uu,int vv,int wweight) {
u=uu;
v=vv;
weight=wweight;
}
public int compareTo(Object x) {
int xWeight=((EdgeCode)x).weight;
if(weight<xWeight) {
return -1;
}
else if(weight==xWeight) {
return 0;
}
else{
return 1;
}
}
}
public static class FastUnionFind{
int u,v;
int n;
int[] code;
public FastUnionFind(int nn) {
n=nn;
code=new int[n+1];
for(int i=1;i<=n;i++) {
code[i]=i;
}
}
public int find(int t) {
while(t!=code[t]) {
t=code[t];
}
return t;
}
public void union(int u,int v) {
int uPID=find(u);
int vPID=find(v);
if(uPID==vPID) {
return;
}
for(int i=1;i<=n;i++) {
if(code[i]==uPID) {
code[i]=vPID;
}
}
}
}
public static class MinHeap{
Comparable[] a;
int length;
public void initialize(Comparable[] aa,int llength) {
a=aa;
length=llength;
for(int i=length/2;i>0;i--) {
adjustHeap(a,i,length);
}
}
public void removeMin() {
if(length>0) {
length=length-1;
swap(a,1,length);
adjustHeap(a,1,length);
}
}
public void returnMinHeap() {
Comparable[] b=new Comparable[a.length];
for(int i=1;i<=a.length-1;i++) {
removeMin();
}
}
public static void adjustHeap(Comparable[] a,int i,int length) {
Comparable temp=a[i];
for(int k=2*i;k<length;k=2*k) {
if(k+1<length && a[k].compareTo(a[k+1])>0) {//判断是否有右孩子
k++;
}
if(a[k].compareTo(temp)<0) {//判断左孩子的值是否大于节点的值
swap(a,i,k);
i=k;
}
else {
break;
}
}
}
public static void swap(Comparable[] arry,int a,int b) {//交换数值
Comparable temp=arry[a];
arry[a]=arry[b];
arry[b]=temp;
}
}
public static boolean kruskal(EdgeCode[] a,EdgeCode[] t,int e,int n) {//a为边的连通分支的数组,t为所求的权重最小的连通分支数组,e为边的条数,n为点的个数
MinHeap H=new MinHeap();
H.initialize(a,a.length);
FastUnionFind U=new FastUnionFind(n);
int k=0;
while(e>0 && k<n-1) {
EdgeCode temp=a[1];
H.removeMin();
int uPID=U.find(temp.u);
int vPID=U.find(temp.v);
//System.out.println(temp.u+" "+temp.v+" "+temp.weight+" "+uPID+" "+vPID);
if(uPID!=vPID) {
t[k++]=temp;
System.out.println(temp.u+" "+temp.v+" "+temp.weight+" "+uPID+" "+vPID);
U.union(uPID, vPID);
System.out.println(temp.u+" "+temp.v+" "+temp.weight+" "+U.find(uPID)+" "+U.find(vPID));
}
e--;
}
return k==n-1;
}
public static void display(EdgeCode[] t) {
for(int i=0;i<t.length;i++) {
System.out.println(t[i].u+" "+t[i].v+" "+t[i].weight);
}
}
public static void main(String[] args) {
int n=6;
EdgeCode[] a=new EdgeCode[2*(n-1)+1];
for(int i=1;i<=a.length-1;i++) {
a[i]=new EdgeCode(i);
}
a[1]=new EdgeCode(1,2,1);
a[2]=new EdgeCode(2,3,2);
a[3]=new EdgeCode(3,4,3);
a[4]=new EdgeCode(4,5,4);
a[5]=new EdgeCode(5,1,5);
a[6]=new EdgeCode(1,6,5);
a[7]=new EdgeCode(2,6,1);
a[8]=new EdgeCode(3,6,2);
a[9]=new EdgeCode(4,6,3);
a[10]=new EdgeCode(5,6,4);
EdgeCode[] t=new EdgeCode[n-1];
int e=a.length-1;
boolean flag = kruskal(a,t,e,n);
System.out.println(flag);
display(t);
}
}
运行结果图: