(转)最小生成树之普利姆算法、克鲁斯卡尔算法

 最小生成树之prim算法

边赋以权值的图称为网或带权图,带权图的生成树也是带权的,生成树T各边的权值总和称为该树的权。

最小生成树(MST):权值最小的生成树。

生成树和最小生成树的应用:要连通n个城市需要n-1条边线路。可以把边上的权值解释为线路的造价。则最小生成树表示使其造价最小的生成树。

构造网的最小生成树必须解决下面两个问题:

1、尽可能选取权值小的边,但不能构成回路;

2、选取n-1条恰当的边以连通n个顶点;

MST性质:假设G=(V,E)是一个连通网,U是顶点V的一个非空子集。若(u,v)是一条具有最小权值的边,其中u∈U,v∈V-U,则必存在一棵包含边(u,v)的最小生成树。

1.prim算法

基本思想:假设G=(V,E)是连通的,TE是G上最小生成树中边的集合。算法从U={u0}(u0∈V)、TE={}开始。重复执行下列操作:

在所有u∈U,v∈V-U的边(u,v)∈E中找一条权值最小的边(u0,v0)并入集合TE中,同时v0并入U,直到V=U为止。

此时,TE中必有n-1条边,T=(V,TE)为G的最小生成树。

Prim算法的核心:始终保持TE中的边集构成一棵生成树。

注意:prim算法适合稠密图,其时间复杂度为O(n^2),其时间复杂度与边得数目无关,而kruskal算法的时间复杂度为O(eloge)跟边的数目有关,适合稀疏图。

看了上面一大段文字是不是感觉有点晕啊,为了更好理解我在这里举一个例子,示例如下:

(1)图中有6个顶点v1-v6,每条边的边权值都在图上;在进行prim算法时,我先随意选择一个顶点作为起始点,当然我们一般选择v1作为起始点,好,现在我们设U集合为当前所找到最小生成树里面的顶点,TE集合为所找到的边,现在状态如下:

U={v1}; TE={};

(2)现在查找一个顶点在U集合中,另一个顶点在V-U集合中的最小权值,如下图,在红线相交的线上找最小值。

通过图中我们可以看到边v1-v3的权值最小为1,那么将v3加入到U集合,(v1,v3)加入到TE,状态如下:

U={v1,v3}; TE={(v1,v3)};

(3)继续寻找,现在状态为U={v1,v3}; TE={(v1,v3)};在与红线相交的边上查找最小值。

我们可以找到最小的权值为(v3,v6)=4,那么我们将v6加入到U集合,并将最小边加入到TE集合,那么加入后状态如下:

U={v1,v3,v6}; TE={(v1,v3),(v3,v6)}; 如此循环一下直到找到所有顶点为止。

(4)下图像我们展示了全部的查找过程:

2.prim算法程序设计

(1)由于最小生成树包含每个顶点,那么顶点的选中与否就可以直接用一个数组来标记used[max_vertexes];(我们这里直接使用程序代码中的变量定义,这样也易于理解);当选中一个数组的时候那么就标记,现在就有一个问题,怎么来选择最小权值边,注意这里最小权值边是有限制的,边的一个顶点一定在已选顶点中,另一个顶点当然就是在未选顶点集合中了。我最初的一个想法就是穷搜了,就是在一个集合中选择一个顶点,来查找到另一个集合中的最小值,这样虽然很易于理解,但是很明显效率不是很高,在严蔚敏的《数据结构》上提供了一种比较好的方法来解决:设置两个辅助数组lowcost[max_vertexes]和closeset[max_vertexes],lowcost[max_vertexes]数组记录从U到V-U具有最小代价的边。对于每个顶点v∈V-U,closedge[v], closeset[max_vertexes]记录了该边依附的在U中的顶点。

注意:我们在考虑两个顶点无关联的时候设为一个infinity 1000000最大值。

说了这么多,感觉有点罗嗦,还是发扬原来的风格举一个例子来说明,示例如下:

过程如下表:顶点标号都比图中的小1,比如v1为0,v2为1,这里首先选择v1点。

 
Lowcost[0]


Lowcost[1]


Lowcost[2]


Lowcost[3]


Lowcost[4]


Lowcost[5]


U


V-U


closeset


v1,infinity


v1,6


v1,1


v1,5


v1,infinity


v1,infinity


v1


v1,v2,v3,v4,v5,v6

从这个表格可以看到依附到v1顶点的v3的Lowcost最小为1,那么选择v3,选择了之后我们必须要更新Lowcost数组的值,因为记录从U到V-U具有最小代价的边,加入之后就会改变。这里更新Lowcost和更新closeset数组可能有点难理解,

for (k=1;k<vcount;k++)
            if (!used[k]&&(G[j][k]<lowcost[k]))
                { lowcost[k]=G[j][k];
                closeset[k]=j; }
        }
j为我们已经选出来的顶点,如果G[j][k]<lowcost[k],则意味着最小权值边发生变化,更新该顶点的最小lowcost权值,依附的顶点肯定就是刚刚选出的顶点j,closeset[k]=j。

 
Lowcost[0]


Lowcost[1]


Lowcost[2]


Lowcost[3]


Lowcost[4]


Lowcost[5]


U


V-U


closeset


v1,infinity


v1,6


v1,1


v1,5


v3,6


v3,4


v1,v3


v1,v2,v4,v5,v6

这样一直选择下去直到选出所有的顶点。

(2)上面把查找最小权值的边结束了,但是这里有一个问题,就是我们没有存储找到的边,如果要求你输出找到的边那么这个程序就需要改进了,我们刚开始的时候选取的是v1作为第一个选择的顶点,那我们设置一个father[]数组来记录每个节点的父节点,当然v1的父节点肯定没有,那么我们设置一个结束标志为-1,每次找到一个新的节点就将它的父节点设置为他依附的节点,这样就可以准确的记录边得存储了。


语法:prim(Graph G,int vcount,int father[]);


参数:


G:


图,用邻接矩阵表示


vcount:


表示图的顶点个数


father[]:


用来记录每个节点的父节点


返回值:


null


注意:


常数max_vertexes为图最大节点数


常数infinity为无穷大

 
数组存储从0开始

 
如果下面的源程序有错请参照测试程序。


源程序:


#define infinity 1000000
#define max_vertexes 5

typedef int Graph[max_vertexes][max_vertexes];

void prim(Graph G,int vcount,int father[])
{
    int i,j,k;
    int lowcost[max_vertexes];

int closeset[max_vertexes],used[max_vertexes];

int min;
    for (i=0;i<vcount;i++)
        {

/* 最短距离初始化为其他节点到1号节点的距离 */
        lowcost[i]=G[0][i];

/* 标记所有节点的依附点皆为默认的1号节点 */

closeset[i]=0; 
        used[i]=0;
        father[i]=-1; 
        }
    used[0]=1;  /*第一个节点是在U集合里的*/

/* vcount个节点至少需要vcount-1条边构成最小生成树 */
    for (i=1;i<=vcount-1;i++)
        {
        j=0;

min = infinity;

/* 找满足条件的最小权值边的节点k */
        for (k=1;k<vcount;k++)

/* 边权值较小且不在生成树中 */
            if ((!used[k])&&(lowcost[k]<min))

{

min =  lowcost[k];

j=k;

}
        father[j]=closeset[j]; 
        used[j]=1;;//把第j个顶点并入了U中
        for (k=1;k<vcount;k++)

/* 发现更小的权值 */
            if (!used[k]&&(G[j][k]<lowcost[k]))
                {

lowcost[k]=G[j][k];/*更新最小权值*/
                  closeset[k]=j;;/*记录新的依附点*/

}
        }
}

测试程序:

测试用例:

1 2 6

1 3 1

1 4 5

2 3 5

2 5 3

3 4 5

3 5 6

3 6 4

5 6 6

4 6 2

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#define infinity 1000000

#define max_vertexes 6

typedef int Graph[max_vertexes][max_vertexes];

void prim(Graph G,int vcount,int father[])

{

int i,j,k;

int lowcost[max_vertexes];

int closeset[max_vertexes],used[max_vertexes];

int min;

for (i=0;i<vcount;i++)

{

/* 最短距离初始化为其他节点到1号节点的距离 */

lowcost[i]=G[0][i];

/* 标记所有节点的依附点皆为默认的1号节点 */

closeset[i]=0;

used[i]=0;

father[i]=-1;

}

used[0]=1; /*第一个节点是在s集合里的*/

/* vcount个节点至少需要vcount-1条边构成最小生成树 */

for (i=1;i<=vcount-1;i++)

{

j=0;

min = infinity;

/* 找满足条件的最小权值边的节点k */

for (k=1;k<vcount;k++)

/* 边权值较小且不在生成树中 */

if ((!used[k])&&(lowcost[k]<min))

{

min =  lowcost[k];

j=k;

}

father[j]=closeset[j];

printf("%d %d\n",j+1,closeset[j]+1);//打印边

used[j]=1;;//把第j个顶点并入了U中

for (k=1;k<vcount;k++)

/* 发现更小的权值 */

if (!used[k]&&(G[j][k]<lowcost[k]))

{

lowcost[k]=G[j][k];/*更新最小权值*/

closeset[k]=j;;/*记录新的依附点*/

}

}

}

int main()

{

FILE *fr;

int i,j,weight;

Graph G;

int fatheer[max_vertexes];

for(i=0; i<max_vertexes; i++)

for(j=0; j<max_vertexes; j++)

G[i][j] = infinity;

fr = fopen("prim.txt","r");

if(!fr)

{

printf("fopen failed\n");

exit(1);

}

while(fscanf(fr,"%d%d%d", &i, &j, &weight) != EOF)

{

G[i-1][j-1] = weight;

G[j-1][i-1] = weight;

}

prim(G,max_vertexes,fatheer);

return 0;

}

程序结果:

3 1

6 3

4 6

2 3

5 2

请按任意键继续. . .

最小生成树之kruskal算法

1.kruskal算法

假设连通网N=(V,{E})。则令最小生成树的初始状态为只有n个顶点而无边的非连通图T=(V,{}),图中每个顶点自成一个连通分量。在E中选择最小代价的边,若该边依附的顶点落在T中不同的连通分量中,则将该边加入到T中,否则舍去此边而选择下一条代价最小的边,依次类推,直到T中所有顶点都在同一连通分量上为止。

示例如下:

图中先将每个顶点看作独立的子图,然后查找最小权值边,这条边是有限制条件的,边得两个顶点必须不在同一个图中,如上图,第一个图中找到最小权值边为(v1,v3),且满足限制条件,继续查找到边(v4,v6),(v2,v5),(v3,v6),当查找到最后一条边时,仅仅只有(v2,v3)满足限制条件,其他的如(v3,v4),(v1,v4)都在一个子图里面,不满足条件,至此已经找到最小生成树的所有边。

2.kruskal算法程序设计

由于我们要查找边权值最小的边,那么我们的第一步应该把边权值排序,这样就可以很快查找到权值最小的边,为了简化程序设计,我们不使用其他的数据结构,仅仅设立一个结构体数组来存储边,用一个标记数组来标记哪些边已经被选择(实际程序设计的时候没有用到标记数组);

解决了边得存储和排序问题,现在是算法最关键的就是怎么判断边的两个顶点不在一个子图里面,一个简单的办法是设立一个辅助数组f[n],初始化如下:

void Initialize()

{

int i;

for(i=0; i<n;i++)

f[i] = i;

}

如此初始化是为了让每个顶点各自为一个图,如果找到一条边(i,j)那么做如下标记:(i<j)

void Mark_same(int i, int j)

{

//找到i的父节点

while(f[i] != i)

{

i= f[i];

}

f[j] = i;//将j指向其父节点

}

上面的标记过程也给了我们怎么判断两个顶点是否在一个图中找到了方法,即判断其父节点是否相同,相同则是在一个图里;

int Is_same(int i, int j)

{

//找到i的父节点

while(f[i] != i)

{

i= f[i];

}

//找到i的父节点

while(f[j] != j)

{

j= f[j];

}

return i == j ? 1 : 0;

}

注意:实际设计程序的时候不用标记已选边,因为已选择的边会讲端点集合合并为一个集合,从而在判断是否为同一集合的时候就可以排除了。

测试用例:

Kruskal.txt

0 1 6

0 2 1

0 3 5

1 2 5

1 4 3

2 3 5

2 4 6

2 5 4

4 5 6

3 5 2

测试程序:

#include <stdio.h>

#include <stdlib.h>

#define MAX 100

#define N 6//顶点数目

/* 定义边(x,y),权为w */

typedef struct

{

int x,y;

int w;

}edge;

edge e[MAX];

/* father[x]表示x的父节点 */

int father[N];

/* 比较函数,按权值(相同则按x坐标)非降序排序 */

int cmp(const void *a, const void *b)

{

if ((*(edge *)a).w == (*(edge *)b).w)

{

return (*(edge *)a).x - (*(edge *)b).x;

}

return (*(edge *)a).w - (*(edge *)b).w;

}

/* 判断集合是否相同 */

int Is_same(int i, int j)

{

//找到i的父节点

while(father[i] != i)

{

i =  father[i];

}

//找到i的父节点

while(father[j] != j)

{

j = father[j];

}

return i == j ? 1 : 0;

}

/* 合并x,y所在的集合 */

void Mark_same(int i, int j)

{

int temp;

if(i > j)

{

temp = i;

i = j;

j = temp;

}

//找到i的父节点

while(father[i] != i)

{

i= father[i];

}

father[j] = i;//将j指向其父节点

}

//初始化 father数组

void Initialize()

{

int i;

for(i=0; i<N;i++)

father[i] = i;

}

/* 主函数 */

int main()

{

int i = 0,j, n;

int x, y;

FILE *fr;

fr = fopen("kruskal.txt","r");

if(!fr)

{

printf("fopen failed\n");

exit(1);

}

/* 读取边信息并初始化集合 */

while(fscanf(fr,"%d %d %d", &e[i].x, &e[i].y, &e[i].w) != EOF)

i++;

/* 将边排序 */

qsort(e, i, sizeof(edge), cmp);

Initialize();

for (i = 0; i < N; i++)

{

if(!Is_same(e[i].x, e[i].y))

{

printf("%d %d\n",e[i].x+1, e[i].y+1);

Mark_same(e[i].x, e[i].y);

}

}

system("pause");

return 0;

}

程序结果:

1 3

4 6

2 5

3 6

1 4

2 3

请按任意键继续. . .

(转)最小生成树之普利姆算法、克鲁斯卡尔算法,布布扣,bubuko.com

时间: 2024-08-27 01:01:59

(转)最小生成树之普利姆算法、克鲁斯卡尔算法的相关文章

数据结构(五) 普利姆与克鲁斯卡尔的最小生成树(Swift面向对象版)

上篇博客我们聊了图的物理存储结构邻接矩阵和邻接链表,然后在此基础上给出了图的深度优先搜索和广度优先搜索.本篇博客就在上一篇博客的基础上进行延伸,也是关于图的.今天博客中主要介绍两种算法,都是关于最小生成树的,一种是Prim算法,另一个是Kruskal算法.这两种算法是很经典的,也是图中比较重要的算法了. 今天博客会先聊一聊Prim算法是如何生成最小生成树的,然后给出具体步骤的示例图,最后给出具体的代码实现,并进行测试.当然Kruskal算法也是会给出具体的示例图,然后给出具体的代码和测试用例.当

c/c++ 用普利姆(prim)算法构造最小生成树

c/c++ 用普利姆(prim)算法构造最小生成树 最小生成树(Minimum Cost Spanning Tree)的概念: ? 假设要在n个城市之间建立公路,则连通n个城市只需要n-1条线路.这时,自然会考虑,如何在最节省经费的前提下建立这个公路网络. ? 每2个城市之间都可以设置一条公路,相应地都要付出一定的经济代价.n个城市之间,最多可以设置n(n-1)/2条线路,那么,如何在这些可能的线路中选择n-1条,以使总的耗费最少? 普利姆(prim)算法的大致思路: ? 大致思想是:设图G顶点

最小生成树(普利姆算法、克鲁斯卡尔算法)

给定一个加权无向连通图,如何选择一个生成树,使权利的最小总和的边缘所有树,叫最小生成树. 求最小生成树算法 (1) 克鲁斯卡尔算法 图的存贮结构採用边集数组,且权值相等的边在数组中排列次序能够是随意的.该方法对于边相对照较多的不是非常有用,浪费时间. (2) p=1313">普里姆算法 图的存贮结构採用邻接矩阵.此方法是按各个顶点连通的步骤进行,须要用一个顶点集合,開始为空集,以后将以连通的顶点陆续增加到集合中,所有顶点增加集合后就得到所需的最小生成树 . 以下来详细讲下: 克鲁斯卡尔算法

图的最小生成树(普利姆prim算法)

什么是生成树呢? 一个连通图的生成树是指一个极小连通子图, 它含有图中的全部顶点,但只有足以构成一棵树的n-1条边. 什么是最小生成树? 在一个连通图的所有生成树中,各边的代价之和最小的那棵生成树称为该连通图的最小代价生成树(MST), 简称最小生成树. 求最小生成树有两种算法,本文讲prim算法. 简略证明 使用反证法证明 设一棵最小生成树T不包含最短边a,将a加入最小生成树T中,书中必定构成一个包含a的回路,而回路中必定有边比a大(因a为最短边),则删除比a大的边得到一棵比原先T更小的树T1

普里姆算法,克鲁斯卡尔算法,迪杰斯特拉算法,弗洛里德算法

做数据结构的课程设计顺便总结一下这四大算法,本人小白学生一枚, 如果总结的有什么错误,希望能够告知指正 普里姆算法如图所示prim 找出最短的边,再以这条边构成的整体去寻找与之相邻的边,直至连接所有顶点,生成最小生成树,时间复杂度为O(n2) 克鲁斯卡尔算法如图所示kruskal 克鲁斯卡尔算法,假设连通网N=(N,{E}),则令最小生成树的初始状态为只有n个顶点而无边的非连通图T=(V,{}),图中每个顶点 自成一个连通分量.在E中选择代价最小的边,若该边依附的定顶点落在T中不同的连通分量上,

最小生成树--克鲁斯卡尔算法(Kruskal)

按照惯例,接下来是本篇目录: $1 什么是最小生成树? $2 什么是克鲁斯卡尔算法? $3 克鲁斯卡尔算法的例题 摘要:本片讲的是最小生成树中的玄学算法--克鲁斯卡尔算法,然后就没有然后了. $1 什么是最小生成树? •定义: 先引入一个定理:N个点用N-1条边连接成一个联通块,形成的图形只可能是树,没有别的可能: 根据这个定理,我们定义:在一个有N个点的图中,选出N-1条边出来,连接所有N个点,这N-1条边的边权之和最小的方案: •最小生成树之prim算法:   由于本蒟蒻还不会这个算法,所以

普利姆算法(最小生成树)

int prim(){ int minid, i, j; double mincost; for(i = 2; i <= n; i ++){ lowcost[i] = map[1][i]; } lowcost[1] = -1; for(i = 2; i <= n; i ++){ mincost = INF; minid = 0; for(j = 2; j <= n; j ++){ if(lowcost[j] < mincost && lowcost[j] >

最小生成树(MST)----普里姆(Prim)算法与克鲁斯卡尔(Kruskal)算法

1.概念:给定一个带权的无向连通图,如何选取一棵生成树,使树上所有边上权的总和为最小,这叫最小生成树. 2.应用:例如:要在n个城市之间铺设光缆,主要目标是要使这 n 个城市的任意两个之间都可以通信,但铺设光缆的费用很高,且各个城市之间铺设光缆的费用不同,因此另一个目标是要使铺设光缆的总费用最低.这就需要找到带权的最小生成树. 3.求最小生成树的算法 3.1 普里姆(Prim)算法 方法:从指定顶点开始将它加入集合中,然后将集合内的顶点与集合外的顶点所构成的所有边中选取权值最小的一条边作为生成树

最小生成树——普利姆 克鲁斯卡尔

最小生成树 生成树定义:是原图的一个极小连通子图,含有原图的全部顶点,但只有n-1条边.它连通但边只有n-1,也就是说任意让两点连边必定成环,不过这结论好像没啥用. 最小生成树:对于一张图的生成树可能有多种,对于边权和最小的一种就是最小生成树了. prim算法 首先首先,我们来几个标识,原图是N={V,E},TE是N上最小生成树的边集,然后有个已选点集合U,那么未选点集就是V-U了. 首先,先来个起点,加入到U里面. 然后,在所以边(a,b),a属于U,b属于V-U里面,找一条权值最小的边(a0