Kruskal算法说明及图解
- 格式:doc
- 大小:380.00 KB
- 文档页数:5
Kruskal算法的思想、证明及步骤Kruskal算法思想:把n个顶点看成看成n棵分离的树(每棵树只有⼀个顶点),每次选取可连接两个分离树中权值最⼩的边把两个分离的树合成⼀个新的树取代原来的两个分离树,如果重复n-1步后便得到最⼩⽣成树。
Kruskal算法步骤:T0存放⽣成树的边,初值为空C(T0) 最⼩⽣成树的权,初值为0VS 分离树顶点的集合,初值为 { {v1} {v2} … {v n} }A B W分别为边的顶点和权值数组,由⽤户输⼊1) T0←0, C(T0)←0, VS←{ {v1} {v2} … {v n} }, A, B, W按W排序后构成队列Q2) If n(VS)==1 then stop else goto 33) 取Q第⼀组数组(u, v, w) 并从Q中将其删除.4) If u, v 属于同⼀个集合 then goto 3 else 分属两个集合X, Y, goto 5.5) T0←T0∪(u, v), C(T0)←C(T0)+w, VS←VS-X-Y+X∪Y goto 2.Kruskal算法证明:树定义:⽆圈连通图。
引理1:⼀个图是树等价于⼀个图⽆圈且任意不相关联的顶点u, v ,图G+(u, v)则必存在唯⼀⼀个圈。
设由Kruskal算法⽣成的T0序列为e1, e2, e3 … e v-1,假设其不是最⼩⽣成树。
任意最⼩⽣成树T定义函数f(T):T0中不存在于T中边在T0的下标。
设T1是使f(T)最⼤的变量,设f(T1)=k,即e k不存在于T1中,T1为树且e k不在T1中,所以由引理1得T1+e k必存在圈C,C上必有e,k ≠e k,e,k不在T0中。
现令T2 = T1 - e,k + e k,则T2也为⽣成树但由Kruskal算法可知e k是使e1, e2 … e k-1, e k⽆圈的权值最⼩的边,e1, e2 … e k-1, e ,k是树的⼦图必⽆圈故e,k的权值必定⼩于e k,即T2的总权值不⼤于T1的权值,因T1是最⼩⽣成树,故必T2也是最⼩⽣成树,但f(T2)>k与k是f函数最⼤取值⽭盾,故T0是最⼩⽣成树。
最小生成树的Kruskal算法Kruskal算法基本思想:每次选不属于同一连通分量(保证不生成圈)且边权值最小的顶点,将边加入MST,并将所在的2个连通分量合并,直到只剩一个连通分量排序使用Quicksort(O(eloge))检查是否在同一连通分量用Union-Find,每次Find和union运算近似常数Union-Find使用rank启发式合并和路径压缩总复杂度O(eloge)=O(elogv) (因为e<n(n-1)/2)}constmaxn=100;maxe=maxn*maxn;typeedge=recorda,b :integer; //边的2个顶点len :integer; //边的长度end;varedges :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); //交换beginedges[0]:=edges[a];edges[a]:=edges[b];edges[b]:=edges[0];end;procedure quicksort(l,r:integer); //快速排序varx,i,j :integer;beginx:=edges[random(r-l+1)+l].len;i:=l;j:=r;repeatwhile edges[i].len<x do inc(i);while edges[j].len>x do dec(j);if i<=j thenbeginswap(i,j);inc(i);dec(j);enduntil i>j;if l<j then quicksort(l,j);if i<r then quicksort(i,r);end;procedure init;vari :integer;beginassign(input,'g.in');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个顶点是否属于一个连通分量beginif x<>p[x] then p[x]:=find(p[x]);find:=p[x]end;procedure union(a,b:integer); //如果不属于且权值最小则将2个顶点合并到一个连通分量vart :integer;begina:=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; //主过程varen :integer; //en为当前边的编号count :integer; //统计进行了几次合并。
贪心策略是指从问题的初始状态出发,通过若干次的贪心选择而得出最优值(或较优解)的一种解题方法。
Ⅰ、库鲁斯卡尔(Kruskal)算法【定义4】设图G=(V,E)是一简单连通图,|V| =n,|E|=m,每条边ei都给以权W ,W 假定是边e 的长度(其他的也可以),i=1,2,3,...,m。
求图G的总长度最短的树,这就是最短树问题。
kruskal算法的基本思想是:首先将赋权图G的边按权的升序排列,不失一般性为:e ,e ,......,e 。
其中W ≤W ,然后在不构成回路的条件下择优取进权最小的边。
其流程如下:(1)对属于E的边进行排序得e ≤e ≤...... ≤e 。
(2)初始化操作 w←0,T←ф,k←0,t←0;(3)若t=n-1,则转(6),否则转(4)(4)若T∪{e }构成一回路,则作【k←k+1,转(4)】(5) T←T∪{ e },w←w+ w ,t←t+1,k←k+1,转(3)(6)输出T,w,停止。
下面我们对这个算法的合理性进行证明。
设在最短树中,有边〈v ,v 〉,连接两顶点v ,v ,边〈v ,v 〉的权为wp,若〈v ,v 〉加入到树中不能保证树的总长度最短,那么一定有另一条边〈v ,v 〉或另两条边〈v ,v 〉、〈v ,v 〉,且w<vi,vj><wp或w<vi,vk>+w〈vk,vj〉<wp,因为〈v ,v 〉、〈v ,v 〉不在最短树中,可知当〈v ,v 〉、〈v ,v 〉加入到树中时已构成回路,此时程序终止。
因为〈v ,v 〉∈ T,〈v ,v 〉∈T且w〈vI,vk〉+w〈vk,vj〉<w p,与程序流程矛盾。
普林(Prim)算法:Kruskal算法采取在不构成回路的条件下,优先选择长度最短的边作为最短树的边,而Prim则是采取了另一种贪心策略。
已知图G=(V,E),V={v ,v ,v ,..., v },D=(d )是图G的矩阵,若〈v ,v 〉∈E,则令dij=∞,并假定dij=∞Prim算法的基本思想是:从某一顶点(设为v )开始,令S←{v },求V/S中点与S中点v 距离最短的点,即从矩阵D的第一行元素中找到最小的元素,设为d ,则令S ←S∪ { v },继续求V/S中点与S的距离最短的点,设为v ,则令S←S∪{ v },继续以上的步骤,直到n个顶点用n-1条边连接起来为止。
kruskal算法百科名片K r u s k a l算法每次选择n- 1条边,所使用的贪婪准则是:从剩下的边中选择一条不会产生环路的具有最小耗费的边加入已选择的边的集合中。
注意到所选取的边若产生环路则不可能形成一棵生成树。
K r u s k a l算法分e 步,其中e 是网络中边的数目。
按耗费递增的顺序来考虑这e 条边,每次考虑一条边。
当考虑某条边时,若将其加入到已选边的集合中会出现环路,则将其抛弃,否则,将它选入。
目录[隐藏]Kruskal算法普里姆算法(prim算法)Kruskal算法普里姆算法(prim算法)[编辑本段]Kruskal算法算法定义克鲁斯卡尔算法假设WN=(V,{E}) 是一个含有n 个顶点的连通网,则按照克鲁斯卡尔算法构造最小生成树的过程为:先构造一个只含n 个顶点,而边集为空的子图,若将该子图中各个顶点看成是各棵树上的根结点,则它是一个含有n 棵树的一个森林。
之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,也就是说,将这两个顶点分别所在的两棵树合成一棵树;反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。
依次类推,直至森林中只有一棵树,也即子图中含有n-1条边为止。
举例描述初始时没有任何边被选择。
边( 1 , 6)是最先选入的边,它被加入到欲构建的生成树中,得到图1 3 - 1 2 c。
下一步选择边(3,4)并将其加入树中(如图1 3 - 1 2 d所示)。
然后考虑边( 2,7 ,将它加入树中并不会产生环路,于是便得到图1 3 - 1 2 e。
下一步考虑边(2,3)并将其加入树中(如图1 3 - 1 2 f所示)。
在其余还未考虑的边中,(7,4)具有最小耗费,因此先考虑它,将它加入正在创建的树中会产生环路,所以将其丢弃。
此后将边(5,4)加入树中,得到的树如图13-12g 所示。
下一步考虑边(7,5),由于会产生环路,将其丢弃。
1. Kruskal算法(1) 算法思想K r u s k a l算法每次选择n- 1条边,所使用的贪婪准则是:从剩下的边中选择一条不会产生环路的具有最小耗费的边加入已选择的边的集合中。
注意到所选取的边若产生环路则不可能形成一棵生成树。
K r u s k a l算法分e 步,其中e 是网络中边的数目。
按耗费递增的顺序来考虑这e 条边,每次考虑一条边。
当考虑某条边时,若将其加入到已选边的集合中会出现环路,则将其抛弃,否则,将它选入。
初始时没有任何边被选择。
边( 1 , 6)是最先选入的边,它被加入到欲构建的生成树中,得到图1 3 - 1 2 c。
下一步选择边( 3,4)并将其加入树中(如图1 3 - 1 2 d所示)。
然后考虑边( 2,7 ,将它加入树中并不会产生环路,于是便得到图1 3 - 1 2 e。
下一步考虑边( 2,3)并将其加入树中(如图1 3 - 1 2 f所示)。
在其余还未考虑的边中,(7,4)具有最小耗费,因此先考虑它,将它加入正在创建的树中会产生环路,所以将其丢弃。
此后将边( 5,4)加入树中,得到的树如图13-12g 所示。
下一步考虑边( 7,5),由于会产生环路,将其丢弃。
最后考虑边( 6,5)并将其加入树中,产生了一棵生成树,其耗费为9 9。
图1 - 1 3给出了K r u s k a l算法的伪代码。
(2)C代码/* Kruskal.cCopyright (c) 2002, 2006 by ctu_85All Rights Reserved.*//* I am sorry to say that the situation of unconnected graph is not concerned */#include "stdio.h"#define maxver 10#define maxright 100int 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;}elseif(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[0]=v1;path[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[0]+1,path[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;}elseif(pre!=i)FindCircle(start,i,times,begin);elsecontinue;}return 1;}。
Kruskal 算法构造最小生成树构造赋权图(,,)G V E W =,其中12{,,,}n V v v v = 为顶点集合,其中i v 表示第i 个顶点,12{,,,,}i E e e e = 为边的集合,()ij n n W w ⨯=为邻接矩阵,其中i j i j ij i j v v v v w v v ∈⎧⎪=⎨∞⎪⎩顶点与之间的距离,当(,)E ,与之间没有边时科茹斯克尔(Kruskal )算法如下: (1)选1e E ∈(E 为边的集合),使得1e 是权重最小的边。
(2)若12,,e i e e …,已选好,则从12{,,e }i E e e -…,中选取1i e +,使得 i )121{,,e }i e e +…,中无圈,且 ii )是1i e +是12{,,e }i E e e -…,中权重最小的边。
(3)直到选得1V e - 为止。
(V 是集合V 中元素的个数)例:已知9个村庄之间的两两距离见表5,要架设通讯线路把9个村庄连接起来,求所需要通讯线的最小长度。
表5 10个村庄的两两距离数据(单位:km )(,,)G V E W =,其中129{,,,}V v v v = 为顶点集合,其中i v 表示第i 个村庄,12{,,,,}i E e e e = 为边的集合,99()ij W w ⨯=为邻接矩阵,其中i j i j ij i j v v v v w v v ∈⎧⎪=⎨∞⎪⎩村庄与之间的距离,当(,)E ,与之间没有通讯路线时科茹斯克尔(Kruskal )算法如下: (1)选1e E ∈(E 为边的集合),使得1e 是距离最小的边。
(2)若12,,e i e e …,已选好,则从12{,,e }i E e e -…,中选取1i e +,使得 i )121{,,e }i e e +…,中无圈,且 ii )是1i e +是12{,,e }i E e e -…,中距离最小的边。
(3)直到选得8e 为止。
贪心策略是指从问题的初始状态出发,通过若干次的贪心选择而得出最优值(或较优解)的一种解题方法。
Ⅰ、库鲁斯卡尔(Kruskal)算法【定义4】设图G=(V,E)是一简单连通图,|V| =n,|E|=m,每条边ei都给以权W ,W 假定是边e 的长度(其他的也可以),i=1,2,3,...,m。
求图G的总长度最短的树,这就是最短树问题。
kruskal算法的基本思想是:首先将赋权图G的边按权的升序排列,不失一般性为:e ,e ,......,e 。
其中W ≤W ,然后在不构成回路的条件下择优取进权最小的边。
其流程如下:(1)对属于E的边进行排序得e ≤e ≤...... ≤e 。
(2)初始化操作 w←0,T←ф,k←0,t←0;(3)若t=n-1,则转(6),否则转(4)(4)若T∪{e }构成一回路,则作【k←k+1,转(4)】(5) T←T∪{ e },w←w+ w ,t←t+1,k←k+1,转(3)(6)输出T,w,停止。
下面我们对这个算法的合理性进行证明。
设在最短树中,有边〈v ,v 〉,连接两顶点v ,v ,边〈v ,v 〉的权为wp,若〈v ,v 〉加入到树中不能保证树的总长度最短,那么一定有另一条边〈v ,v 〉或另两条边〈v ,v 〉、〈v ,v 〉,且w<vi,vj><wp或w<vi,vk>+w〈vk,vj〉<wp,因为〈v ,v 〉、〈v ,v 〉不在最短树中,可知当〈v ,v 〉、〈v ,v 〉加入到树中时已构成回路,此时程序终止。
因为〈v ,v 〉∈ T,〈v ,v 〉∈T且w〈vI,vk〉+w〈vk,vj〉<w p,与程序流程矛盾。
普林(Prim)算法:Kruskal算法采取在不构成回路的条件下,优先选择长度最短的边作为最短树的边,而Prim则是采取了另一种贪心策略。
已知图G=(V,E),V={v ,v ,v ,..., v },D=(d )是图G的矩阵,若〈v ,v 〉∈E,则令dij=∞,并假定dij=∞Prim算法的基本思想是:从某一顶点(设为v )开始,令S←{v },求V/S中点与S中点v 距离最短的点,即从矩阵D的第一行元素中找到最小的元素,设为d ,则令S ←S∪ { v },继续求V/S中点与S的距离最短的点,设为v ,则令S←S∪{ v },继续以上的步骤,直到n个顶点用n-1条边连接起来为止。
1. 概览Kruskal算法是一种用来寻找最小生成树的算法,由Joseph Kruskal在1956年发表。
用来解决同样问题的还有Prime 算法和Boruvka 算法等。
三种算法都是贪婪算法的应用。
和Boruvka 算法不同的地方是,Kruskal 算法在图中存在相同权值的边时也有效。
2. 算法简单描述1.记Graph 中有v个顶点,e个边2.新建图Graph new,Graph new中拥有原图中相同的e个顶点,但没有边3.将原图Graph new中所有e个边按权值从小到大排序4.循环:从权值最小的边开始遍历每条边直至图Graph new中所有的节点都在同一个连通分量中如果这条边连接的两个节点于图Graph new中不在同一个连通分量中添加这条边到图Graph new中图例描述:3. 简单证明Kruskal算法对图的顶点数n 做归纳,证明Kruskal 算法对任意n 阶图适用。
归纳基础:n = 1,显然能够找到最小生成树。
归纳过程:假设Kruskal 算法对n ≤k 阶图适用,那么,在k + 1 阶图G 中,我们把最短边的两个端点a 和b 做一个合并操作,即把u 与v 合为一个点v',把原来接在u 和v 的边都接到v' 上去,这样就能够得到一个k阶图G'(u ,v 的合并是k + 1 少一条边),G' 最小生成树T' 可以用Kruskal 算法得到。
我们证明T' + {<u,v>} 是G 的最小生成树。
用反证法,如果T' + {<u,v>} 不是最小生成树,最小生成树是T,即W(T) < W(T' + {<u,v>})。
显然T 应该包含<u,v>,否则,可以用<u,v> 加入到T 中,形成一个环,删除环上原有的任意一条边,形成一棵更小权值的生成树。
而T - {<u,v>},是G' 的生成树。
1.无向网图及边集数组存储示意图vertex[6]=2.Kruskal 方法构造最小生成树的过程(a)一个图 (b)最小生成树过程1V0 V1 V2 V3 V4 V5下标 0 1 2 3 4 5 6 7 8 from 1 2 0 2 3 4 0 3 0 to 4 3 5 5 5 5 1 4 2 weigh t121719252526343846V1V0V4 V5V2 V3V1V0 V5 V2 V3 V4(c)最小生成树过程2 (d)最小生成树过程3(e)最小生成树过程3.伪代码1)初始化辅助数组parent[vertexNum];num=0; 2) 依次考查每一条边for(i=0; i<arcNum; i++ ) vex1=edge[i].form 所在生成树的根结点 vex2=edge[i].to 所在生成树的根结点 If(vex1!=vex2)parent[vex2]=vex1; num++;if(num==vertexNum-1) 算法结束 4.构造过程中参数变化顶点集 数组 parent V0 V1 V2 V3 V4 V5 被考查边 输出说明初始化parent -1 -1 -1 -1 -1 -16棵生成树,均只有根结点parent -1 -1 -1 -1 1 -1 (v1,v4)12 (v1,v4)12 vex1=1,vex2=4;parent[4]=1; parent -1 -1 -1 2 1 -1 (v2,v3)17 (v2,v3)17 vex1=2,vex2=3;parent[3]=2; parent -1 -1 -1 2 1 0 (v0,v5)19 (v0,v5)19 vex1=0,vex2=5;parent[5]=0; parent2-1 -1 21(v2,v5)25(v2,v5)25 vex1=2,vex2=0;parent[0]=2;5.主要代码/**********构造函数************/template<class DataType>EdgeGraph<DataType>::EdgeGraph(DataType a[], int n, int e){vertexNum=n; edgeNum=e;int i,j,weight;for (int k=0; k<vertexNum; k++)vertex[k]=a[k];for (k=0; k<edgeNum; k++){cout<<"输入边依附的两个顶点的编号:";cin>>i>>j;edge[k].from=i;edge[k].to=j;cout<<"请输入权重:";cin>>weight;edge[k].weight=weight;}}/**********Kruskal算法构造最小生成树************/template <class DataType>void EdgeGraph<DataType>::Kruskal(){ int num;int parent[MaxVertex], vex1, vex2;for (int i=0; i<vertexNum; i++)parent[i]=-1;for (i=0,num=0; i<edgeNum; i++){vex1=FindRoot(parent, edge[i].from);vex2=FindRoot(parent, edge[i].to);if (vex1!=vex2){cout << "(" << edge[i].from <<"->"<<edge[i].to << ")" <<"weight: "<< edge[i].weight << endl;parent[vex2]=vex1;num++;if (num==vertexNum-1) return;}}}/**********寻找根节点************/template <class DataType>int EdgeGraph<DataType>::FindRoot(int parent[], int v) {int t=v;while(parent[t] > -1)t=parent[t];return t;}/**********遍历输出************/template <class DataType>void EdgeGraph<DataType>::Print(){for (int i=0; i<edgeNum; i++){cout<<"("<<edge[i].from<<""<<edge[i].to<<")"<<"weight:"<<edge[i].weight<<endl;;}}6.结果截图发现不按大小输入weight的值则不能正确生成最小生成树如排好序输入时则得到正确的最小生成树结果正确,所以需要按大小顺序输入权值大小的Kruskal算法实际上对较复杂无向网图来说不适用。
kruskal wallis 检验公式Kruskal-Wallis检验公式是一种非参数统计方法,用于比较三个或多个独立样本的中位数是否存在差异。
它是对方差分析的一种推广,适用于数据不满足正态分布的情况。
本文将详细介绍Kruskal-Wallis检验公式的原理和应用。
Kruskal-Wallis检验公式的原理基于秩次转换,即将每个样本的观测值按照大小顺序排列,并用相应的秩次替代原始值。
这样,我们可以将原始数据转化为秩次数据,从而避免了对数据分布的假设。
接下来,我们将根据秩次数据计算出一个统计量H,该统计量反映了不同样本之间的差异程度。
Kruskal-Wallis检验公式的计算过程如下:1. 将每个样本的观测值按照大小顺序排列,并为每个值分配一个秩次。
如果有多个相同的值,可以为它们分配相同的秩次,计算方法为将相同值的秩次相加后除以相同值的个数。
2. 计算每个样本的秩次和,记为Ri。
3. 计算每个样本的秩次平方和,记为Ri^2。
4. 计算样本的秩次平方和之和,记为T。
5. 计算统计量H的值,公式为H = 12 * T / (N * (N + 1)) - 3 * (N + 1),其中N为总样本量。
6. 根据样本量和显著性水平选择相应的临界值,比较统计量H的值与临界值的大小关系。
7. 如果统计量H的值大于临界值,则拒绝原假设,即认为样本之间存在差异;反之,接受原假设,即认为样本之间不存在差异。
Kruskal-Wallis检验公式的应用场景广泛。
例如,在医学研究中,可以使用Kruskal-Wallis检验来比较不同治疗组的疗效差异;在市场调研中,可以使用Kruskal-Wallis检验来比较不同品牌产品的受欢迎程度;在教育研究中,可以使用Kruskal-Wallis检验来比较不同教学方法的效果差异。
需要注意的是,Kruskal-Wallis检验公式对样本间的方差齐性假设比较敏感。
如果样本方差不齐,可能会导致检验结果的偏误。
贪⼼算法之Kruskal克鲁斯卡尔Kruskal算法同Prim算法⼀样,都是求最⼩⽣成树。
Kruskal是不断的找最短边,加⼊集合,且不构成回路。
所以,我们可以给每个点定义⼀个集合,⼀边的起点和终点查看是否属于同⼀集合,如果是说明是回路,不成⽴,找下⼀条边。
如果不属于同⼀集合,则成⽴,并把其中的⼀个集合的全部节点的集合改为另外⼀个集合,进⾏统⼀。
具体代码如下:#include <iostream>#include <algorithm>using namespace std;#define MAXNODE 1000int n,m;struct Edge{int u;int v;int w;} e[MAXNODE * MAXNODE];int nodeset[MAXNODE]; //每个顶点的集合int Kruskal(int n);bool Merge(int u, int i);bool comp(Edge a, Edge b){return a.w < b.w;}void Init(int n){for(int i=0; i < n; i++){nodeset[i] = i;}}int main(){cout<<"请输⼊节点数n和边数m:";cin>>n>>m;Init(n);cout << "请输⼊节点边的权值:";for(int i = 0; i < m; i++){cin>>e[i].u>>e[i].v>>e[i].w;}sort(e, e+m, comp);int ans = Kruskal(n);cout<<ans<<endl;}int Kruskal(int n) {int ans = 0;for(int i = 0; i < m; i++){if(Merge(e[i].u, e[i].v)){//可以合并ans += e[i].w;n--;if(n==1)return ans;}}return0;}bool Merge(int u, int i) {int a = nodeset[u];int b = nodeset[i];if(a == b)return false;//归并节点集合for(int j = 0; j < n; j++){if(nodeset[j] == b){nodeset[j] = a;}}return true;}同时,与Prim算法相⽐,因为Kruskal是按照边进⾏的,所以适合边少的情况,即稀疏图。
克鲁斯卡尔算法(Kruskal算法)(最⼩⽣成树算法)-贪⼼克鲁斯卡尔算法:Kruskal算法是⼀种⽤来查找的算法,由Joseph Kruskal在1956年发表。
⽤来解决同样问题的还有和Boruvka算法等。
三种算法都是的应⽤。
和Boruvka算法不同的地⽅是,Kruskal算法在图中存在相同权值的边时也有效。
基本思想:先构造⼀个只含 n 个顶点、⽽边集为空的⼦图,把⼦图中各个顶点看成各棵树上的根结点,之后,从⽹的边集 E 中选取⼀条权值最⼩的边,若该条边的两个顶点分属不同的树,则将其加⼊⼦图,即把两棵树合成⼀棵树,反之,若该条边的两个顶点已落在同⼀棵树上,则不可取,⽽应该取下⼀条权值最⼩的边再试之。
依次类推,直到森林中只有⼀棵树,也即⼦图中含有 n-1 条边为⽌。
发现⼀个好的视频:下图为初始图、只含有点的森林和点与点之间的联系循环找权值最⼩的边依次向下循环...输⼊:6 101 2 61 3 11 4 52 3 52 5 33 4 53 5 63 6 44 6 25 6 6输出:V1-V3=1V4-V6=2V2-V5=3V3-V6=4V5-V6=515代码:#include <iostream>#include <bits/stdc++.h>using namespace std;#define MAX 100int Find(int parent[],int i){while(parent[i]>0){i=parent[i];}return i;}void Kruskal(int u[],int v[],int w[],int n,int m){int parent[MAX];int sum=0;for(int i=1;i<=n;i++) //初始化{parent[i]=0;}int a,b;for(int i=1;i<=m;i++){a=Find(parent,u[i]);b=Find(parent,v[i]);if(a!=b) //a==b说明成环{parent[a]=b;cout<<"V"<<a<<"-"<<"V"<<b<<"="<<w[i]<<endl; sum+=w[i];}}cout<<sum;}int main(){int n,m;int u[MAX],v[MAX],w[MAX];cin>>n>>m;for(int i=1;i<=m;i++){cin>>u[i]>>v[i]>>w[i];}for(int i=1;i<=m;i++) //排序{int min=i;for(int j=i+1;j<=m;j++){if(w[min]>w[j]){min=j;}}swap(u[i],u[min]);swap(v[i],v[min]);swap(w[i],w[min]);}Kruskal(u,v,w,n,m);return0;}。
数据结构与算法——克鲁斯卡尔(Kruskal)算法⽬录应⽤场景-公交站问题某城市新增 7 个站点(A, B, C, D, E, F, G) ,现在需要修路把 7 个站点连通,各个站点的距离⽤边线表⽰(权) ,⽐如 A – B 距离 12公⾥问:如何修路保证各个站点都能连通,并且总的修建公路总⾥程最短?如上图所⽰:要求和前⾯的普利姆算法中的修路问题是⼀样的要求,只是换了⼀个背景。
克鲁斯卡尔算法介绍克鲁斯卡尔(Kruskal)算法,是⽤来求加权连通图的最⼩⽣成树的算法。
基本思想:按照权值从⼩到⼤的顺序选择n-1条边,并保证这n-1条边不构成回路具体做法:⾸先构造⼀个只含 n 个顶点的森林然后依权值从⼩到⼤从连通⽹中选择边加⼊到森林中,并使森林中不产⽣回路,直⾄森林变成⼀棵树为⽌克鲁斯卡尔算法图解在含有 n 个顶点的连通图中选择 n-1 条边,构成⼀棵极⼩连通⼦图,并使该连通⼦图中 n-1 条边上权值之和达到最⼩,则称其为连通⽹的最⼩⽣成树。
例如,对于如上图 G4 所⽰的连通⽹可以有多棵权值总和不相同的⽣成树。
有多种不同的连通⽅式,但是哪⼀种权值才是最优的呢?下⾯是克鲁斯卡尔算法的图解步骤:以上图 G4 为例,来对克鲁斯卡尔进⾏演⽰(假设,⽤数组 R 保存最⼩⽣成树结果)。
第 1 步:将边E,F [2]加⼊ R 中。
边E,F的权值最⼩,因此将它加⼊到最⼩⽣成树结果 R 中。
第 2 步:将边C,D [3]加⼊ R 中。
上⼀步操作之后,边C,D的权值最⼩,因此将它加⼊到最⼩⽣成树结果 R 中。
第 3 步:将边D,E [4]加⼊ R 中。
同理,权值最⼩第 4 步:将边B,F [7]加⼊ R 中。
上⼀步操作之后,边C,E [5]的权值最⼩,但C,E会和已有的边构成回路;因此,跳过边C,E。
同理,跳过边C,F [6]。
将边B,F加⼊到最⼩⽣成树结果R中。
第 5 步:将边E,G [8]加⼊ R 中。
同理第 6 步:将边A,B [12]加⼊ R 中。
克鲁斯卡尔算法最小生成树过程嘿,朋友!咱们今天来聊聊克鲁斯卡尔算法最小生成树过程,这玩意儿听起来有点复杂,是不是?但别怕,跟着我,保证让你弄个明白!咱先来说说啥是生成树。
你就想象你有一堆城市,城市之间有路相连。
要把这些城市都连起来,还得让线路最短,这连起来的线路就是生成树。
那最小生成树呢,就是在所有可能的连线方式里,线路总长最短的那种。
克鲁斯卡尔算法就是找到这个最小生成树的好办法。
它就像个聪明的小侦探,一步步找出最合适的连线。
它是怎么工作的呢?一开始,它把所有的边都按照长度从小到大排好队。
这就好比把一堆长短不一的小木棍按照长度排整齐。
然后呢,从最短的边开始,一条一条地看。
如果加上这条边不会形成环,那就把它留下来,就好像你找到了一根合适的木棍能稳稳地搭在你的“城市线路”里。
要是加上这条边就形成环了,那可不行,得扔掉,这就好比一根木棍放进去会让你的线路乱套,那可不能要。
你说这是不是有点像搭积木?得挑合适的,不合适的就扔一边。
比如说,有五个城市A、B、C、D、E ,它们之间的距离是这样的:A 到 B 是 3 ,A 到 C 是 5 ,B 到 C 是 4 ,B 到 D 是 2 ,C 到 E 是 6 。
按照克鲁斯卡尔算法,先把边按照长度排好,最短的是 B 到 D ,长度为 2 。
加上这条边,没问题,不会形成环。
然后是 A 到 B ,长度为 3 ,加上,也没问题。
再看 A 到 C ,长度为 5 ,加上,还是没问题。
就这样一步步地,最后就能找到那个能把所有城市连起来,而且线路最短的办法,也就是最小生成树啦!你想想,如果在现实生活中,要铺设管道啊,架电线啊,用这个算法是不是能省好多材料,省好多钱?所以说,克鲁斯卡尔算法虽然听起来有点神秘,但其实就是个聪明的小技巧,能帮我们解决好多实际问题呢!学会了它,咱们在处理这类问题的时候,就能像个高手一样,轻松搞定!。
Kruskal算法正确性证明Kruskal算法: 步骤1,选择边e1,使得权值w(e1)尽可能⼩; 步骤2,若已选定边e1,e2,...,ei,则从E\{e1,e2,...,ei}选取e(i+1),使得 (1)G[{e1,e2,...,e(i+1)}]为⽆圈图 (2)权值w(e(i+1))是满⾜(1)的尽可能⼩的权; 步骤3,当步骤2不能继续执⾏时停⽌。
证明:由Kruskal算法构成的任何⽣成树T*=G[{e1,e2,...,e(n-1)}]都是最下⽣成树,这⾥n为赋权图G的顶点数。
(个⼈理解:因为局部最优,取尽可能⼩的权,不代表全局最⼩,因此正确性还是要证明的吧)使⽤反证法,1、有kruskal算法构成的⽣成树T*和异于T*的⽣成树T,这两种⽣成树。
2、定义函数f(T)表⽰不在T中的最⼩权值i的边ei。
假设T*不是最⼩树,T真正的最⼩树,显然T会使f(T)尽可能⼤的,即T本⾝权重则会尽可能⼩,。
3、设f(T)=k,表⽰存在⼀个不在T中的最⼩权值边ek=k,也就是说e1,e2,...e(k-1)同时在T和T*中,ek=k不在T中 4、T+ek包含唯⼀圈C。
设ek ' 是C的⼀条边,他在T中⽽不在T*中。
(想象圈C中⾄少有ek 和ek ' ,其中ek是⼜Kruskal算法得出的最⼩权边) 5、令T ' =W(T)+w(ei)-w(ei ' ),kruskal算法选出的是最⼩权边ek,(⽽ek'是T⾃⼰根据f(T)选出来的边)有w(ek ' )>=w(ek) 且W(T ' )=W(T*)(T ' 也是⼀个最⼩⽣成树) 6、但是f(T ' )>k= f(T),即T没有做到使得f(T)尽可能⼤,不再是真正的最⼩树,所以T=T*,从⽽T*确实是⼀棵最⼩数。
1.无向网图及边集数组存储示意图
vertex[6]=
2.Kruskal 方法构造最小生成树的过程
(a)一个图 (b)最小生成树过程1
V0 V1 V2 V3 V4 V5
下标 0 1 2 3 4 5 6 7 8 from 1 2 0 2 3 4 0 3 0 to
4
3 5 5 5 5 1
4 2 weight 12
17
19
25
25
26
34
38
46
V1
V0
V4 V5
V2 V3
V1
V0 V5 V2 V3 V4
(c)最小生成树过程2 (d)最小生成树过程3
(e)最小生成树过程4 3.伪代码
1)初始化辅助数组parent[vertexNum];num=0; 2) 依次考查每一条边for(i=0; i<arcNum; i++ ) vex1=edge[i].form 所在生成树的根结点 vex2=edge[i].to 所在生成树的根结点 If(vex1!=vex2)
parent[vex2]=vex1; num++;
if(num==vertexNum-1) 算法结束 4.构造过程中参数变化
顶点集 数组 parent V0 V1 V2 V3 V4 V5
被考查边 输出
说明
初始化
parent -1 -1 -1 -1 -1 -1
6棵生成树,均只有根结点 parent -1 -1 -1 -1 1 -1 (v1,v4)12 (v1,v4)12 vex1=1,vex2=4;parent[4]=1; parent -1 -1 -1 2 1 -1 (v2,v3)17 (v2,v3)17 vex1=2,vex2=3;parent[3]=2; parent -1 -1 -1 2 1 0 (v0,v5)19 (v0,v5)19 vex1=0,vex2=5;parent[5]=0; parent 2 -1 -1 2 1 0 (v2,v5)25 (v2,v5)25 vex1=2,vex2=0;parent[0]=2; parent 2 -1 -1 2 1 0 (v3,v5)25 vex1=2,vex2=2;所在根结点相同 parent 2 -1 1 2 1 0 (v4,v6)26 (v4,v6)26 vex1=1,vex2=2;parent[2]=1; parent 2
-1
1
2
1
生成树根结点是v1
5.主要代码
/**********构造函数************/
template<class DataType>
EdgeGraph<DataType>::EdgeGraph(DataType a[], int n, int e)
{
vertexNum=n; edgeNum=e;
int i,j,weight;
for (int k=0; k<vertexNum; k++)
vertex[k]=a[k];
for (k=0; k<edgeNum; k++)
{
cout<<"输入边依附的两个顶点的编号:";
cin>>i>>j;
edge[k].from=i;
edge[k].to=j;
cout<<"请输入权重:";
cin>>weight;
edge[k].weight=weight;
}
}
/**********Kruskal算法构造最小生成树************/
template <class DataType>
void EdgeGraph<DataType>::Kruskal()
{ int num;
int parent[MaxVertex], vex1, vex2;
for (int i=0; i<vertexNum; i++)
parent[i]=-1;
for (i=0,num=0; i<edgeNum; i++)
{
vex1=FindRoot(parent, edge[i].from);
vex2=FindRoot(parent, edge[i].to);
if (vex1!=vex2)
{
cout << "(" << edge[i].from <<"->"<<edge[i].to << ")" <<"weight: "<< edge[i].weight << endl;
parent[vex2]=vex1;
num++;
if (num==vertexNum-1) return;
}
}
}
/**********寻找根节点************/
template <class DataType>
int EdgeGraph<DataType>::FindRoot(int parent[], int v)
{
int t=v;
while(parent[t] > -1)
t=parent[t];
return t;
}
/**********遍历输出************/
template <class DataType>
void EdgeGraph<DataType>::Print()
{
for (int i=0; i<edgeNum; i++)
{
cout<<"("<<edge[i].from<<"
"<<edge[i].to<<")"<<"weight:"<<edge[i].weight<<endl;;
}
}
6.结果截图
发现不按大小输入weight的值则不能正确生成最小生成树如排好序输入时则得到正确的最小生成树
结果正确,所以需要按大小顺序输入权值大小的Kruskal算法实际上对较复杂无向网图来说不适用。
故思考在程序中添加一个对权值排序的函数(修改对应的from,to)。