Kruskal算法和Prim算法
的最小生成树算法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算法)最⼩⽣成树的性质: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数组记录各点的连通分量),则将其添加到最⼩⽣成树中。
4-3贪心-Prim和Kruskal算法
基本思想:在保证连通的前提下依次选出权重较小的n –1条边。
G=(V, E)为无向连通带权图,令V={1, 2, …, n}。
设置一个集合S ,初始化S = {1},T = Φ。
贪心策略:如果V–S中的顶点j与S中的某个点i连接且(i, j)是E中的权重最小的边,于是就选择j(将j加入S),并将(i, j) 加入T中。
重复执行贪心策略,直至V–S为空。
图用连接矩阵C[i][j]给出,即C[i][j]为结点i到结点j的权重。
为了有效地找出V–S中满足与S中的某个点i连接且(i, j) 权重最小的顶点j,对其中的每个顶点j设立两个数组closest[j]和lowcost[j]:closest[j]是s中与j最近的顶点,(closest[j], j)即为选中的边,而lowcost[j]是相应边的权重。
•Prim(int n, Type **c) {初始化:结点1放入S ;并初始化lowcost[]和closest[];执行以下操作n–1次:依据lowcost[]找出与S 最近的点j 并放入S ;调整lowcost[]和closest[];}int j = 1; s[j] = true; for (int i = 2; i <= n; i++) { closest[i] = 1; lowcost[i]=c[1][i]; s[i]=false;}for (int i = 1; i < n; i++) {min= inf; for (int k = 2; k <= n; k++) {if (lowcost[k]<min&&!s[k]){min = lowcost[k]; j = k} s[j] = true;s 中仅加入了一个新成员j ,因此只需要依据结点j 调整lowcost[]和closest[];for (int k = 2; k <= n; k++) {if (c[j][k]< lowcost[k]&&!s[k]){lowcost[k] = c[j][k]; closest[k] = j}}}给定一个连通带权图如下:1655536624 初始时S={1},T= Φ;1第一次选择:(1, 3)S={1, 3}T= {(1, 3)} ;3第二次选择:(3, 6)S={1, 3, 6},T= {(1, 3), (3, 6)} ;6第三次选择:(6, 4)S={1, 3, 6, 4},T= {(1, 3), (3, 6), (6, 4)} ;4第四次选择:(2, 3)S={1, 3, 6, 4, 2},(2, 3)} ;2 第五次选择:∵(5, 2)权最小∴S={1, 3, 6, 4, 2, 5},T= {(1, 3), (3, 6), (6, 4),(3, 2) (2, 5)} ;5基本思想:在保证无回路的前提下依次选出权重较小的n –1条边。
最小生成树(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算法的优劣
最小生成树算法比较Prim和Kruskal算法的优劣在图论中,最小生成树(Minimum Spanning Tree, MST)是指一个连通图的生成树,它的所有边的权值之和最小。
最小生成树算法是解决最小生成树问题的常用方法,而Prim算法和Kruskal算法是两种经典的最小生成树算法。
本文将比较Prim算法和Kruskal算法的优劣,为读者提供更全面的了解。
一、Prim算法Prim算法是一种贪心算法,通过逐步扩展生成树的方式来构建最小生成树。
Prim算法以一个初始节点开始,然后逐渐添加与当前生成树相连的最短边,直到生成树包含图中的所有节点为止。
以下是Prim算法的基本步骤:1. 选择任意一个节点作为初始节点,并将其加入生成树中。
2. 从生成树的节点中选择一个最短边,并将与该边相连的节点加入生成树。
3. 重复步骤2,直到生成树中包含所有节点。
相比于Kruskal算法,Prim算法在每一步只考虑一个节点,并且每次选择最短边,因此Prim算法的时间复杂度为O(V^2),其中V是图中的节点数。
二、Kruskal算法Kruskal算法也是一种贪心算法,通过按照边的权值递增的顺序来构建最小生成树。
Kruskal算法从图中所有边中选择最短边,并将其加入生成树中,同时保证生成树不形成环,直到生成树中包含所有节点为止。
以下是Kruskal算法的基本步骤:1. 对图中的所有边按照权值进行排序。
2. 依次遍历排序后的边,将权值最小的边加入生成树中,并检查是否形成环。
3. 重复步骤2,直到生成树中包含所有节点。
Kruskal算法中的关键步骤是判断是否形成环,可以使用并查集数据结构来实现。
Kruskal算法的时间复杂度为O(ElogE),其中E是图中的边数。
三、Prim算法与Kruskal算法的比较1. 时间复杂度:Prim算法的时间复杂度为O(V^2),而Kruskal算法的时间复杂度为O(ElogE)。
由于E通常小于V^2,所以Kruskal算法在大多数情况下更快。
最小生成树算法详解
最小生成树算法详解最小生成树(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算法通过并查集来判断两个顶点是否属于同一个连通分量,从而避免形成环。
prim和克鲁斯卡尔算法
prim和克鲁斯卡尔算法
Prim算法和Kruskal算法是两类思想完全不同的最小生成树(MST)算法,它们分别具有独特的优缺点。
Prim算法采用贪心策略和建模方法,被认为是一种比较容易实现的方法。
在每一步中,它会选择离已经选择的节点最近的未选择节点作为新的节点,然后根据具体实施情况选择一条最短的边连接这两个节点。
换句话说,Prim算法根据节点之间的距离以局部选择的方式构建最小生成树。
Kruskal算法根据边的权重选择最小生成树,它着重于整体最优,是一种比较复杂但更有效的方法。
它会将所有节点以及它们之间的边按照权重排序,然后每次从最短边中选择一条边,直到所有节点都被连接。
这种算法有时也称作贪心策略,它通过每一步中查找最短边来尽量减少最终最小生成树的总费用。
由于Prim算法主要的重点是局部最优,它比较容易实现,因此是比较常用的一种算法,而Kruskal算法则着重于整体最优,它的实施通常需要非常复杂的算法,但最终的结果更加优秀。
要想确定适合给定特定问题的最佳算法,一般来说,必须对这两种算法进行详细分析,以便选择更有效的算法实施,并确保最小生成树构建的总成本最低。
最小生成树---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年发表。
最小支撑树的三种算法
最小支撑树的三种算法
最小支撑树(Minimum Spanning Trees,MST)是指在一个加权连通图上,具
有最小权值的连通图,它是网络规划的重要部分。
它有三种求解算法,即Prim算法,Kruskal算法,和Bor邻接算法。
Prim算法是一种求最小支撑树的常用算法,它以点的形式进行遍历,以邻接
矩阵描述网络结构,选取其中权值最小的边,一步步向外扩展,直到构造出支撑树。
它的优势在于简单易行,但有一个缺点就是以某个点为起始点,其他点没有参与进来,因此最终的结果受到起始点的影响较大。
Kruskal算法是另一种经典的算法,它从权值最小的边开始往外展开,直到所
有点都包括在支撑树内,将构造的最小支撑树的边连接起来,使其构成一个循环结构,这样就可以避免出现单拐点,它的缺点是算法耗时较长,在大型图结构中效率也比较低。
Bor邻接算法是最近出现的一种算法,它结合了Prim和Kruskal算法的优势,可以在短时间内生成支撑树,并且不存在多个拐点的问题,使用实用性也很强。
但是,由于Bor邻接算法是较新的方法,相比Prim和Kruskal算法,目前尚未产生
比较多的研究和评价,在某些情况下仍可能会导致支撑树的生成错误。
总的来说,三种最小支撑树的求解算法都有各自的优势和缺点,从实际应用来看,根据要求的精度和计算速度的不同,可以灵活的采用相应的方法来有效解决问题。
Prim算法和Kruskal算法
Prim算法和Kruskal算法Prim算法和Kruskal算法都能从连通图找出最小生成树。
区别在于Prim算法是挨个找,而Kruskal是先排序再找。
一、Prim算法:Prim算法实现的是找出一个有权重连通图中的最小生成树,即:具有最小权重且连接到所有结点的树。
(强调的是树,树是没有回路的)。
Prim算法是这样来做的:首先以一个结点作为最小生成树的初始结点,然后以迭代的方式找出与最小生成树中各结点权重最小边,并加入到最小生成树中。
加入之后如果产生回路则跳过这条边,选择下一个结点。
当所有结点都加入到最小生成树中之后,就找出了连通图中的最小生成树了。
Prim算法最小生成树查找过程:注意:若候选轻边集中的轻边不止一条,可任选其中的一条扩充到T中。
连通网的最小生成树不一定是惟一的,但它们的权相等。
【例】在上图(e)中,若选取的轻边是(2,4)而不是(2,1)时,则得到如图(h)所示的另一棵MST。
算法特点该算法的特点是当前形成的集合T始终是一棵树。
将T中U和TE分别看作红点和红边集,V-U看作蓝点集。
算法的每一步均是在连接红、蓝点集的紫边中选择一条轻边扩充进T中。
MST性质保证了此边是安全的。
T从任意的根r开始,并逐渐生长直至U=V,即T 包含了C中所有的顶点为止。
MST性质确保此时的T是G的一棵MST。
因为每次添加的边是使树中的权尽可能小,因此这是一种"贪心"的策略。
算法分析该算法的时间复杂度为O(n2)。
与图中边数无关,该算法适合于稠密图。
算法演示:/sjjg/DataStructure/DS/web/flashhtml/prim.htm二、Kruskal算法:Kruskal算法与Prim算法的不同之处在于,Kruskal在找最小生成树结点之前,需要对所有权重边做从小到大排序。
将排序好的权重边依次加入到最小生成树中,如果加入时产生回路就跳过这条边,加入下一条边。
当所有结点都加入到最小生成树中之后,就找出了最小生成树。
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),可以看出前者与⽹中的边数⽆关,⽽后者相反。
因此,普利姆算法适⽤于边稠密的⽹络⽽克鲁斯卡尔算法适⽤于求解边稀疏的⽹。
Kruskal算法与Prim算法
Kruskal算法与Prim算法(话说这么些算法的名字嗯。
Kruskal算法:(下⽂它就暂时叫k算法吧k算法是⼀种应⽤贪⼼思想及并查集,在图中查找最⼩⽣成树的算法。
(最⼩⽣成树:若⼀个⽆向图内任意两个顶点联通切图为⼀棵树,此图即为⽣成树。
在带权值⽆向图中权值和最⼩的⽣成树即为最⼩⽣成树。
时间复杂度:O(mlogm)//m为边数算法思路:1,读⼊图中各个边,将所有边权值从⼩到⼤排列加⼊集合S;2,将图看作⼀个森林,初始每个顶点都为⼀棵独⽴的树;3,从S中取出当前最短边(u,v),若选择这条边不会与其它已选边构成回路,则选择这条边并将u,v加⼊已知⽣成树的集合中;4,重复上述操作直到⽣成树集合中包含所有点。
1 #include<bits/stdc++.h>2using namespace std;3const int N=5002,M=2e5+2;4int n,m;5int f[N],ans,cnt;6struct qwq{7int u,v,w;8 }e[M];9bool cmp(qwq x,qwq y){return x.w<y.w;}10int getf(int x){//并查集11if(f[x]==x) return x;12return f[x]=getf(f[x]);13 }14int main(){15 cin>>n>>m;16for(int i=1;i<=n;i++) f[i]=i;17for(int i=1;i<=m;i++) cin>>e[i].u>>e[i].v>>e[i].w;18 sort(e+1,e+m+1,cmp);19int i;20for(i=1;i<=m;i++){21int fu=getf(e[i].u),fv=getf(e[i].v);22if(fu==fv) continue;//若已有共同祖先,再选当前边会形成回路23 f[fu]=fv;24 ans+=e[i].w;25 cnt++;//记录连边条数26if(cnt==n-1||cnt==m) break;//emm蒟蒻也不知道cnt==m是⼲啥的不加板⼦也能过27 }28if(i!=m+1) cout<<ans;29else cout<<"orz";30return0;31 }Prim算法:Prim算法也是应⽤贪⼼思想,⽤于⽆向图中求最⼩⽣成树的算法,与k算法的区别在于Prim算法从点出发,⽽k算法从边出发。
普里姆算法和克鲁斯卡尔算法
普里姆算法和克鲁斯卡尔算法普里姆算法和克鲁斯卡尔算法介绍在图论中,最小生成树是一种重要的概念。
最小生成树是指在一个加权连通图中,找到一棵生成树,使得所有边的权值之和最小。
其中,Prim算法和Kruskal算法是两种常用的求解最小生成树的方法。
Prim算法Prim算法是一种贪心算法,它从一个顶点开始构建最小生成树。
具体实现过程如下:1. 选取任意一个顶点作为起始点,并将其标记为已访问。
2. 遍历与该顶点相邻的所有边,并将这些边加入一个优先队列中。
3. 从优先队列中选取一条权值最小的边,并将与之相邻的未被访问过的顶点标记为已访问。
4. 重复步骤2和3,直到所有顶点都被访问过。
代码实现```def prim(graph, start):visited = set()visited.add(start)edges = []min_span_tree_cost = 0while len(visited) != len(graph.keys()):candidate_edges = []for node in visited:for edge in graph[node]:if edge[1] not in visited:candidate_edges.append(edge)sorted_edges = sorted(candidate_edges, key=lambda x:x[2]) min_edge = sorted_edges[0]edges.append(min_edge)min_span_tree_cost += min_edge[2]visited.add(min_edge[1])return edges, min_span_tree_cost```时间复杂度Prim算法的时间复杂度为O(ElogV),其中E为边数,V为顶点数。
因为每次需要从优先队列中选取一条权值最小的边,所以需要使用堆来实现优先队列。
的最小生成树算法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和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
求无向图的最小生成树算法——Prim与Kruskal一.Prim算法1.算法思想对于图G=(V,E),用Prim算法求最小生成树T=(S,TE)的流程如下① 初始化:设S、TE为空集,任选节点K加入S。
② 选取一条权值最小的边(X,Y),其中X∈S,且not (Y∈S)即,选取一条权值最小的、连接着S中一点与S外一点的边。
将Y加入S中,边(X,Y)加入TE中重复② 直到V=S即所有G中的点都在S中,此时的T为G的最小生成树。
由此流程可见,Prim算法求最小生成树时任何时候的T都是一颗树。
2.实现显然,Prim算法的主要运行时间花在过程②的选边中。
看起来复杂度是O(VE)=O(V^3)不是么,效率也太低了吧……为了比较快速地选边,我们用两个数组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。
设出发点为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】每一次找出lowcost中不为0的最小值lowcost[i],然后把i加入S(即lowcost[i]:=0),然后对于图中所有点k,若w(k,i)<lowcost[k],则把lowcost[k]赋为w(k,i),把closest[k]赋为i。
【由于s中所有点的lowcost都为0,所以只影响到s以外的点】以上操作重复|V|-1次结束。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Kruskal算法和Prim算法
本节所阐述的两种最小生成树算法是上节所介绍的一般算法的细化。
每个算法均采用一特定规则来确定GENERIC-MST算法第3行所描述的安全边;在Kruskal
算法中,集合A是一森林,加大集合A中的安全边总是图中连结两不同连通支的最小权边。
在Prim算法中,集合A仅形成单棵树。
添加入集合A的安全边总是连结树与一非树结点的最小权边。
Kruskal算法
Kruskal算法是直接基于上一节中给出的一般最小生成树算法的基础之上的。
该算法找出森林中连结任意两棵树的所有边中具有最小权值的边(u,v)作为安全边,并把它添加到正在生长的森林中。
设C1和C2表示边(u,v)连结的两棵树。
因为(u,v)必是连C1和其他某棵树的一条轻边,所以由推论2可知(u,v)对C1是安全边。
Kruskal算法同时也是一种贪心算法,因为算法每一步添加到森林中的边的权值都尽可能小。
Kruskal算法的实现类似于计算连通支的算法。
它使用了分离集合数据结构以保持数个互相分离的元素集合。
每一集合包含当前森林中某个树的结点,操作FIND-SET(u)返回包含u的集合的一个代表元素,因此我们可以通过FIND-SET(v)来确定两结点u和v是否属于同一棵树,通过操作UNION来完成树与树的联结。
MST-KRUSKAL(G,w)
1. A←∅
2. for 每个结点v V[G]
3. do MAKE-SET(v)
4. 根据权w的非递减顺序对E的边进行排序
5. for 每条边(u,v)∈E,按权的非递减次序
6. do if FIND-SET(u) ≠ FIND-SET(v)
7. then A←A∪{(u,v)}
8. UNION(u,v)
9 return A
Kruskal算法的工作流程如图4所示。
阴影覆盖的边属于正在生成的森林A。
算法按权的大小顺序考察各边。
箭头指向算法每一步所考察到的边。
第1-3行初始化集合A为空集并建立|V|棵树,每裸树包含图的一个结点。
在第4行中根据其权值非递减次序对E的边进行排序。
在第5-8行的for循环中首先检查对每条边(u,v)其端点u和v是否属于同一棵树。
如果是,则把(u,v)加入森林就会形成回路,所以这时放弃边(u,v)。
如果不是,则两结点分属不同的树,由第7行把边加入集合A中,第8行对两棵树中的结点进行归并。
(a)
(b)
(c)
(d)
(e)
(f)
(g) (h)
(i)
(j)
(k)
(l)
(m) (n)
图4 Kruskal算法在图1所示的图上的执行流程
Kruskal算法在图G=(V,E)上的运行时间取决于分离集合这一数据结构如何实现。
我们采用在分离集合中描述的按行结合和通路压缩的启发式方法来实现分离集合森林的结构,这是由于从渐近意义上来说,这是目前所知的最快的实现方法。
初始化需占用时间O(V),第4行中对边进行排序需要的运行时间为O(E㏒E);对分离集的森林要进行O(E)次操作,总共需要时间为O(Eα(E,V)),其中α函数为Ackerman函数的反函数。
因为α(E,V)=O(㏒E),所以KruskaI算法的全部运行时间为O(E㏒E)。
Prim算法
正如Kruskal算法一样,Prim算法也是第上一节中讨论的一般最小生成树算法的特例。
Prim算法的执行非常类似于寻找图的最短通路的Dijkstra算法。
Prim 算法的特点是集合A中的边总是只形成单棵树。
如图5所示,阴影覆盖的边属于正在生成的树,树中的结点为黑色。
在算法的每一步,树中的结点确定了图的一个割,并且通过该割的轻边被加进树中。
树从任意根结点r开始形成并逐渐生长直至该树跨越了V中的所有结点。
在每一步,连接A中某结点到V-A中某结点的轻边被加入到树中,由推论2,该规则仅加大对A安全的边,因此当算法终止时,A中的边就成为一棵最小生成树。
因为每次添加到树中的边都是使树的权尽可能小的边,因此上述策略也是贪心的。
有效实现Prim算法的关键是设法较容易地选择一条新的边添加到由A的边所形成的树中,在下面的伪代码中,算法的输入是连通图G和将生成的最小生成树的根r。
在算法执行过程中,不在树中的所有结点都驻留于优先级基于key域的队列Q中。
对每个结点v,key[v]是连接v到树中结点的边所具有的最小权值;按常规,若不存在这样的边则key[v]=∞。
域π[v]说明树中v的“父母”。
在算法执行中,GENERIC-MST的集合A隐含地满足:
A={(v,π[v])|v∈V-{r}-Q}
当算法终止时,优先队列Q为空,因此G的最小生成树A满足:
A={(v,π[v])|v∈ V-{r}}
(a) (b)
(c) (d)
(e)
(f)
(g)
(h)
(i)
图5 Prim算法在图1所示的图上的执行流程
MST-PRIM(G,w,r)
1. Q←V[G]
2. for 每个u∈Q
3. do key[u]←∞
4. key[r]←0
5. π[r]←NIL
6. while Q≠∅
7. do u←EXTRACT-MIN(Q)
8. for 每个v∈Adj[u]
9. do if v∈Q and w(u,v)<key[v]
10. then π[v]←u
11. key[v]←w(u,v)
Prim算法的工作流程如图5所示。
第1-4行初始化优先队列Q使其包含所有结点,置每个结点的key域为∞(除根r以外),r的key域被置为0。
第5行初始化π[r]的值为NIL,这是由于r没有父母。
在整个算法中,集合V-Q包含正在生长的树中的结点。
第7行识别出与通过割(V-Q,Q)的一条轻边相关联的结点u∈Q(第一次迭代例外,根据第4行这时u=r)。
从集合Q中去掉u后把它加入到树的结点集合V-Q中。
第8-11行对与u邻接且不在树中的每个结点v的key 域和π域进行更新,这样的更新保证key[v]=w(v,π[v])且(v,π[v])是连接v到树中某结点的一条轻边。
Prim算法的性能取决于我们如何实现优先队列Q。
若用二叉堆来实现Q,我们可以使用过程BUILD-HEAP来实现第1-4行的初始化部分,其运行时间为O(V)。
循环需执行|V|次,且由于每次EXTRACT-MIN操作需要O(㏒V)的时间,所以对EXTRACT-MIN的全部调用所占用的时间为O(V㏒V)。
第8-11行的for循环总共要执行O(E)次,这是因为所有邻接表的长度和为2|E|。
在for循环内部,第9行对队列Q的成员条件进行测试可以在常数时间内完成,这是由于我们可以为每个结点空出1位(bit)的空间来记录该结点是否在队列Q中,并在该结点被移出队列时随时对该位进行更新。
第11行的赋值语句隐含一个对堆进行的DECREASE-KEY操作,该操作在堆上可用0(㏒V)的时间完成。
因此,Prim算法的整个运行时间为O(V㏒V+E㏒V)=O(E㏒V),从渐近意义上来说,它和实现Kruskal 算法的运行时间相同。
通过使用Fibonacci堆,Prim算法的渐近意义上的运行时间可得到改进。
在Fibonacci堆中我们己经说明,如果|V|个元素被组织成Fibonacci堆,可以在O(㏒V)的平摊时间内完成EXTRACT-MIN操作,在O(1)的平摊时间里完成DECREASE-KEY操作(为实现第11行的代码),因此,若我们用Fibonacci堆来实现优先队列Q,Prim算法的运行时间可以改进为O(E+V㏒V)。