普里姆Prim算法 - 图解最小生成树

我们在图的定义中说过,带有权值的图就是网结构。一个连通图的生成树是一个极小的连通子图,它含有图中全部的顶点,但只有足以构成一棵树的n-1条边。所谓的最小成本,就是n个顶点,用n-1条边把一个连通图连接起来,并且使得权值的和最小。综合以上两个概念,我们可以得出:构造连通网的最小代价生成树,即最小生成树(Minimum Cost Spanning Tree)。

找连通图的最小生成树,经典的有两种算法,普里姆算法和克鲁斯卡尔算法,这里介绍普里姆算法。

为了能够讲明白这个算法,我们先构造网图的邻接矩阵,如图7-6-3的右图所示。

也就是说,现在我们已经有了一个存储结构为MGraph的MG(见《邻接矩阵创建图》)。MG有9个顶点,它的二维数组如右图所示,数组中我们使用65535代表无穷。

下面我们对着程序和每一步循环的图示来看:

算法代码:(改编自《大话数据结构》)

C++ Code


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

 
/* Prim算法生成最小生成树  */

void MiniSpanTree_Prim(MGraph MG)

{

int min, i, j, k;

int adjvex[MAXVEX];/* 保存相关顶点下标 */

int lowcost[MAXVEX];/* 保存相关顶点间边的权值 */

lowcost[0] = 0;/* 初始化第一个权值为0,即v0加入生成树 */

/* lowcost的值为0,在这里就是此下标的顶点已经加入生成树 */

adjvex[0] = 0;/* 初始化第一个顶点下标为0 */

cout << "最小生成树的边为:" << endl;

for (i = 1; i < MG.numVertexes; i++)

{

lowcost[i] = MG.arc[0][i];/* 将v0顶点与之有边的权值存入数组 */

adjvex[i] = 0;/* 初始化都为v0的下标 */

}

for (i = 1; i < MG.numVertexes; i++)

{

min = INFINITY; /* 初始化最小权值为∞, */

j = 1;

k = 0;

while (j < MG.numVertexes)/* 循环全部顶点 */

{

if (lowcost[j] != 0 && lowcost[j] < min)/* 如果权值不为0且权值小于min */

{

min = lowcost[j];/* 则让当前权值成为最小值 */

k = j;/* 将当前最小值的下标存入k */

}

j++;

}

cout << "(" << adjvex[k] << ", " << k << ")" << "  "; /* 打印当前顶点边中权值最小的边 */

lowcost[k] = 0;/* 将当前顶点的权值设置为0,表示此顶点已经完成任务 */

for (j = 1; j < MG.numVertexes; j++)/* 循环所有顶点 */

{

/* 如果下标为k顶点各边权值小于此前这些顶点未被加入生成树权值 */

if (lowcost[j] != 0 && MG.arc[k][j] < lowcost[j])

{

lowcost[j] = MG.arc[k][j];/* 将较小的权值存入lowcost相应位置 */

adjvex[j] = k;/* 将下标为k的顶点存入adjvex */

}

}

}

cout << endl;

}

1、程序中1~16行是初始化操作,其中第7~8行 adjvex[0] = 0 意思是现在从顶点v0开始(事实上从那一点开始都无所谓,假定从v0开始),lowcost[0]= 0 表示v0已经被纳入到最小生成树中,之后凡是lowcost数组中的值被设为0就表示此下标的顶点被纳入最小生成树。

2、第11~15行表示读取邻接矩阵的第一行数据,所以 lowcost数组为{ 0 ,10, 65535, 65535, 65535, 11, 65535, 65535, 65535 },而adjvex数组为全0。至此初始化完毕。

3、第17~49行共循环了8次,i从1一直累加到8,整个循环过程就是构造最小生成树的过程。

4、第24~33行,经过循环后min = 10, k = 1。注意26行的if 判断lowcost[j] != 0 表示已经是生成树的顶点则不参加最小权值的查找。

5、第35行,因k = 1, adjvex[1] = 0, 所以打印结果为(0, 1),表示v0 至 v1边为最小生成树的第一条边,如下图的第一个小图。

6、第36行,因k = 1 将lowcost[k] = 0 就是说顶点v1纳入到最小生成树中,此时lowcost数组为{ 0,0, 65535, 65535, 65535, 11, 65535, 65535, 65535 }

7、第38~47行,j 循环从1 到8, 因k = 1,查找邻接矩阵的第v1行的各个权值,与lowcost数组对应值比较,若更小则修改lowcost值,并将k值存入adjvex数组中。所以最终lowcost = { 0,0, 18, 65535, 65535, 11, 16, 65535, 12 }。 adjvex数组的值为 {0, 0, 1, 0, 0, 0, 1, 0,
1 }。这里的if判断也表示v0和v1已经是生成树的顶点不参与最小权值的比对了。

上面所述为第一次循环,对应下图i = 1的第一个小图,由于要用文字描述清楚整个流程比较繁琐,下面给出i为不同值一次循环下来后的生成树图示,所谓一图值千言,大家对着图示自己模拟地循环8次就能理解普里姆算法的思想了。

即最小生成树的边为:(0, 1), (0, 5), (1, 8), (8, 2), (1, 6), (6, 7), (7, 4), (7, 3)

最后再来总结一下普里姆算法的定义:

假设N = (V{E} )是连通网,TE是N上最小生成树的集合。算法从U = { u0} ( uo V),TE = { } 开始。重复执行下述操作:在所有

uU,v V - U 的边(u, v) E 中找一条代价最小的边(u0 , v0)
并入集合TE, 同时v0 并入U, 直至 U = V 为止。此时TE 中必有n-1 条边, 则 T = (V,{TE} ) 为N的最小生成树。

由算法代码中的循环嵌套可得知此算法的时间复杂度为O(n^2)。

对比普里姆和克鲁斯卡尔算法,克鲁斯卡尔算法主要针对边来展开,边数少时效率比较高,所以对于稀疏图有较大的优势;而普里姆算法对于稠密图,即边数非常多的情况下更好一些。

原文地址:https://www.cnblogs.com/alantu2018/p/8471741.html

时间: 2024-10-02 03:37:31

普里姆Prim算法 - 图解最小生成树的相关文章

普里姆(Prim)算法

1 /* 2 普里姆算法的主要思想: 3 利用二维数组把权值放入,然后找在当前顶点的最小权值,然后走过的路用一个数组来记录 4 */ 5 # include <stdio.h> 6 7 typedef char VertexType;//定义顶点类型 8 typedef int EdgeType;//定义边上的权值类型 9 # define MAX_VERTEX 100//最大顶点个数 10 # define INFINITY 65535//用65535代表无穷大 11 12 typedef

图的最小生成树之普里姆Prim算法

源代码如下: #include<iostream> using namespace std; #define MAX_VERTEX_NUM 20 #define infinity 9 typedef int QElemType; typedef int EdgeData; typedef char VertexData; typedef struct { VertexData verlist[MAX_VERTEX_NUM]; //顶点表 EdgeData edge[MAX_VERTEX_NUM

图的生成树(森林)(克鲁斯卡尔Kruskal算法和普里姆Prim算法)、以及并查集的使用

图的连通性问题:无向图的连通分量和生成树,所有顶点均由边连接在一起,但不存在回路的图. 设图 G=(V, E) 是个连通图,当从图任一顶点出发遍历图G 时,将边集 E(G) 分成两个集合 T(G) 和 B(G).其中 T(G)是遍历图时所经过的边的集合,B(G) 是遍历图时未经过的边的集合.显然,G1(V, T) 是图 G 的极小连通子图,即子图G1 是连通图 G 的生成树. 深度优先生成森林   右边的是深度优先生成森林: 连通图的生成树不一定是唯一的,不同的遍历图的方法得到不同的生成树;从不

数据结构之---C语言实现最小生成树之prim(普里姆)算法

//最小生成树之Prim算法 //杨鑫 #include <stdio.h> #include <stdlib.h> #define n 6 #define MaxNum 10000 /*定义一个最大整数*/ /*定义邻接矩阵类型*/ typedef int adjmatrix[n + 1][n + 1]; /*0号单元没用*/ typedef struct { int fromvex, tovex; //生成树的起点和终点 int weight; //边的权重 }Edge; ty

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

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

图-&gt;连通性-&gt;最小生成树(普里姆算法)

文字描述 用连通网来表示n个城市及n个城市间可能设置的通信线路,其中网的顶点表示城市,边表示两城市之间的线路,赋于边的权值表示相应的代价.对于n个定点的连通网可以建立许多不同的生成树,每一棵生成树都可以是一个通信网.现在,我们要选择这样一个生成树,使总的耗费最少.这个问题就是构造连通网的最小代价生成树(Minimum Cost Spanning Tree: 最小生成树)的问题.一棵生成树的代价就是树上各边的代价之和. 有多种算法可以构造最小生成树,其他多数都利用的最小生成的MST(minimum

普里姆算法介绍

普里姆(Prim)算法,和克鲁斯卡尔算法一样,是用来求加权连通图的最小生成树的算法. 基本思想 对于图G而言,V是所有顶点的集合:现在,设置两个新的集合U和T,其中U用于存放G的最小生成树中的顶点,T存放G的最小生成树中的边. 从所有u?U,v?(V-U) (V-U表示出去U的所有顶点)的边中选取权值最小的边(u, v),将顶点v加入集合U中,将边(u, v)加入集合T中,如此不断重复,直到U=V为止,最小生成树构造完毕,这时集合T中包含了最小生成树中的所有边. 普里姆算法图解 以上图G4为例,

普里姆算法与修路问题

应用场景-修路问题 看一个应用场景和问题: 有胜利乡有7个村庄(A, B, C, D, E, F, G) ,现在需要修路把7个村庄连通 各个村庄的距离用边线表示(权) ,比如 A – B 距离 5公里 问:如何修路保证各个村庄都能连通,并且总的修建公路总里程最短? 最小生成树 修路问题本质就是就是最小生成树问题, 先介绍一下最小生成树(Minimum Cost Spanning Tree),简称MST. 给定一个带权的无向连通图,如何选取一棵生成树,使树上所有边上权的总和为最小,这叫最小生成树

最小生成树 Prim(普里姆)算法和Kruskal(克鲁斯特尔)算法

Prim算法 1.概览 普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树.意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (graph theory)),且其所有边的权值之和亦为最小.该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克(英语:Vojtěch Jarník)发现:并在1957年由美国计算机科学家罗伯特·普里姆(英语:Robert C. Prim)独立发现:1959年,艾兹格·迪科斯彻再次发现了该算法.因此,在某些场