贪心算法之最小生成树(Prim算法)

问题描述

假设现有7个村庄,需要在村庄之间架设电缆。在保证每一个村庄都有电缆链接的前提下,总的电缆长度最小。
该问题用无向连通图 G = ( V , E ) G=(V,E) G=(V,E)来表示电缆链接网络, V V V表示顶点集, E E E表示边集。把各个村庄抽象为图中的顶点,顶点与顶点之间的边表示村庄之间电缆网络,边的权值表示两个村庄之间的电缆费用。如果两个顶点之间没有连线,代表两个村庄之间架设电缆,费用为无穷大。对应 n n n个顶点只需要 n − 1 n-1 n1条边就可以使这个图连通。只要必须无回路才能保证 n − 1 n-1 n1条边连通。
所以,我们只需找到 n − 1 n-1 n1条权值和最小并且无回路的边即可。
强烈推荐小白下载这个APP,快速搞懂算法运行流程。
算法动态图解:链接:https://pan.baidu.com/s/1mX3s7VjLTKLr7MZhAQO-6Q
提取码:cv5y
相关概念:

  • 子图:从子图中选出一些顶点和边组成的图,称为原图的子图。
  • 生成子图:选出一些边和所有顶点组成的图,称为原图的生成子图。
  • 生成树:如果生成子图恰好是一棵树(无回路),称为生成树。
  • 最小生成树:权值之和最小的生成树,称为最小生成树。
    这几个概念的范围是逐渐递减的。

算法流程

算法关键的两点是无回路连通权值最小,权值最小想到的是我们的贪心算法,在选择下一个村庄的时候,我们都会基于当前选出架设电缆长度最短的村庄。无回路连通这里采用的是集合的概念, A A A集合和 B B B集合之间点连接的边,取出集合之间连接的边就能够防止回路连通。因为只能集合内的点连接会导致回路。所有的顶点所在的集合称为 V V V集合,权值最小的边关联的节点所在的集合称为 U U U集合,剩下的顶点为 V − U V-U VU集合。

  • 1.初始化
  • 2.找到两个集合之间的最短边
  • 3.加入到 U U U集合
  • 4.更新集合之间的边连接
  • 5.重复2-4之间的步骤直到 n − 1 n-1 n1条边连接。

源代码

#include <iostream>
using namespace std;

const int INF = 0x3fffffff;
const int N = 100;
bool s[N];
int closest[N];
int lowcost[N];
void Prim(int n, int u0, int c[N][N])
{
    
        
	//如果s[i]=true,说明顶点i已加入最小生成树
	//的顶点集合U;否则顶点i属于集合V-U
	//将最后的相关的最小权值传递到数组lowcost
	s[u0] = true; 
	int i;
	int j;
	for (i = 1; i <= n; i++)
	{
    
    
		if (i != u0)
		{
    
    
			lowcost[i] = c[u0][i];
			closest[i] = u0;
			s[i] = false;
		}
		else
			lowcost[i] = 0;
	}
	//2.找到两个集合之间的最短边
	for (i = 1; i <= n; i++)
	{
    
    
		int temp = INF;
		int t = u0;
		for (j = 1; j <= n; j++)
		{
    
    
			if ((!s[j]) && (lowcost[j] < temp))
			{
    
    
				t = j;
				temp = lowcost[j];
			}
		}
		//3.加入到$U$集合
		if (t == u0)
			break;       //找不到t,跳出循环
		s[t] = true;   

		// 4.更新集合之间的边连接
		for (j = 1; j <= n; j++) //更新lowcost和closest
		{
    
    
			if ((!s[j]) && (c[t][j] < lowcost[j]))
			{
    
    
				lowcost[j] = c[t][j];
				closest[j] = t;
			}
		}
	}
}

int main()
{
    
    

	//1.初始化邻接矩阵,村庄之间的距离值
	int n, c[N][N], m, u, v, w;
	int u0;
	cout << "输入结点数n和边数m:" << endl;
	cin >> n >> m;
	int sumcost = 0;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
			c[i][j] = INF;
	cout << "输入结点数u,v和边值w:" << endl;
	for (int i = 1; i <= m; i++)
	{
    
    
		cin >> u >> v >> w;
		c[u][v] = c[v][u] = w;
	}
	cout << "输入任一结点u0:" << endl;
	cin >> u0;
	//计算最后的lowcos的总和,即为最后要求的最小的费用之和
	Prim(n, u0, c);
	cout << "数组lowcost的内容为" << endl;
	for (int i = 1; i <= n; i++)
		cout << lowcost[i] << " ";
	cout << endl;
	for (int i = 1; i <= n; i++)
		sumcost += lowcost[i];
	cout << "最小的花费是:" << sumcost << endl;
	return 0;
}

在这里插入图片描述

复杂度分析

P r i m Prim Prim算法中一共有4个for循环,其中第二个for循环为双重循环嵌套。故时间复杂度为 O ( n 2 ) O(n^2) O(n2)
算法所需要的辅助空间包括 i , j , l o w c o s t 和 c l o s e s t i,j,lowcost和closest i,j,lowcostclosest,故空间复杂度为 O ( n ) O(n) O(n)
老汤建议:测试代码前先用笔画出算法图和计算流程能够更容易理解代码哦!

猜你喜欢

转载自blog.csdn.net/weixin_42662358/article/details/100046128
今日推荐