C++ 最小生成树之Prim(普里姆)算法

原创 2017年07月15日 11:29:29
最小生成树之Prim(普里姆)算法

最小生成树:是在一个给定的无向图G(V,E)中求一棵树T,使得这棵树拥有图G中的所有顶点,且所有边都是来自图G中的边,并且满足整棵树的边权之和最小。

如上图给出了一个图G及其最小生成树T,其中红色的线即为最小生成树的边。最小生成树T包含了图G中所有的顶点,且由它们生成的树的边权之和为15,是所有生成树中权值最小的。
最小生成树有3个性质:
(1)最小生成树是树,因此其边数等于定点数减1,且树内一定不会有环;
(2)对给定的图G(V,E),其最小生成树可以不唯一,但是其边权之和一定是唯一的;
(3)由于最小生成树是无向图上生成的,因此其根结点可以是这棵树上的任意一个结点。

Prim算法
Prim算法是用来解决最小生成树问题的。
基本思想:对图G(V,E)设置集合S,存放已经被访问的顶点,然后每次从集合V-S中选择与集合S的最短距离最小的一个顶点(记为u),访问并加入集合S。之后,令顶点u为中介点,优化所有从u能到达的顶点v与集合S之间的最短距离。这样的操作执行n次(n为顶点个数),直到集合S已包含所有顶点。
注意:可以发现,prim算法的思想和最短路径中Dijkstra算法的思想几乎完全相同,只是在涉及最短距离时使用了集合S代替Dijkstra算法中的起点s。
下面举例来说明一下prim是如何求最小生成树的。
假设从顶点V0开始,当前集合V={V0,V1,V2,V3,V4,V5}(蓝色),集合S={}(黄色),顶点V0与集合S之间的距离为0,其它均为INF(一个很大的数)
(1)如图(a),选择与集合S距离最小的顶点V0,将其加入到集合S中,并连接顶点V0与其它顶点的边,此时集合V={V1,V2,V3,V4,V5},集合S={V0};
(2)如图(b),选择与集合S距离最小的顶点V4,将其加入到集合S中,并连接顶点V4与其它顶点的边,此时集合V={V1,V2,V3,V5},集合S={V0,V4};
(3)如图(c),选择与集合S距离最小的顶点V5,将其加入到集合S中,并连接顶点V5与其它顶点的边,此时集合V={V1,V2,V3},集合S={V0,V4,V5};
(4)如图(d),选择与集合S距离最小的顶点V1,将其加入到集合S中,并连接顶点V1与其它顶点的边,此时集合V={V2,V3},集合S={V0,V1,V4.V5};
(5)如图(e),选择与集合S距离最小的顶点V3,将其加入到集合S中,并连接顶点V3与其它顶点的边,此时集合V={V2},集合S={V0,V1,V3,V4,V5};
(6)如图(f),最后选择顶点V2,将其加入到集合S中,此时集合V={},集合S={V0,V1,V2,V3,V4,V5};
此时集合S已经包含了所有的顶点,算法结束。



Prim算法流程:
对图G(V,E)设置集合S,存放已经被访问的顶点,然后执行n次下面的两个步骤(n为顶点个数):
  1. 每次从集合V-S中选择与集合S的最短距离最小的一个顶点(记为u),访问并加入集合S,同时把这条离集合最近的边加入到最小生成树中。
  2. 令顶点u为中介点,优化所有从u能到达的未访问的顶点v与集合S之间的最短距离。
具体实现:
prim算法需要实现两个关键的概念,即集合S的实现,顶点Vi(0<=i<=n-1)与集合S的最短距离。
  • 集合S的实现使用一个bool型数组vis[]表示顶点是否已经被访问,其中vis[i]=true表示顶点Vi已被访问,vis[i]=false则表示顶点Vi未被访问;
  • 令int型数组d[]来存放顶点Vi与集合S的最短距离。初始时除了起点s的d[s]=0,其余顶点赋值为一个很大的数来表示INF,即不可达。
伪代码如下:
//G为图,数组d为顶点与集合S的最短距离
Prim(G, d[]){
     初始化;
     for(循环n次){
          u = 使d[u]最小的未被访问的顶点的标号;
          记u已被访问;
          for(从u出发能到达的所有顶点v){
               if(v未被访问 && 以u为中介点使得v与集合S的最短距离d[v]更优){
                    将G[u][v]赋值给v与集合S的最短距离d[v]; 
               } 
          } 
     }
}

具体代码实现:
邻接矩阵版:
const int INF = 1000000000;

/*Prim算法求无向图的最小生成树,返回最小生成树的边权之和*/
int Prim(int n, int s, vector<vector<int>> G, vector<bool>& vis, vector<int>& d)
{
       /*
       param
       n:                                     顶点个数
       s:                                     初始点
       G:                                     图的邻接矩阵
       vis:                                   标记顶点是否已被访问
       d:                                     存储顶点与集合S的最短距离
       return:                                最小生成树的边权之和
       */
       fill(d.begin(), d.end(), INF);                                        //初始化最短距离,全部为INF
       d[s] = 0;                                                             //初始点与集合S的距离为0
       int sum = 0;                                                          //记录最小生成树的边权之和
       for (int i = 0; i < n; ++i)
       {
              int u = -1;                                                    //u使得d[u]最小
              int MIN = INF;                                                 //记录最小的d[u]
              for (int j = 0; j < n; ++j)                                    //开始寻找最小的d[u]
              {
                     if (vis[j] == false && d[j] < MIN)
                     {
                           MIN = d[j];
                           u = j;
                     }
              }
              //找不到小于INF的d[u],则剩下的顶点与集合S不连通
              if (u == -1)
                     return -1;
              vis[u] = true;                                                  //标记u为已访问
              sum += d[u];                                                    //将与集合S距离最小的边加入到最小生成树
              for (int v = 0; v < n; ++v)
              {
                     //v未访问 && u能够到达v && 以u为中介点可以使v离集合S更近
                     if (vis[v] == false && G[u][v] != INF && G[u][v] < d[v])
                           d[v] = G[u][v];                                    //更新d[v]
              }
       }
       return sum;                                                            //返回最小生成树的边权之和
}

邻接表版
const int INF = 1000000000;
struct Node
{
       int v;
       int dis;
       Node(int x, int y):v(x),dis(y){}
};

int Prim(int n, int s, vector<vector<Node>> Adj, vector<bool>& vis, vector<int>& d)
{
       /*
       param
       n:                       顶点个数
       s:                       初始点
       Adj:                     图的邻接表
       vis:                     标记顶点是否被访问
       d:                       存储起点s到其他顶点的最短距离
       return:                   最小生成树的边权之和
       */
       fill(d.begin(), d.end(), INF);                                //初始化最短距离,全部为INF
       d[s] = 0;                                                     //初始点与集合S的距离为0
       int sum = 0;                                                  //记录最小生成树的边权之和
       for (int i = 0; i < n; ++i)
       {
              int u = -1;                                            //u使得d[u]最小
              int MIN = INF;                                         //记录最小的d[u]
              for (int j = 0; j < n; ++j)                            //开始寻找最小的d[u]
              {
                     if (vis[j] == false && d[j] < MIN)
                     {
                           MIN = d[j];
                           u = j;
                     }
              }
              //找不到小于INF的d[u],则剩下的顶点与集合S不连通
              if (u == -1)
                     return -1;
              vis[u] = true;                                          //标记u为已访问
              sum += d[u];                                            //将与集合S距离最小的边加入到最小生成树
              for (int j = 0; j < Adj[u].size(); ++j)
              {
                     int v = Adj[u][j].v;
                     if (vis[v] == false && Adj[u][j].dis < d[v])
                           d[v] = Adj[u][j].dis;                      //更新d[v]
              }
       }
       return sum;
}

测验上面的例子代码如下:
int main()
{
	int n = 6;
	/*邻接矩阵*/
	//vector<vector<int>> G = { {0,4,INF,INF,1,2},
	//											{4,0,6,INF,INF,3},
	//											{INF,6,0,6,INF,5},
	//											{INF,INF,6,0,4,5},
	//											{1,INF,INF,4,0,3},
	//											{2,3,5,5,3,0} };

	/*邻接表*/
	vector<vector<Node>> Adj = { {Node(4,1),Node(5,2),Node(1,4)},
	{Node(0,4),Node(5,3),Node(2,6)},
	{Node(1,6),Node(3,6),Node(5,5)},
	{Node(2,6),Node(4,4),Node(5,5)},
	{Node(0,1),Node(5,3),Node(3,4)},
	{Node(0,2),Node(1,3),Node(2,5),Node(3,5),Node(4,3)} };
	/*for (auto x : Adj)
	{
		for (auto y : x)
			cout << y.v<<"-"<<y.dis << "  ";
		cout << endl;
	}*/
	vector<bool> vis(n);
	vector<int> d(n);
//	int res = Prim(n, 0, G, vis, d);				//邻接矩阵版
	int res = Prim1(n, 0, Adj, vis, d);		//邻接表版
	cout << res << endl;

	return 0;
}
运行结果如下:

时间复杂度为O(n^2),只与图中顶点个数有关,与边数无关,因此prim算法适用于稠密图

版权声明:本文为博主原创文章,未经博主允许不得转载。

[C++]最小生成树--Prim算法&Kruskal算法

最小生成树–Prim算法&Kruskal算法 一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。最小生成树可以用kruskal(克...
  • stary_yan
  • stary_yan
  • 2016年05月16日 20:24
  • 8029

最小生成树:普里姆算法.c++

普里姆算法简易步骤: 辅助数组closedge记录从U到V-U具有最小权值的边,其中lowcost储存最小边上的权值,adjvex储存最小边在U中的那个顶点数组的定义:struct{ VerTe...
  • sinat_39253550
  • sinat_39253550
  • 2017年06月28日 11:45
  • 315

最小生成树之Prim算法C++实现

Prim算法的基本思路: 将图中的所有的顶点分为两类:树顶点(已经被选入生成树的顶点)和非树顶点(还未被选入生成树的顶点)。首先选择任意一个顶点加入生成树,接下来要找出一条边添加到生成树,这需要...
  • u012577585
  • u012577585
  • 2015年04月16日 14:38
  • 1726

用Kruska和Prim算法求最小生成树完整代码

  • 2016年06月03日 23:50
  • 505KB
  • 下载

利用普利姆算法求最小生成树

  • 2010年01月06日 18:56
  • 2KB
  • 下载

Prim算法——求无向图的最小生成树

1212 无向图最小生成树 基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题  收藏  关注 N个点M条边的无向连通图,...
  • qq_27717967
  • qq_27717967
  • 2015年12月27日 21:54
  • 638

80386的分段机制、分页机制和物理地址的形成

MOVE REG,ADDR ; 它把地址为ADDR(假设为10000)的内存单元的内容复制到REG 中 在8086 的实模式下,把某一段寄存器(段基址)左移4 位,然后与地址ADDR 相加后被直...
  • Simba888888
  • Simba888888
  • 2013年09月15日 19:50
  • 3108

2015全国硕士研究生计算机考研真题(附答案)

2015年全国硕士研究生计算机考研真题及答案一、单项选择题:140小题,每小题2分,共80分。下列每题给出的四个选项中,只有一个选项符合题目要求。请在答题卡上将所选项的字母涂黑。 1.已知程序如下:...
  • xuqingbo6686
  • xuqingbo6686
  • 2016年04月18日 11:38
  • 1240

C++ 最小生成树之kruskal(克鲁斯卡尔)算法

最小生成树之kruskal(克鲁斯卡尔)算法 kruskal算法:同样解决最小生成树的问题,和prim算法不同,kruskal算法采用了边贪心的策略,思想要比prim算法简单。 关于prim算...
  • YF_Li123
  • YF_Li123
  • 2017年07月16日 08:44
  • 898

最小生成树之Prim(普里姆)算法

最小生成树之Prim(普里姆)算法
  • u012965373
  • u012965373
  • 2015年08月11日 15:18
  • 1339
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:C++ 最小生成树之Prim(普里姆)算法
举报原因:
原因补充:

(最多只允许输入30个字)