最小生成树(Prim、Kruskal算法)整理版
MST最小生成树的Kruskal和Prim算法
1.实验目的(结出本次实验所涉及并要求掌握的知识点)最小生成树kruskal算法和prim算法的实现2.实验内容(结出实验内容具体描述)最小生成树kruskal算法和prim算法的实现3.算法描述及实验步骤(用适当的形式表达算法设计思想与算法实现步骤)数据结构:(MST也是图,所以实现用了邻接表表示)void union_v(LinkGraph*g,int index,int j){edgenode*p=g->adjlist[index].firstedge;g->adjlist[index].state=j;while(p){if(g->adjlist[p->adjvex].state!=j)union_v(g,p->adjvex,j);p=p->next;}}//(加边)对边按权排升序,每次考虑最小边,判断他两端是否是不同集合,是就把这边unionedges* Kruskal_MST(LinkGraph*g,int c){edges*head=creatEdgeList(g);edges*p;edgenode*s;int i,j;//判断每一条边,是否需要unionprintf("排序后的边结点:\n");p=head->next;while(p){printf("%d %d %d",p->leftv,p->rightv,p->weigth);printf("\n");p=p->next;}p=head->next;for(i=0;i<g->n;i++){g->adjlist[i].state=i;// 初始化点集,每个点都位于不同的连通分量}while(p){if(g->adjlist[p->leftv].state!=g->adjlist[p->rightv].state){// 只要这条边可以加入MST,就创建图结点// 把这些边当成图结点,创建图(MST树)p->flag=1;i=p->leftv;j=p->rightv;s=(edgenode *)malloc(sizeof(edgenode));s->adjvex=j;s->weigth=p->weigth;s->next=g->adjlist[i].firstedge; // 头插法创建邻接表g->adjlist[i].firstedge=s;if (c==0) { // c=0表示无向图s=(edgenode *)malloc(sizeof(edgenode));s->adjvex=i;s->next=g->adjlist[j].firstedge;g->adjlist[j].firstedge=s;}// union操作int m=g->adjlist[i].state;int n=g->adjlist[j].state;if(m<n){g->adjlist[j].state=m;union_v(g,j,m);}else{g->adjlist[i].state=n;}}p=p->next;}// 返回边结点链表return head;}Prim算法的实现:edges* Prim_MST(LinkGraph*g,int c){edges*head=creatEdgeList(g);edges*p;edgenode*s;int i,j;printf("排序后的边结点:\n");p=head->next;while(p){printf("%d %d %d",p->leftv,p->rightv,p->weigth);printf("\n");p=p->next;}for(i=0;i<g->n;i++){g->adjlist[i].state=0;// 初始化点集,所有点未在MST中}//从head的第一条边的顶点出发,逐个寻找需要union的int count=0;//用来记录目前MST中有多少个点p=head->next;g->adjlist[p->leftv].state=1;//选中第一个点加入count++;int l,r;while(count!=g->n){p=head->next;while(p){l=g->adjlist[p->leftv].state;r=g->adjlist[p->rightv].state;// 只有l,r一个在点集,一个不在点集,才unionif(l+r == 1){if(l*r==0){p->flag=1;count++;// 建MST树/图j=p->rightv;s=(edgenode *)malloc(sizeof(edgenode));s->adjvex=j;s->weigth=p->weigth;s->next=g->adjlist[i].firstedge; // 头插法创建邻接表g->adjlist[i].firstedge=s;if (c==0) { // c=0表示无向图s=(edgenode *)malloc(sizeof(edgenode));s->adjvex=i;s->next=g->adjlist[j].firstedge;g->adjlist[j].firstedge=s;}// union操作if(l==0)g->adjlist[p->leftv].state=1;else if (r==0)g->adjlist[p->rightv].state=1;break;}else{p=p->next;}}else{p=p->next;}}}// 返回边结点链表return head;}4.调试过程及运行结果(详细记录在调试过程中出现的问题及解决方法。
的最小生成树算法Prim与Kruskal算法的比较
的最小生成树算法Prim与Kruskal算法的比较Prim算法和Kruskal算法都是常用的最小生成树算法,它们可以在给定的加权连通图中找到连接所有节点的最小权重边集合。
然而,这两种算法在实现细节和时间复杂度上有所不同。
本文将对Prim算法和Kruskal算法进行比较,并讨论它们的优缺点以及适用场景。
一、Prim算法Prim算法是一种贪心算法,它从一个起始节点开始,逐步扩展最小生成树的边集合,直到包含所有节点为止。
具体步骤如下:1. 选取一个起始节点作为最小生成树的根节点。
2. 在最小生成树的边集合中寻找与当前树集合相连的最小权重边,并将这条边添加到最小生成树中。
3. 将新添加的节点加入到树集合中。
4. 重复步骤2和3,直到最小生成树包含所有节点为止。
Prim算法的时间复杂度为O(V^2),其中V是节点的个数。
这是因为在每轮迭代中,需要从树集合以外的节点中找到与树集合相连的最小权重边,而在最坏情况下,可能需要检查所有的边。
二、Kruskal算法Kruskal算法是一种基于边的贪心算法,它按照边的权重从小到大的顺序依次选择边,并判断是否加入最小生成树中。
具体步骤如下:1. 初始化一个空的最小生成树。
2. 将所有边按照权重从小到大进行排序。
3. 依次检查每条边,如果这条边连接了两个不同的树(即不会形成环),则将这条边加入到最小生成树中。
4. 重复步骤3,直到最小生成树包含所有节点为止。
Kruskal算法使用并查集数据结构来快速判断连通性,时间复杂度为O(ElogE),其中E是边的个数。
排序边的时间复杂度为O(ElogE),而对每条边进行判断和合并操作的时间复杂度为O(E)。
三、比较与总结1. 时间复杂度:Prim算法的时间复杂度为O(V^2),而Kruskal算法的时间复杂度为O(ElogE)。
因此,在边的数量较大的情况下,Kruskal 算法的效率优于Prim算法。
2. 空间复杂度:Prim算法需要维护一个大小为V的优先队列和一个大小为V的布尔数组,而Kruskal算法需要维护一个大小为V的并查集。
最小生成树的Prim算法以及Kruskal算法的证明
最⼩⽣成树的Prim算法以及Kruskal算法的证明Prime算法的思路:从任何⼀个顶点开始,将这个顶点作为最⼩⽣成树的⼦树,通过逐步为该⼦树添加边直到所有的顶点都在树中为⽌。
其中添加边的策略是每次选择外界到该⼦树的最短的边添加到树中(前提是⽆回路)。
Prime算法的正确性证明:引理1:对于连通图中的顶点vi,与它相连的所有边中的最短边⼀定是属于最⼩⽣成树的。
引理2:证明:假设最⼩⽣成树已经建成;(vi, vj)是连接到顶点vi的最短边,在最⼩⽣成树中取出vi,断开连接到vi的边,则⽣成树被拆分成1、顶点vi2、顶点vj所在的连通分量(单独⼀个顶点也看作⼀个独⽴的连通分量)3、其余若⼲个连通分量(个数⼤于等于0)三个部分现在要重建⽣成树,就要重新连接之前被断开的各边虽然不知道之前被断开的都是哪⼏条边,但是可以通过这样⼀个简单的策略来重建连接:将vi分别以最⼩的成本逐个连接到这若⼲个互相分离的连通分量;具体来说,就是要分别遍历顶点vi到某个连通分量中的所有顶点的连接,然后选择其中最短的边来连接vi和该连通分量;⽽要将vi连接到vj所在的连通分量,显然通过边(vi, vj)连接的成本最低,所以边(vi, vj)必然属于最⼩⽣成树(如果连接到vi的最短边不⽌⼀条,只要任意挑选其中的⼀条(vi, vj)即可,以上的证明对于这种情况同样适⽤)。
这样我们就为原来只有⼀个顶点vi的⼦树添加了⼀个新的顶点vj及新边(vi, vj);接下来只要将这棵新⼦树作为⼀个连通⼦图,并且⽤这个连通⼦图替换顶点vi重复以上的分析,迭代地为⼦树逐个地添加新顶点和新边即可。
Kruskal算法:通过从⼩到⼤遍历边集,每次尝试为最⼩⽣成树加⼊当前最短的边,加⼊成功的条件是该边不会在当前已构建的图中造成回路,当加⼊的边的数⽬达到n-1,遍历结束。
Kruskal算法的正确性证明:Kruskal算法每次为当前的图添加⼀条不会造成回路的新边,其本质是逐步地连接当前彼此分散的各个连通分量(单个顶点也算作⼀个连通分量),⽽连接的策略是每次只⽤最⼩的成本连接任意两个连通分量。
最小生成树---普里姆算法(Prim算法)和克鲁斯卡尔算法(Kruskal算法)
最⼩⽣成树---普⾥姆算法(Prim算法)和克鲁斯卡尔算法(Kruskal算法)最⼩⽣成树的性质:MST性质(假设N=(V,{E})是⼀个连通⽹,U是顶点集V的⼀个⾮空⼦集,如果(u,v)是⼀条具有最⼩权值的边,其中u属于U,v属于V-U,则必定存在⼀颗包含边(u,v)的最⼩⽣成树)普⾥姆算法(Prim算法)思路:以点为⽬标构建最⼩⽣成树1.将初始点顶点u加⼊U中,初始化集合V-U中各顶点到初始顶点u的权值;2.根据最⼩⽣成树的定义:从n个顶点中,找出 n - 1条连线,使得各边权值最⼩。
循环n-1次如下操作:(1)从数组lowcost[k]中找到vk到集合U的最⼩权值边,并从数组arjvex[k] = j中找到该边在集合U中的顶点下标(2)打印此边,并将vk加⼊U中。
(3)通过查找邻接矩阵Vk⾏的各个权值,即vk点到V-U中各顶点的权值,与lowcost的对应值进⾏⽐较,若更⼩则更新lowcost,并将k存⼊arjvex数组中以下图为例#include<bits/stdc++.h>using namespace std;#define MAXVEX 100#define INF 65535typedef char VertexType;typedef int EdgeType;typedef struct {VertexType vexs[MAXVEX];EdgeType arc[MAXVEX][MAXVEX];int numVertexes, numEdges;}MGraph;void CreateMGraph(MGraph *G) {int m, n, w; //vm-vn的权重wscanf("%d %d", &G->numVertexes, &G->numEdges);for(int i = 0; i < G->numVertexes; i++) {getchar();scanf("%c", &G->vexs[i]);}for(int i = 0; i < G->numVertexes; i++) {for(int j = 0; j < G->numVertexes; j++) {if(i == j) G->arc[i][j] = 0;else G->arc[i][j] = INF;}}for(int k = 0; k < G->numEdges; k++) {scanf("%d %d %d", &m, &n, &w);G->arc[m][n] = w;G->arc[n][m] = G->arc[m][n];}}void MiniSpanTree_Prim(MGraph G) {int min, j, k;int arjvex[MAXVEX]; //最⼩边在 U集合中的那个顶点的下标int lowcost[MAXVEX]; // 最⼩边上的权值//初始化,从点 V0开始找最⼩⽣成树Tarjvex[0] = 0; //arjvex[i] = j表⽰ V-U中集合中的 Vi点的最⼩边在U集合中的点为 Vjlowcost[0] = 0; //lowcost[i] = 0表⽰将点Vi纳⼊集合 U ,lowcost[i] = w表⽰ V-U中 Vi点到 U的最⼩权值for(int i = 1; i < G.numVertexes; i++) {lowcost[i] = G.arc[0][i];arjvex[i] = 0;}//根据最⼩⽣成树的定义:从n个顶点中,找出 n - 1条连线,使得各边权值最⼩for(int i = 1; i < G.numVertexes; i++) {min = INF, j = 1, k = 0;//寻找 V-U到 U的最⼩权值minfor(j; j < G.numVertexes; j++) {// lowcost[j] != 0保证顶点在 V-U中,⽤k记录此时的最⼩权值边在 V-U中顶点的下标if(lowcost[j] != 0 && lowcost[j] < min) {min = lowcost[j];k = j;}}}printf("V[%d]-V[%d] weight = %d\n", arjvex[k], k, min);lowcost[k] = 0; //表⽰将Vk纳⼊ U//查找邻接矩阵Vk⾏的各个权值,与lowcost的对应值进⾏⽐较,若更⼩则更新lowcost,并将k存⼊arjvex数组中for(int i = 1; i < G.numVertexes; i++) {if(lowcost[i] != 0 && G.arc[k][i] < lowcost[i]) {lowcost[i] = G.arc[k][i];arjvex[i] = k;}}}int main() {MGraph *G = (MGraph *)malloc(sizeof(MGraph));CreateMGraph(G);MiniSpanTree_Prim(*G);}/*input:4 5abcd0 1 20 2 20 3 71 2 42 3 8output:V[0]-V[1] weight = 2V[0]-V[2] weight = 2V[0]-V[3] weight = 7最⼩总权值: 11*/时间复杂度O(n^2)克鲁斯卡尔算法(Kruskal算法)思路:以边为⽬标进⾏构建最⼩⽣成树在边集中依次寻找最⼩权值边,若构建是不形成环路(利⽤parent数组记录各点的连通分量),则将其添加到最⼩⽣成树中。
最小生成树(MST)Prim算法和Kruskal算法
最⼩⽣成树(MST)Prim算法和Kruskal算法刚学完最⼩⽣成树,赶紧写写学习的⼼得(其实是怕我⾃⼰忘了)最⼩⽣成树概念:⼀个有 n 个结点的的⽣成树是原图的极⼩连通⼦图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。
就是说如果我们想把⼀张有n个点的图连接起来,那我们就只需要n-1条边(原因显然:就如同⼀条有n个点的线段,他们之间最少需要n-1条边连起来)最⼩⽣成树就是寻找值最⼩的这n-1个点,把他们加和。
⾸先,最⼩⽣成树最基本的算法是Prim和Kruskal算法Prim算法:算法分析&思想讲解:Prim算法采⽤“蓝⽩点”思想:⽩点代表已经进⼊最⼩⽣成树的点,蓝点代表未进⼊最⼩⽣成树的点。
Prim算法每次循环都将⼀个蓝点u变为⽩点,并且此蓝点u与⽩点相连的最⼩边权min[u]还是当前所有蓝点中最⼩的。
这样相当于向⽣成树中添加了n-1次最⼩的边,最后得到的⼀定是最⼩⽣成树。
Prim算法的好处就在于它与边⽆关,主要⽤于稠密图,复杂度为O(n^2),实⽤度不如Kruskal算法⾼代码介绍:(好像不可以直接⽤,有点问题)#include<iostream>#include<cstring>#include<cstdio>using namespace std;const int MAXN=5010;int t[MAXN][MAXN];bool b[MAXN];int MIN[MAXN];int main(){memset(b,false,sizeof(b));memset(t,127,sizeof(t));memset(MIN,127,sizeof(MIN)); //把每⼀条未赋值的边赋为较⼤的⼀个数int n,m;int ans=0;scanf("%d",&n);for(int i=1;i<=n;i++)t[i][i]=0;for(int i=1;i<=n;i++){ //邻接矩阵存图for (int j=1;j<=n;j++){ //不同问题存图⽅式不同cin>>t[i][j];}}MIN[1]=0;//先找点:for(int i=1;i<=n;i++){int x=0; //x为0 就是说⼀开始是从⼀个虚拟点开始的然后我们找与它相邻的边并且还没被找过的点for(int j=1;j<=n;j++){if(!b[j]&&MIN[j]<MIN[x]){ //我们以这⼀个点开始寻找与它相邻的最⼩的边x=j; //然后就标记这个点以便于接着⽤这个点继续往下找}}b[x]=true; //找完这个点后就变成⽩点,表⽰已找过//再扩边:for(int j=1;j<=n;j++){if(!b[j]&&MIN[j]>t[x][j]){ //这段代码就是给我们刚找到的X点的邻边赋实际值,这样在下次寻找X的最⼩边时就可以找到啦MIN[j]=t[x][j]; //所以说找点的代码就⽐较好理解了}}}for(int i=1;i<=n;i++){ans+=MIN[i];//求最⼩和}cout<<ans<<endl;return0;}知识扩展:本算法在移动通信、智能交通、移动物流、⽣产调度等物联⽹相关领域都有⼗分现实的意义,采⽤好的算法,就能节省成本提⾼效率。
最小生成树算法总结
最小生成树算法总结最小生成树是指在一个无向连通图中,找到一个子树,使得这棵子树中所有边的权值之和最小。
最小生成树可以用于最优化问题,例如道路铺设、网络布线等。
下面将介绍三种最小生成树算法:Prim算法、Kruskal算法、Boruvka算法。
1. Prim算法Prim算法是一种贪心算法,从一个点开始,每次添加连接到已有集合中的最小边,直到所有点都在同一个集合中。
可以用以下步骤描述Prim算法:(1) 选择一个起点,将该起点加入最小生成树的顶点集合,然后将该顶点相邻的边加入边集合中。
(2) 从边集合中找到权值最小的一条边,将该边对应的顶点加入最小生成树的顶点集合,同时将该顶点相邻的边加入边集合中。
(3) 重复上述步骤,直到所有顶点都在最小生成树的顶点集合中。
Prim算法的实现可以使用堆优化,时间复杂度为O(E + VlogV),其中E为边数,V为顶点数。
2. Kruskal算法Kruskal算法也是一种贪心算法,与Prim算法不同的是,Kruskal算法是按照边的权值从小到大依次添加,直到所有顶点都在同一个集合中。
可以用以下步骤描述Kruskal算法:(1) 将所有边按照权值从小到大排序。
(2) 依次取出排好序的边,如果该边所连接的两个顶点不在同一个集合中,就将这条边加入最小生成树的边集合中,并将这两个顶点合并到同一个集合中。
(3) 重复步骤(2),直到所有顶点都在同一个集合中。
Kruskal算法的实现可以使用并查集,时间复杂度为O(ElogE),其中E为边数。
3. Boruvka算法Boruvka算法是一种基于集合的分治算法,与Prim算法和Kruskal算法不同,Boruvka算法的时间复杂度是线性的。
可以用以下步骤描述Boruvka算法:(1) 对每个顶点建立单元素集合。
(2) 对每个集合,选择与该集合相连的最小权值的边,将这些边添加到最小生成树的边集合中,并将这些集合合并到同一个集合中。
(3) 如果只剩下一个集合,算法结束。
求无向图的最小生成树算法——Prim与Kruskal
1.算法思想
对于图G=(V,E),用Prim算法求最小生成树T=(S,TE)的流程如下
① 初始化:设S、TE为空集,任选节点K加入S。
② 选取一条权值最小的边(X,Y),其中X∈S,且not (Y∈S) 即,选取一条权值最小的、连接着S中一点与S外一点的边。
以上操作重复|V|-1次结束。由于每次加入S的点i都在当时取到了符合流程②的边min{lowcost},而lowcost[i]=w(i,closest[i]),所以此时的最小生成树的各边就是(i,closest[i]),i∈V且not (i=x)【需要注意的是出发点x的closest[x]还是x,所以应忽略,实际取到x-1条边】。把i从1取到|V|,便得到最小生成树T的每条边。
为了比较快速地选边,我们用两个数组lowcost、closest动态地维护每一个点到S的最短距离。在某一状态下,lowcost[i]表示所有与i相连且另一端点在S中的边中的权值最小值,closest[i]表示在S中且与i相连的点中与i之间距离最小的点。显然,lowcost[i]=w(i,closest[i])。需要注意的是两个数组记录的都是边而不是路径。若i没有边直接连向S,则lowcost[i]=∞。另外,若i已在S中,则lowcost[i]=0。
lowcost[j] = w[k][j];
closest[j] = k;
} //由新加入S中的k点使某些点到S的距离缩短,所以更新各点的lowcost和close= 1; i <= n; i++)
if(i != closest[i]){
设出发点为x。初始时对于任意k∈V,closest[k]=x,lowcost[k]=w(k,x)【w(i,j)表示i、j间的距离。初始化时,若两点间没有边则w(i,j)赋为一个足够大的整数(如maxint),并且所有点到自身的距离赋为0,即w(i,i)=0】
分别利用prim算法和kruskal算法实现求图的最小生成树
/*分别利用prim算法和kruskal算法实现求图的最小生成树*/ #include<stdio.h>#include<stdlib.h>#define MaxVertexNum 12#define MaxEdgeNum 20#define MaxValue 1000typedef int Vertextype;typedef int adjmatrix[MaxVertexNum][MaxVertexNum]; typedef Vertextype vexlist[MaxVertexNum];int visited[MaxVertexNum]={0};struct edgeElem{int fromvex;int endvex;int weight;};typedef struct edgeElem edgeset[MaxVertexNum];void Creat_adjmatrix(vexlist GV,adjmatrix GA,int n,int e) {int i,j,k,w;printf("输入%d个顶点数据",n);for(i=0;i<n;i++)scanf("%d",&GV[i]);for(i=0;i<n;i++)for(j=0;j<n;j++)if(i==j) GA[i][j]=0;else GA[i][j]=MaxValue;printf("输入%d条无向带权边",e);for(k=0;k<e;k++){scanf("%d%d%d",&i,&j,&w);GA[i][j]=GA[j][i]=w;}}void Creat_edgeset(vexlist GV,edgeset GE,int n,int e) {int i,j,k,w;printf("输入%d个顶点数据",n);for(i=0;i<n;i++)scanf("%d",&GV[i]);printf("输入%d条无向带权边",e);for(k=0;k<e;k++){ scanf("%d%d%d",&i,&j,&w);GE[k].fromvex=i;GE[k].endvex=j;GE[k].weight=w;}}void output_edgeset(edgeset GE,int e){int k;for(k=0;k<e;k++)printf("%d %d %d,",GE[k].fromvex,GE[k].endvex,GE[k].weight); printf("\n");}void prim(adjmatrix GA,edgeset CT,int a,int n){int i,j,t,k,w,min,m;struct edgeElem x;for(i=0;i<n;i++)if(i<a){CT[i].fromvex=a;CT[i].endvex=i;CT[i].weight=GA[a][i];}else if(i>a){CT[i-1].fromvex=a;CT[i-1].endvex=i;CT[i-1].weight=GA[a][i];}for(k=1;k<n;k++){min=MaxValue;m=k-1;for(j=k-1;j<n-1;j++)if(CT[j].weight<min){min=CT[j].weight;m=j;}x=CT[k-1];CT[k-1]=CT[m];CT[m]=x;j=CT[k-1].endvex;for(i=k;i<n-1;i++){t=CT[i].endvex;w=GA[j][t];if(w<CT[i].weight){CT[i].weight=w;CT[i].fromvex=j;}}}}void kruskal(edgeset GE,edgeset C,int n){ int i,j,k,d;int m1,m2;adjmatrix s;for(i=0;i<n;i++){for(j=0;j<n;j++)if(i==j) s[i][j]=1;else s[i][j]=0;}k=1;d=0;while(k<n){for(i=0;i<n;i++){if(s[i][GE[d].fromvex]==1) m1=i;if(s[i][GE[d].endvex]==1) m2=i;}if(m1!=m2){C[k-1]=GE[d];k++;for(j=0;j<n;j++){s[m1][j]=s[m1][j]||s[m2][j];s[m2][j]=0;}}d++;}}void main(){int n,e;vexlist GV;adjmatrix GA;edgeset GE,C;printf("输入图的顶点数和边数:");scanf("%d%d",&n,&e);Creat_adjmatrix( GV, GA, n, e);printf("利用prim算法从0点出发求图的最小生成树:\n");prim(GA,GE,0,n);output_edgeset( GE, n-1);printf("输入图的顶点数和边数:");scanf("%d%d",&n,&e);Creat_edgeset( GV,GE,n, e);printf("利用kruskal算法从0点出发求图的最小生成树:\n");kruskal( GE, C, n);output_edgeset( C, n-1);}最小生成树(prim算法和kruskal算法)收藏最小生成树(prim算法)用最小生成树的解决的经典问题:若要在n个城市间建设通信网路,给出任意两个城市的距离和每米通信网路的造价,问怎样设计网络可以使网路的造价最小。
的最小生成树算法Prim和Kruskal算法
的最小生成树算法Prim和Kruskal算法Prim和Kruskal算法是求解最小生成树的两种常用算法。
最小生成树指的是在一个连通图中,找到一棵包含所有顶点的生成树,使得树上边的权重之和最小。
Prim算法基于贪心思想,从一个起始顶点开始,逐步向其他顶点扩展,每次选择权重最小的边连接已经选中的顶点和未选中的顶点。
具体步骤如下:1. 初始化一个空的集合S,用于存放已经选中的顶点。
2. 从图中任选一个顶点作为起始顶点,并将其加入集合S。
3. 重复以下步骤,直到集合S包含了所有顶点:a. 在未加入集合S的顶点中,找到与集合S中顶点相连的边中权重最小的边。
b. 将该边加入生成树中,并将与该边相连的顶点加入集合S。
4. 生成的树即为最小生成树。
Kruskal算法是基于边的权重排序的思想。
具体步骤如下:1. 初始化一个空的集合S,用于存放已经选中的边。
2. 将图中的所有边按照权重从小到大排序。
3. 重复以下步骤,直到生成的树中包含了所有顶点-1条边(其中顶点的数量为n):a. 从排序后的边列表中选取一条最小权重的边。
b. 若该边的两个顶点不在同一连通分量中,将该边加入生成树中。
c. 否则,舍弃该边,继续选择下一条边。
4. 生成的树即为最小生成树。
值得注意的是,在构建最小生成树时,如果图不是连通图,那么最小生成树就不存在。
Prim算法的时间复杂度为O(V^2),其中V表示顶点的数量。
在稠密图中效果较好。
而Kruskal算法的时间复杂度为O(ElogE),其中E表示边的数量。
在稀疏图中效果较好。
这两种算法在实际应用中都有一定的局限性。
Prim算法适合处理边稠密、顶点稀疏的图,而Kruskal算法适合处理边稀疏、顶点稀疏的图。
在选择使用算法时,需要根据具体问题的特点进行权衡。
最小生成树算法Prim和Kruskal算法在图论领域具有重要的地位,广泛应用于网络设计、电路布线、城市规划等领域。
通过构建最小生成树,可以在保证连通性的前提下,选择最经济、最高效的路径和连接方式,节约资源并提高效率。
最小生成树
算法图解
例如,对前面的连通带权图,按Kruskal算法顺序得到的 最小生成树上的边如下图所示。
用Kruskal算法解决
1.用并查集检查待加入生成树的两边是否会
构成回路,快速排序按权值排列边。 2.这里有一个优化:因为是无向图,所以矩 阵是对称的,因此我们只保存上三角矩阵 即可。这样到最后k的值就是边数,由循环 n*n次缩减到循环k次...
Poj 1258
农夫约翰决定竞选他们城市的市长。他参加竞选的承诺之一是,促进所有农场网络的连通性。他需 要你的帮助。 约翰为他的农场提供了高速连接,并准备分享他的连通性给其他农夫。为了使花费最低,他希望放 置最少数目的光纤用于连接他的农场与其他所有农场。 给出一列关于多少需要光纤连接每一对农场,你需要找出所需光纤的最少数目。每个农场必须连接 到其他农场!! 每两个农场之间的距离不超过100,000. 输入:输入包括多个数据。对于每个数据,第一行包括了一个数字N,即农场数目(3<=N<=100)。 接下来几行包括一个N*N的矩阵,其中每个元素都代表了从第i个农场到第j个农场的距离。理论上, 他们都是N行,每行N个整数,【他们的长度限制在80个字符内,所以一些行将跟在其他行后继 续。】当然,对角线上的数字为0. 输出:对于每组数据,输出是一个整数,表示所需光纤的最少长度的总和。 Sample Input: 4 0 4 9 21 4 0 8 17 9 8 0 16 21 17 16 0 Sample Output: 28
int main() { int i, j, n, k;int tmp; while (scanf("%d", &n) != EOF) { k = 0;sum=0; for (i = 0; i < n; i++) { Make_Set(i); for (j = 0; j < n; j++) /* 只读取上三角 */ { if (i < j) { e[k].x = i; e[k].y = j; scanf("%d", &e[k].w); k++; } else scanf("%d", &tmp); } } qsort(e, k, sizeof(edge), cmp); for (i = 0; i < k; i++) { Union(Find_Set(e[i].x), Find_Set(e[i].y), e[i].w); } printf("%d\n", sum); } return 0; }
最小生成树算法详解
最小生成树算法详解最小生成树(Minimum Spanning Tree,简称MST)是图论中的一个经典问题,它是指在一个加权连通图中找出一棵包含所有顶点且边权值之和最小的树。
在解决实际问题中,最小生成树算法被广泛应用于网络规划、电力传输、城市道路建设等领域。
本文将详细介绍最小生成树算法的原理及常见的两种算法:Prim算法和Kruskal算法。
一、最小生成树算法原理最小生成树算法的核心思想是贪心算法。
其基本原理是从图的某个顶点开始,逐步选取当前顶点对应的边中权值最小的边,并确保选取的边不会构成环,直到所有顶点都被连接为止。
具体实现最小生成树算法的方法有多种,两种常见的算法是Prim 算法和Kruskal算法。
二、Prim算法Prim算法是一种基于顶点的贪心算法。
它从任意一个顶点开始,逐渐扩展生成树的规模,直到生成整个最小生成树。
算法的具体步骤如下:1. 初始化一个空的生成树集合和一个空的顶点集合,将任意一个顶点加入到顶点集合中。
2. 从顶点集合中选择一个顶点,将其加入到生成树集合中。
3. 以生成树集合中的顶点为起点,寻找与之相邻的顶点中权值最小的边,并将该边与对应的顶点加入到最小生成树中。
4. 重复第3步,直到生成树中包含所有顶点。
Prim算法是一种典型的贪心算法,其时间复杂度为O(V^2),其中V为顶点数。
三、Kruskal算法Kruskal算法是一种基于边的贪心算法。
它首先将所有边按照权值从小到大进行排序,然后从小到大依次选择边,判断选取的边是否与已选取的边构成环,若不构成环,则将该边加入到最小生成树中。
算法的具体步骤如下:1. 初始化一个空的生成树集合。
2. 将图中的所有边按照权值进行排序。
3. 依次选择权值最小的边,判断其两个顶点是否属于同一个连通分量,若不属于,则将该边加入到最小生成树中。
4. 重复第3步,直到最小生成树中包含所有顶点。
Kruskal算法通过并查集来判断两个顶点是否属于同一个连通分量,从而避免形成环。
最小生成树(Kruskal和Prim算法)
最⼩⽣成树(Kruskal和Prim算法)关于图的⼏个概念定义:关于图的⼏个概念定义:连通图:在⽆向图中,若任意两个顶点vi与vj都有路径相通,则称该⽆向图为连通图。
强连通图:在有向图中,若任意两个顶点vi与vj都有路径相通,则称该有向图为强连通图。
连通⽹:在连通图中,若图的边具有⼀定的意义,每⼀条边都对应着⼀个数,称为权;权代表着连接连个顶点的代价,称这种连通图叫做连通⽹。
⽣成树:⼀个连通图的⽣成树是指⼀个连通⼦图,它含有图中全部n个顶点,但只有⾜以构成⼀棵树的n-1条边。
⼀颗有n个顶点的⽣成树有且仅有n-1条边,如果⽣成树中再添加⼀条边,则必定成环。
最⼩⽣成树:在连通⽹的所有⽣成树中,所有边的代价和最⼩的⽣成树,称为最⼩⽣成树。
构造最⼩⽣成树的准则有3条:(1)必须只使⽤该⽹络中的边来构造最⼩⽣成树。
(2)必须使⽤且仅使⽤n-1条边来连接⽹络中的n个顶点。
(3)不能使⽤产⽣回路的边。
下⾯介绍两种求最⼩⽣成树算法1 Prim(普利姆算法)算法--加点法此算法可以称为“加点法”,每次迭代选择代价最⼩的边对应的点,加⼊到最⼩⽣成树中。
算法从某⼀个顶点s开始,逐渐长⼤覆盖整个连通⽹的所有顶点。
Prim算法从任意⼀个顶点开始,每次选择⼀个与当前顶点集最近的⼀个顶点,并将两顶点之间的边加⼊到树中。
Prim算法在找当前最近顶点时使⽤到了贪婪算法。
实现过程:5int logo[1010];///⽤0和1来表⽰是否被选择过6int map1[1010][1010];7int dis[1010];///记录任意⼀点到这⼀点的最近的距离8int n,m;9int prim()10 {11int i,j,now;12int sum=0;13for(i=1;i<=n;i++)///初始化14 {15 dis[i]=MAX;16 logo[i]=0;17 }18for(i=1;i<=n;i++)19 {20 dis[i]=map1[1][i];21 }22 dis[1]=0;23 logo[1]=1;24for(i=1;i<n;i++)///循环查找25 {26 now=MAX;27int min1=MAX;28for(j=1;j<=n;j++)29 {30if(logo[j]==0&&dis[j]<min1)31 {32 now=j;33 min1=dis[j];34 }35 }36if(now==MAX)///防⽌不成图37 {38break;39 }40 logo[now]=1;41 sum=sum+min1;42for(j=1;j<=n;j++)///填⼊新点后更新最⼩距离,到顶点集的距离43 {44if(logo[j]==0&&dis[j]>map1[now][j])45 {46 dis[j]=map1[now][j];47 }48 }49 }50if(i<n)51 {52 printf("?\n");53 }54else55 {56 printf("%d\n",sum);57 }58 }59int main()60 {61while(scanf("%d%d",&m,&n)!=EOF)///n是点数62 {63if(m==0)64 {65break;66 }67 memset(map1,0x3f3f3f3f,sizeof(map1));///map是邻接矩阵储存图的信息68for(int i=0;i<m;i++)69 {70int a,b,c;71 scanf("%d%d%d",&a,&b,&c);72if(c<map1[a][b])///防⽌出现重边73 {74 map1[a][b]=map1[b][a]=c;75 }76 }77 prim();78 }79return0;80 }邻接表实现:1 #include<stdio.h>2 #include<string.h>3 #include<vector>4 #include<algorithm>5#define INF 0x3f3f3f3f6using namespace std;7struct node8 {9int end;///终点10int power;///权值11 } t;12int n;///n为点数13 vector<node>q[500001];///邻接表储存图的信息14int dis[500001];///距离15int vis[500001];///标记数组16void prime()17 {18int i,len,j,pos,sum,start;19 memset(vis,0,sizeof(vis));20 sum=0;21 start=1;///任意取起点22for(i=0; i<=n; i++)23 {24 dis[i]=INF;25 }26 len=q[start].size();27for(i=0; i<len; i++)///从任意起点开始的dis数组更新28 {29if(q[start][i].power<dis[q[start][i].end])30 {31 dis[q[start][i].end]=q[start][i].power;32 }33 }34 vis[start]=1;35for(j=0; j<n-1; j++)36 {37int pos,min=INF;38for(i=1; i<=n; i++)39 {40if(vis[i]!=0&&dis[i]<min)41 {42 min=dis[i];43 pos=i;///找到未访问节点中权值最⼩的44 }45 }46if(pos==INF)///防⽌不成图47 {48break;49 }50 vis[pos]=1;51 sum=sum+min;52 len=q[pos].size();///再次更新dis数组53for(j=0; j<len; j++)54 {55if(vis[q[pos][j].end]==0&&dis[q[pos][j].end]>q[pos][j].power)56 {57 dis[q[pos][j].end] = q[pos][j].power;58 }59 }60 }61if(j<n)62 {63 printf("?\n");64 }65else66 {67 printf("%d\n",sum);68 }69 }70int main()71 {72int m,i;73int begin,end,power;74int a,b;75while(scanf("%d%d",&n,&m)!=EOF)76 {77for(i=0; i<=n; i++)78 {79 q[i].clear();///将victor数组清空80 }81for(i=0; i<m; i++)82 {83 scanf("%d%d%d",&begin,&end,&power);///输⼊84 t.end=end;85 t.power=power;86 q[begin].push_back(t);87 t.end=begin;///⽆向图88 t.power=power;89 q[end].push_back(t);90 }91 prime();92 }93return0;94 }这⾥再给出⼀个没有使⽤标记数组的代码:int prim(int s){int i,j,sum=0;int now;for(i=1;i<=n;i++){closest[i]=INT_MAX;}for(i=1;i<=n;i++){closest[i]=map[s][i];}closest[s]=0;for(i=1;i<n;i++)//这⾥的i代表的是边数,有n个点就会有n-1条边{int min=INT_MAX;for(j=1;j<=n;j++){if(closest[j]&&closest[j]<min){min=closest[j];now=j;//找到所需的最⼩边}}sum+=min;closest[now]=0;//将找到的边加⼊到最⼩⽣成树之中for(j=1;j<=n;j++)//找到新的点加⼊已选点集合之后,更新该点到未选点集合的距离{if(map[now][j]&&map[now][j]<closest[j]){closest[j]=map[now][j];}}}return sum;}2 Kruskal(克鲁斯卡尔)算法--加边法1.概览 Kruskal算法是⼀种⽤来寻找最⼩⽣成树的算法,在剩下的所有未选取的边中,找最⼩边,如果和已选取的边构成回路,则放弃,选取次⼩边。
最小生成树算法详解
最小生成树算法详解常见的最小生成树算法包括普里姆(Prim)算法和克鲁斯卡尔(Kruskal)算法。
下面将详细介绍这两种算法的原理和步骤。
普里姆算法的基本思想是从一个顶点出发,每次选择与当前生成树相连的权重最小的边,直到生成树包含所有的顶点。
具体步骤如下:1.初始化一个空的生成树和一个空的候选边集合。
2.随机选择一个起始顶点,将其加入生成树中,并将与该顶点相连的边加入候选边集合。
3.从候选边集合中选择权重最小的边,如果该边的另一个顶点不在生成树中,则将该顶点加入生成树,并将与该顶点相连的边加入候选边集合。
4.重复步骤3,直到生成树包含所有的顶点。
克鲁斯卡尔算法的基本思想是从所有边中选取权重最小的边,然后逐步扩展生成树,直到生成树包含所有的顶点。
具体步骤如下:1.初始化一个空的生成树和一个空的候选边集合。
2.将图中的所有边按权重从小到大排序,并加入候选边集合中。
3.从候选边集合中选择权重最小的边,如果该边的两个顶点不在同一个连通分量中,则将该边加入生成树,并将其两个顶点合并到同一个连通分量中。
4.重复步骤3,直到生成树包含所有的顶点。
普里姆算法和克鲁斯卡尔算法都能够求解最小生成树,它们的主要区别在于选择候选边的方式不同。
普里姆算法每次选择与当前生成树相连的权重最小的边,而克鲁斯卡尔算法每次选择整个图中权重最小的边。
因此,普里姆算法适用于稠密图,而克鲁斯卡尔算法适用于稀疏图。
总结起来,最小生成树算法是图论中的一种重要算法,用于求解连通图中的一棵权重最小的生成树。
普里姆算法和克鲁斯卡尔算法是常见的最小生成树算法,它们的主要区别在于选择候选边的方式不同。
这些算法的时间复杂度与图的边数有关,通常为O(ElogE)。
在实际应用中,可以根据具体问题选择适合的算法来求解最小生成树。
最小生成树---Prim算法和Kruskal算法
最⼩⽣成树---Prim算法和Kruskal算法Prim算法1.概览普⾥姆算法(Prim算法),图论中的⼀种算法,可在加权连通图⾥搜索最⼩⽣成树。
意即由此算法搜索到的边⼦集所构成的树中,不但包括了连通图⾥的所有顶点(英语:Vertex (graph theory)),且其所有边的权值之和亦为最⼩。
该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克(英语:Vojtěch Jarník)发现;并在1957年由美国计算机科学家罗伯特·普⾥姆(英语:Robert C. Prim)独⽴发现;1959年,艾兹格·迪科斯彻再次发现了该算法。
因此,在某些场合,普⾥姆算法⼜被称为DJP算法、亚尔尼克算法或普⾥姆-亚尔尼克算法。
2.算法简单描述1).输⼊:⼀个加权连通图,其中顶点集合为V,边集合为E;2).初始化:V new = {x},其中x为集合V中的任⼀节点(起始点),E new = {},为空;3).重复下列操作,直到V new = V:a.在集合E中选取权值最⼩的边<u, v>,其中u为集合V new中的元素,⽽v不在V new集合当中,并且v∈V(如果存在有多条满⾜前述条件即具有相同权值的边,则可任意选取其中之⼀);b.将v加⼊集合V new中,将<u, v>边加⼊集合E new中;4).输出:使⽤集合V new和E new来描述所得到的最⼩⽣成树。
⽰例图演⽰:下⾯对算法的图例描述:3.简单证明prim算法反证法:假设prim⽣成的不是最⼩⽣成树1).设prim⽣成的树为G02).假设存在G min使得cost(G min)<cost(G0) 则在G min中存在<u,v>不属于G03).将<u,v>加⼊G0中可得⼀个环,且<u,v>不是该环的最长边(这是因为<u,v>∈G min)4).这与prim每次⽣成最短边⽭盾5).故假设不成⽴,命题得证.Kruskal算法1.概览Kruskal算法是⼀种⽤来寻找最⼩⽣成树的算法,由Joseph Kruskal在1956年发表。
python版最小生成树Prim和Kruskal算法
python版最⼩⽣成树Prim和Kruskal算法@⽬录最⼩⽣成树(Prim算法、Kruskal算法)⽣成树的定义⽣成树是⼀个连通图G的⼀个极⼩连通⼦图。
包含G的所有n个顶点,但只有n-1条边,并且是连通的。
⽣成树可由遍历过程中所经过的边组成(有多个)。
扩展:⽆向图。
极⼩连通⼦图与极⼤连通⼦图是在⽆向图中进⾏讨论的。
连通图:在⽆向图中,若从定点V1到V2有路径,则称顶点V1和V2是连通的。
如果图中任意⼀对顶点都是连通的,则称此图是连通图。
(连通的⽆向图)极⼩连通⼦图:1.⼀个连通图的⽣成树是该连通图顶点集确定的极⼩连通⼦图。
(同⼀个连通图可以有不同的⽣成树,所以⽣成树不是唯⼀的)(极⼩连通⼦图只存在于连通图中)2.⽤边把极⼩连通⼦图中所有节点给连起来,若有n个节点,则有n-1条边。
如下图⽣成树有6个节点,有5条边。
3.之所以称为极⼩是因为此时如果删除⼀条边,就⽆法构成⽣成树,也就是说给极⼩连通⼦图的每个边都是不可少的。
4.如果在⽣成树上添加⼀条边,⼀定会构成⼀个环。
也就是说只要能连通图的所有顶点⽽⼜不产⽣回路的任何⼦图都是它的⽣成树。
最⼩⽣成树的定义⼀个带权连通⽆向图的⽣成树中,边的权值之和最⼩的那棵树叫做此图的最⼩⽣成树。
图⼀的最⼩⽣成树就是图⼆(最⼩⽣成树在某些情况下并不唯⼀)。
最⼩⽣成树的⽣成算法:求解最⼩⽣成树的算法主要有两个:1.Prim(普⾥姆)算法;2.Kruskal(克鲁斯卡尔)算法。
Prim算法1. 输⼊:⼀个加权连通图,其中顶点集合为V,边集合为E;2. 初始化:定义存放当前已⾛点的集合Vnew = {x},其中x为集合V中的任意节点(作为起始点),定义存放当前已⾛边的集合Enew = { },为空;3. 重复下列操作,直到 Vnew = V:①在集合E中选取权值最⼩的边<u, v>,其中u为集合Vnew中的元素,⽽v不在Vnew集合当中,并且v∈V(如果存在有多条满⾜前述条件即具有相同权值的边,则可任意选取其中之⼀);②将v加⼊集合Vnew中,将<u, v>边加⼊集合Enew中;4. 输出:使⽤集合Vnew和Enew来描述所得到的最⼩⽣成树。
prim算法和kruskal算法例题
一、概述在图论中,prim算法和kruskal算法是两种常用的最小生成树算法。
它们分别以不同的方式来寻找给定图的最小生成树,是解决最小生成树问题的有效方法。
本文将重点介绍prim算法和kruskal算法,并通过例题分析,展示它们的应用及原理。
二、prim算法1. prim算法概述2. prim算法步骤3. 例题分析:通过一个具体图示例,展示prim算法的应用过程,详细阐述每一步的操作及思路。
4. prim算法优缺点三、kruskal算法1. kruskal算法概述2. kruskal算法步骤3. 例题分析:通过一个具体图示例,展示kruskal算法的应用过程,详细阐述每一步的操作及思路。
4. kruskal算法优缺点四、prim算法和kruskal算法的比较1. 时间复杂度2. 空间复杂度3. 适用范围4. 其他特点五、例题分析总结通过对两种算法在具体例题中的应用过程分析,总结prim算法和kruskal算法的异同点,以及在实际问题中应用时的考虑因素。
六、结论根据对prim算法和kruskal算法的介绍及例题分析,总结两种算法的特点和应用场景,为不同情况下的最小生成树问题提供参考指导。
七、参考文献列出本文所参考的相关书籍、文献或全球信息站信息,为读者进一步了解prim算法和kruskal算法提供便利。
八、附录可放置示例代码、补充说明或其他相关内容,以便读者更好地理解prim算法和kruskal算法。
由于当前训练模型对于编程题的掌握有一定限制,可以提供在Prim算法和Kruskal算法方面相关的例题解析和应用案例。
以下是一个基于图的例题及其解析。
假设有一个带权重的无向连通图G,图中的顶点集合为V,边的集合为E,每条边的权重由权重函数w(u, v)给出,其中u, v为边的两个顶点。
现在需要使用Prim算法和Kruskal算法来寻找图G的最小生成树。
首先我们需要给出一个具体的图G,如下所示:顶点集合V = {A, B, C, D, E}边的集合E = {(A, B, 3), (A, C, 1), (A, D, 5), (B, C, 4), (B, D, 6), (B, E, 2), (C, D, 7), (C, E, 8), (D, E, 9)}其中,每个元组表示一条边的起始顶点、终止顶点和权重。
数据结构(三十三)最小生成树(Prim、Kruskal)
数据结构(三⼗三)最⼩⽣成树(Prim、Kruskal) ⼀、最⼩⽣成树的定义 ⼀个连通图的⽣成树是⼀个极⼩的连通⼦图,它含有图中全部的顶点,但只有⾜以构成⼀棵树的n-1条边。
在⼀个⽹的所有⽣成树中,权值总和最⼩的⽣成树称为最⼩代价⽣成树(Minimum Cost Spanning Tree),简称为最⼩⽣成树。
构造最⼩⽣成树的准则有以下3条:只能使⽤该图中的边构造最⼩⽣成树当且仅当使⽤n-1条边来连接图中的n个顶点不能使⽤产⽣回路的边 对⽐两个算法,Kruskal算法主要是针对边来展开,边数少时效率会⾮常⾼,所以对于稀疏图有很⼤的优势;⽽Prim算法对于稠密图,即边数⾮常多的情况会更好⼀些。
⼆、普⾥姆(Prim)算法 1.Prim算法描述 假设N={V,{E}}是连通⽹,TE是N上最⼩⽣成树中边的集合。
算法从U={u0,u0属于V},TE={}开始。
重复执⾏下⾯的操作:在所有u属于U,v 属于V-U的边(u,v)中找⼀条代价最⼩的边(u0,v0)并加⼊集合TE,同时v0加⼊U,直到U=V为⽌。
此时TE中必有n-1条边,则T=(V,{TE})为N的最⼩⽣成树。
2.Prim算法的C语⾔代码实现/* Prim算法⽣成最⼩⽣成树 */void MiniSpanTree_Prim(MGraph G){int min, i, j, k;int adjvex[MAXVEX]; /* 保存相关顶点下标 */int lowcost[MAXVEX]; /* 保存相关顶点间边的权值 */lowcost[0] = 0;/* 初始化第⼀个权值为0,即v0加⼊⽣成树 *//* lowcost的值为0,在这⾥就是此下标的顶点已经加⼊⽣成树 */adjvex[0] = 0; /* 初始化第⼀个顶点下标为0 */for(i = 1; i < G.numVertexes; i++) /* 循环除下标为0外的全部顶点 */{lowcost[i] = G.arc[0][i]; /* 将v0顶点与之有边的权值存⼊数组 */adjvex[i] = 0; /* 初始化都为v0的下标 */}for(i = 1; i < G.numVertexes; i++){min = INFINITY; /* 初始化最⼩权值为∞, *//* 通常设置为不可能的⼤数字如32767、65535等 */j = 1;k = 0;while(j < G.numVertexes) /* 循环全部顶点 */{if(lowcost[j]!=0 && lowcost[j] < min)/* 如果权值不为0且权值⼩于min */{min = lowcost[j]; /* 则让当前权值成为最⼩值 */k = j; /* 将当前最⼩值的下标存⼊k */}j++;}printf("(%d, %d)\n", adjvex[k], k);/* 打印当前顶点边中权值最⼩的边 */lowcost[k] = 0;/* 将当前顶点的权值设置为0,表⽰此顶点已经完成任务 */for(j = 1; j < G.numVertexes; j++) /* 循环所有顶点 */{if(lowcost[j]!=0 && G.arc[k][j] < lowcost[j]){/* 如果下标为k顶点各边权值⼩于此前这些顶点未被加⼊⽣成树权值 */lowcost[j] = G.arc[k][j];/* 将较⼩的权值存⼊lowcost相应位置 */adjvex[j] = k; /* 将下标为k的顶点存⼊adjvex */}}}}Prim算法 3.Prim算法的Java语⾔代码实现package bigjun.iplab.adjacencyMatrix;/*** 最⼩⽣成树之Prim算法*/public class MiniSpanTree_Prim {int lowCost; // 顶点对应的权值public CloseEdge(Object adjVex, int lowCost) {this.adjVex = adjVex;this.lowCost = lowCost;}}private static int getMinMum(CloseEdge[] closeEdges) {int min = Integer.MAX_VALUE; // 初始化最⼩权值为正⽆穷int v = -1; // 顶点数组下标for (int i = 0; i < closeEdges.length; i++) { // 遍历权值数组,找到最⼩的权值以及对应的顶点数组的下标if (closeEdges[i].lowCost != 0 && closeEdges[i].lowCost < min) {min = closeEdges[i].lowCost;v = i;}}return v;}// Prim算法构造图G的以u为起始点的最⼩⽣成树public static void Prim(AdjacencyMatrixGraphINF G, Object u) throws Exception{// 初始化⼀个⼆维最⼩⽣成树数组minSpanTree,由于最⼩⽣成树的边是n-1,所以数组第⼀个参数是G.getVexNum() - 1,第⼆个参数表⽰边的起点和终点符号,所以是2 Object[][] minSpanTree = new Object[G.getVexNum() - 1][2];int count = 0; // 最⼩⽣成树得到的边的序号// 初始化保存相关顶点和相关顶点间边的权值的数组对象CloseEdge[] closeEdges = new CloseEdge[G.getVexNum()];int k = G.locateVex(u);for (int j = 0; j < G.getVexNum(); j++) {if (j!=k) {closeEdges[j] = new CloseEdge(u, G.getArcs()[k][j]);// 将顶点u到其他各个顶点权值写⼊数组中}}closeEdges[k] = new CloseEdge(u, 0); // 加⼊u到⾃⾝的权值0for (int i = 1; i < G.getVexNum(); i++) { // 注意,这⾥从1开始,k = getMinMum(closeEdges); // 获取u到数组下标为k的顶点的权值最短minSpanTree[count][0] = closeEdges[k].adjVex; // 最⼩⽣成树第⼀个值为uminSpanTree[count][1] = G.getVexs()[k]; // 最⼩⽣成树第⼆个值为k对应的顶点count++;closeEdges[k].lowCost = 0; // 下标为k的顶点不参与最⼩权值的查找了for (int j = 0; j < G.getVexNum(); j++) {if (G.getArcs()[k][j] < closeEdges[j].lowCost) {closeEdges[j] = new CloseEdge(G.getVex(k), G.getArcs()[k][j]);}}}System.out.print("通过Prim算法得到的最⼩⽣成树序列为: {");for (Object[] Tree : minSpanTree) {System.out.print("(" + Tree[0].toString() + "-" + Tree[1].toString() + ")");}System.out.println("}");}} 4.举例说明Prim算法实现过程 以下图为例: 测试类:// ⼿动创建⼀个⽤于测试最⼩⽣成树算法的⽆向⽹public static AdjacencyMatrixGraphINF createUDNByYourHand_ForMiniSpanTree() {Object vexs_UDN[] = {"V0", "V1", "V2", "V3", "V4", "V5", "V6", "V7", "V8"};int arcsNum_UDN = 15;int[][] arcs_UDN = new int[vexs_UDN.length][vexs_UDN.length];for (int i = 0; i < vexs_UDN.length; i++) // 构造⽆向图邻接矩阵for (int j = 0; j < vexs_UDN.length; j++)if (i==j) {arcs_UDN[i][j]=0;} else {arcs_UDN[i][j] = arcs_UDN[i][j] = INFINITY;}arcs_UDN[0][5] = 11;arcs_UDN[1][2] = 18;arcs_UDN[1][6] = 16;arcs_UDN[1][8] = 12;arcs_UDN[2][3] = 22;arcs_UDN[2][8] = 8;arcs_UDN[3][4] = 20;arcs_UDN[3][6] = 24;arcs_UDN[3][7] = 16;arcs_UDN[3][8] = 21;arcs_UDN[4][5] = 26;arcs_UDN[4][7] = 7;arcs_UDN[5][6] = 17;arcs_UDN[6][7] = 19;for (int i = 0; i < vexs_UDN.length; i++) // 构造⽆向图邻接矩阵for (int j = i; j < vexs_UDN.length; j++)arcs_UDN[j][i] = arcs_UDN[i][j];return new AdjMatGraph(GraphKind.UDN, vexs_UDN.length, arcsNum_UDN, vexs_UDN, arcs_UDN);}public static void main(String[] args) throws Exception {AdjMatGraph UDN_Graph = (AdjMatGraph) createUDNByYourHand_ForMiniSpanTree();MiniSpanTree_Prim.Prim(UDN_Graph, "V0");} 输出为:通过Prim算法得到的最⼩⽣成树序列为: {(V0-V1)(V0-V5)(V1-V8)(V8-V2)(V1-V6)(V6-V7)(V7-V4)(V7-V3)} 分析算法执⾏过程:从V0开始:-count为0,k为0,closeEdges数组的-lowCost为{0 10 INF INF INF 11 INF INF INF},adjVex数组为{V0,V0,V0,V0,V0,V0,V0,V0,V0}-⽐较lowCost,于是k为1,adjVex[1]为V0,minSpanTree[0]为(V0,V1),lowCost为{0 0 INF INF INF 11 INF INF INF}-k为1,与V1的权值⾏⽐较,得到新的-lowCost为:{0 0 18 INF INF 11 16 INF 12},adjVex数组为{V0,V0,V1,V0,V0,V0,V1,V0,V1}-⽐较lowCost,于是k为5,adjVex[5]为V0,minSpanTree[1]为(V0,V5),lowCost为{0 0 18 INF INF 0 16 INF 12}-k为5,与V5的权值⾏⽐较,得到新的-lowCost为{0 0 18 INF 26 0 16 INF 12},adjVex数组为{V0,V0,V1,V0,V5,V0,V1,V0,V1}-⽐较lowCost,于是k为8,adjVex[8]为V1,minSpanTree[2]为(V1,V8),lowCost为{0 0 18 INF INF 0 16 INF 0}... 三、克鲁斯卡尔(Kruskal)算法 1.Kruskal算法描述 Kruskal算法是根据边的权值递增的⽅式,依次找出权值最⼩的边建⽴的最⼩⽣成树,并且规定每次新增的边,不能造成⽣成树有回路,直到找到n-1条边为⽌。
Prim算法和Kruskal算法介绍
Prim算法和Kruskal算法介绍⼀、Prim算法普利姆(Prim)算法适⽤于求解⽆向图中的(Minimum Cost Spanning Tree)。
下⾯是Prim算法构造最⼩⽣成树的过程图解。
选择⼀个节点开始,⽐如V1进⼊集合U,剩下的集合的V-U包括剩下的节点,然后寻找从集合U到集合V-U最近的路径。
这⾥有三条路径分别是权重为6到V2,权重为5到V4以及权重为1到V3,显然到通过V3连接⽽集合U和集合V-U是最近的,选择V3进⼊集合U。
同样继续选择到V-U的路径,此时有6条可选路径,分别是权为6到V2【从V1】,权为5到V4【从V1】,权为5到V2【从V3】,权为5到V4【从V3】,权为6到V5【从V3】,权为4到V6【从V3】。
选择出从V3到V6的路径并将V6添加⾄集合U中。
按照这种⽅法依次将V4,V2和V5添加到集合U直到U和全体节点结合V相等,或者说V-U集合为空时结束,这时选出的n-1条边即为最⼩⽣成树。
⼆、Kruskal算法克鲁斯卡尔(Kruskal)算法是另⼀种求解最⼩⽣成树的算法。
下⾯是Kruskal算法构造最⼩⽣成树的过程图解。
Kruskal则是采取另⼀种思路,即从边⼊⼿。
⾸先n个顶点分别视为n个连通分量,然后选择⼀条权重最⼩的边,如果边的两端分属于两个连通分量,就把这个边加⼊集合E,否则舍去这条边⽽选择下⼀条代价最⼩的边,依次类推,直到所有节点都在同⼀个连通分量上。
三、对⽐假设⽹中有n个节点和e条边,普利姆算法的时间复杂度是O(n^2),克鲁斯卡尔算法的时间复杂度是O(eloge),可以看出前者与⽹中的边数⽆关,⽽后者相反。
因此,普利姆算法适⽤于边稠密的⽹络⽽克鲁斯卡尔算法适⽤于求解边稀疏的⽹。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
一、树及生成树的基本概念树是无向图的特殊情况,即对于一个N个节点的无向图,其中只有N-1条边,且图中任意两点间有且仅有一条路径,即图中不存在环,这样的图称为树,一般记为T。
树定义有以下几种表述:(1)、T连通、无圈、有n个结点,连通有n-1条边;(2)、T无回路,但不相邻的两个结点间联以一边,恰得一个圈;(3)、T连通,但去掉任意一边,T就不连通了(即在点集合相同的图中,树是含边数最少的连通图);(4)、T的任意两个结点之间恰有一条初等链。
例如:已知有六个城市,它们之间要架设电话线,要求任意两个城市均可以互相通话,并且电话线的总长度最短。
若用六个点v1…v6代表这六个城市,在任意两个城市之间架设电话线,即在相应的两个点之间连一条边。
这样,六个城市的一个电话网就作成一个图。
任意两个城市之间均可以通话,这个图必须是连通图,且这个图必须是无圈的。
否则,从圈上任意去掉一条边,剩下的图仍然是六个城市的一个电话网。
图5-6是一个不含圈的连通图,代表了一个电话线网。
生成树(支撑树)定义:如果图G’是一棵包含G的所有顶点的树,则称G’是G的一个支撑树或生成树。
例如,图5-7b是图5-7a的一个支撑树。
定理:一个图G有生成树的条件是G是连通图。
证明:必要性显然;充分性:设图G是连通的,若G不含圈,则按照定义,G是一个树,从而G是自身的一个生成树。
若G含圈,则任取G的一个圈,从该圈中任意去掉一条边,得到图G的一生成子图G1。
若G1不含圈,则G1是G的一个生成树。
若G1仍然含圈,则任取G1的一个圈,再从圈中任意去掉一条边,得到图G的一生成子图G2。
依此类推,可以得到图G的一个生成子图G K,且不含圈,从而G K是一个生成树。
寻找连通图生成树的方法:破圈法:从图中任取一个圈,去掉一条边。
再对剩下的图重复以上步骤,直到不含圈时为止,这样就得到一个生成树。
取一个圈(v1,v2,v3,v1),在一个圈中去掉边e3。
在剩下的图中,再取一个圈(v1,v2,v4,v3,v1),去掉边e4。
再从圈(v3,v4,v5,v3)中去掉边e6。
再从圈(v1,v2,v5,v4,v3,v1)中去掉边e7,这样,剩下的图不含圈,于是得到一个支撑树,如图所示。
避圈法:也称为生长法,从图中某一点开始生长边,逐步扩展成长为一棵树,每步选取与已入树的边不构成圈的那些边。
二、最小生成树概念:设G=(V,E)是无向连通带权图,即一个网络。
E中每条边(v,w)的权为c[v,w]。
所有生成树G’上各边权的总和最小的生成树称为G的最小生成树。
应用:如在设计通信网络时,用图的顶点表示城市,用边(v,w)的权c[v,w]表示建立城市v、w之间的通信线路所需的费用,则最小生成树就给出了建立通信网络最经济的方案。
性质:设G=(V,E)是连通带权图,U是V的真子集。
若(u,v)∈E,且u∈U,v∈V-U,且在所有这样的边中,(u,v)的权c[u,v]最小,那么一定存在G的一棵最小生成树,它以(u,v)为其中一条边。
这个性质也称为MST性质。
算法:经典方法有两种:kruskal、prim算法(贪心思想):一次生成一条最短边。
【Prim算法】:算法思想:任意时刻的中间结果都是一棵树,每次花费最小的代价,用一条边把不在树中的结点加进来。
按结点来贪,因此适用于稠密图的处理.算法内容:①设置顶点集合V和边集E,它们的初始状态为空集。
②任意选取一个顶点A加入V中。
③重复以下过程直到V中已经包含原图的所有节点:1、选一条权值最小的边(u,v),并使其满足u,v两节点只有一个在点集V中。
2、将两个节点中不在V的那个点加入集合V中,并将边(u,v)加入边集E中。
④所得的子图G’=(V,E)即为所求的最小生成树。
关键:找出当前最优得一条边,穷举每一条不在集合E中的边,找出符合条件且最优的边。
时间复杂度:O(V*E),即顶点数乘以边数。
代码:varn,i,j,k,min,sum:longint;a:array[1..1000,1..1000]of longint;b,d:array[1..1000]of longint;procedure prim;beginsum:=0;for i:=1 to n do d[i]:=a[1,i];for j:=2 to n dobeginmin:=maxlongint;for i:=1 to n doif (d[i]<min)and(d[i]<>0) thenbeginmin:=d[i]; k:=i;end;sum:=sum+d[k]; d[k]:=0;for i:=1 to n doif (a[k,i]<d[i])and(i<>k) then d[i]:=a[k,i];end;end;beginreadln(n);for i:=1 to n dofor j:=1 to n dobeginread(a[i,j]);if (i<>j) and (a[i,j]=0) then a[i,j]:=maxlongint;end;//初始化prim;writeln(sum);end.Prim算法:初始状态A包含任意一个顶点r,从r开始,每次都向A中添加一条连接树A和G=(V,A)中某个孤立顶点的轻边,直至生成树A包含了图中所有的顶点。
有效实现该算法的关键是设法较容易地选择一条轻边。
我们可以借助最小优先级队列。
图中的顶点可以分为两类,一类是在A中的,已经纳入最小生成树了,另一种是不在A 中的,记为B,对于这些顶点,我们需要保存它们与A中的某个顶点相连的边中的最小权值。
最小优先级队列保存的就是B(尚未纳入最小生成树)中的顶点以及它们与A中某个顶点相连的边中的最小权值。
每次队首出队,设新加入A的顶点为V,那么我们要修改V的邻接点中尚未在A中(在最小优先级队列)的且与A中顶点相连的边的最小权值(比较拗口)。
Prim+heapy优化:*优化:在选择权值最小的可行边时可以使用堆。
(nlogn) 堆优化的Prim算法适用于稀疏图,而不优化的Prim算法适用于稠密图。
代码:varn,i,j,k,sum:longint;a:array[0..1000,0..1000]of longint;b,d,heap,pos:array[0..10000]of longint;procedure swap(var i,j:longint);{交换整数i和j}//省略procedure heapify(p:longint);{向下调整堆的过程}var best:longint;beginbest:=p;//下面两个if是分别判断根和左、右孩子最短距离的大小if (p*2<=j-1) and (key[heap[p*2]]<key[heap[best]]) then best:=p*2;if (p*2+1<=j-1) and (key[heap[p*2+1]]<key[heap[best]]) then best:=p*2+1;if best<>p then//若根有所变动,即跟比左右孩子都大(最短距离)beginswap(pos[heap[p]],pos[heap[best]]);//互换节点heap[p]、heap[best]在堆的位置swap(heap[p],heap[best]);//互换堆中元素p、bestheapify(best);//继续调整互换后的元素bestend;end;procedure modify(id,new_d:longint);{判断new_d与d[id]大小,并修改key[id]大小}var p:longint;beginif (new_d<d[id]) thenbegin//修改d[id]:=new_d;//更新最短距离p:=pos[id];//结点id在堆中的位置while (p>1) and (key[heap[p]]<key[heap[p div 2]{父}]) do//向上调整beginswap(pos[heap[p]],pos[heap[p div 2]]);swap(heap[p],heap[p div 2]);p:=p div 2;//更上一层end;end;end;procedure extract(抽出)(var id:longint);{读取堆中最小元素的节点编号}beginid:=heap[1];//堆顶swap(pos[heap[1]],pos[heap[j]]);// 堆顶的元素和第j个元素换位置swap(heap[1],heap[j]);//把堆顶的元素扔到j后面去,heapify(1);//此时堆顶不一定是最小的~扔到下面去,把最小的搞上来。
end;procedure prim;begind[1]:=0;for j:=n downto 1 dobeginextract(k);sum:=sum+d[k];for i:=1 to n doif (pos[i]<j) then modify(i,a[k,i]);end;beginreadln(n);for i:=1 to n dofor j:=1 to n dobeginread(a[i,j]);if (i<>j) and (a[i,j]=0) then a[i,j]:=maxlongint;end;//初始化for i:=1 to n dobeginheap[i]:=i; pos[i]:=i; d[i]:=maxlongint;end;prim;writeln(sum);end.【Kruskal算法】算法内容:初始时把每个顶点看作一个集合①将所有边以长度为关键词从小到大排序。
②将每个顶点都加入一个集合中,即N个顶点共N个集合。
③设置边集E,初始状态为空。
④从小到大访问每条边,若边连接的两个顶点属于不同集合,则合并两个顶点所在的集合,并将该边加入到边集E中。
⑤所得的子图TG=(V,E)即为所求的最小生成树,其中顶点集V就是原图的所有顶点。
**关键:集合的合并。
我们可以采用路径压缩的算法,用树结构作为集合的结构,对于每个点只记录它的父亲,集合的代表元素即为树的根。
在判断两个节点是否属于同一集合时,递归查找节点所在树的根,同时压缩路径。
合并集合只需将集合B的根的父亲记为集合A 的根即可。
时间复杂度:O(eloge)x,j,tot,i,n:longint;l,a,b:array[1 ..10000] of longint;f:array[1 ..100] of longint;procedure qs(low,high:longint); //以每条边的长度排序;function find(x:longint):longint;//找集合的根结点、var tmp: longint;beginif f[x]=x then exit(x) else exit(find(f[x]));end;procedure union(u,v:longint); //合并子集varfu,fv:longint;beginfu:=find(u);fv:=find(v);f[fv]:=fu;end;procedure kruskal;varcnt,i,ans:longint;beginfor i:=1 to n do f[i]:=i;//初始子集,都是自己;cnt:=0; ans:=0;for i := 1 to tot doif (find(a)<>find(b)) then//判断是否属于同一子集beginunion(a,b);inc(ans); inc(cnt);if cnt=n-1 then break; //n个结点,连接n-1条边end;writeln(ans);end;begintot:= 0; readln(n);for i:=1 to n dofor j := 1 to n dobeginread(x);if (i<>j) thenbegininc(tot); a[tot]:= i; b[tot] := j; l[tot]:= x;end;end;qsort(1,tot);kruskal;end.1.Prim在稠密图中比Kruskal优,在稀疏图中比Kruskal劣。