学渣都能看懂的-最小生成树Prim算法(普里姆算法)

最小生成树问题:要将图中的n个点连通,即任意两点之间都存在路径。要使连通图中各边的权值之和最小。
例:
学渣都能看懂的-最小生成树Prim算法(普里姆算法)_第1张图片
最小生成树所要掌握的点:
·求出最小生成树权值之和
·保存最小生成树的各边

使各边权值之和最短需要满足两个条件:
·图中的边数最少
·图中的边的权值尽量小

图中的边数最少:
连接n个点最少需要n-1条边,由反证法可知:若任意增加1条边,图都将形成回路。回路中任意减去1条边都会使图中的各边权值之和减少。故最小生成树中图中的边数为n-1条。
图中的边的权值尽量小:
学渣都能看懂的-最小生成树Prim算法(普里姆算法)_第2张图片
(最小生成树问题不是一味地连接权值最小的边(否则会构成回路),而是在保证不构成回路的情况下保证选择的边的权值最小。)

Prim算法的构造过程简单的来说就是:
从小到大构建最小生成树
从1个结点的最小生成树到2个结点的最小生成树再到3个结点的最小生成树……到n个结点的最小生成树。(即构造过程中的每一步已连接过的结点都能构成最小生成树)

Prim算法的构造实现简单的来说就是:
不停地在已经连接过的结点与未连接过的结点之间连接权值最小的边。
例:学渣都能看懂的-最小生成树Prim算法(普里姆算法)_第3张图片
(A、B、C三点已连接,D、E、F三点未连接,在已经连接过的结点与未连接过的结点之间权值最小的边为AE,故连接AE)

已知:
已连接的点构成的图构成一棵最小生成树

可以用反证法证明Prim算法:
假设新连接的点与旧连接的点中权值最小的边不在最小生成树中。
此时将权值最小的边加入生成树中,那么必然会构成一个回路,去掉回路中权值最大的边,构成一个新的树,这时与假设构成矛盾。所以权值最小的边一定在最小生成树中。

学渣都能看懂的-最小生成树Prim算法(普里姆算法)_第4张图片(连接BE后再连接AE,则A-B-E间形成回路,此时去掉BE才是最小生成树。)

#include
#include
int v[100][100], tree[100][100], visited[100], min[100], next[100];
/*v数组为原始的邻接矩阵,tree数组为最小生成树的邻接矩阵
visited数组判断是否结点是否已连接,min数组为各已连接结点与未连接结点的权值最小的边,
next数组储存各已连接结点与未连接结点的权值最小的边对应的未连接结点*/
int vnum, arcnum, sum = 0;//vnum为顶点数,arcnum为边数,sum为权值之和
void buildtree()
{
	for (int i = 1; i <= vnum; i++)//初始化min数组,使其为一个到不可能的数字
		min[i] = 100000;

	visited[1] = 1;//这里为了方便,从结点1开始建立最小生成树
	for (int i = 1; i <= vnum; i++)//找出结点1的权值最小的边,与该边对应的结点
	{
		if (v[1][i] && min[1] > v[1][i] && !visited[i])//存在边且权值较小且未连接过
		{
			min[1] = v[1][i];
			next[1] = i;
		}
	}

	for (int i = 1; i <= vnum - 1; i++)//连接n-1条边
	{
		int min_arc = 10000, min_i, next_i;
		//储存在已经连接过的结点与未连接过的结点之间连接权值最小的边对应的两个结点与权值
		for (int i = 1; i <= vnum; i++)//寻找已经连接过的结点与未连接过的结点之间连接权值最小的边
		{
			if (visited[i] && min_arc > min[i])
			{
				min_i = i;
				min_arc = min[i];
				next_i = next[i];
			}
		}
		sum = sum + min_arc;//相加
		visited[next_i] = 1;//将未连接点连接
		tree[min_i][next_i] = min_arc;//构造最小生成树
		tree[next_i][min_i] = min_arc;

		for (int i = 1; i <= vnum; i++)//更新已连接的点中next为新连接点的点的next
		{
			if (next[i] == next_i)
			{
				min[i] = 10000;
				for (int j = 1; j <= vnum; j++)
				{
					if (min[i] > v[i][j] && !visited[j] && v[i][j])
					{
						min[i] = v[i][j];
						next[i] = j;
					}
				}
			}
		}

		for (int i = 1; i <= vnum; i++)//记录新连接点与未连接点中的权值最小的信息,类似上面代码对点1的操作
		{
			if (min[next_i] > v[next_i][i] && !visited[i] && v[next_i][i])//存在边且权值较小且未连接过
			{
				min[next_i] = v[next_i][i];
				next[next_i] = i;
			}
		}
	}
}

int main()
{
	scanf_s("%d %d", &vnum, &arcnum);//输入顶点数和边数
	int v1, v2, w;//v1,v2为顶点,w为权值
	for (int i = 1; i <= arcnum; i++)//输入边
	{
		scanf_s("%d %d %d", &v1, &v2, &w);
		v[v1][v2] = w;
		v[v2][v1] = w;
	}
	buildtree();//构造最小生成树

	//下面为测试代码,可自行删除
	for (int i = 1; i <= vnum; i++)
	{
		for (int j = 1; j <= vnum; j++)
		{
			printf("%d ", tree[i][j]);
		}
		printf("\n");
	}//输出最小生成树的邻接矩阵
	printf("%d\n", sum);//输出权值的和
}

代码说明:

for (int i = 1; i <= vnum; i++)//更新已连接的点中next为新连接点的点的next
		{
			if (next[i] == next_i)
			{
				min[i] = 10000;
				for (int j = 1; j <= vnum; j++)
				{
					if (min[i] > v[i][j] && !visited[j] && v[i][j])
					{
						min[i] = v[i][j];
						next[i] = j;
					}
				}
			}
		}

当连接了权值最小的边后会连接新连接点,此时已连接的点若权值最小的边指向该新连接点,则需要重置该旧连接点的权值最小边。学渣都能看懂的-最小生成树Prim算法(普里姆算法)_第5张图片
结点1连接结点2后,结点1连接结点3,此时结点2的next指向3,此时结点3已经被连接,故要更新结点2的next。
推荐免费课程:[浙江大学-数据结构-Prim]
(看前面8分钟就好了)

你可能感兴趣的:(最小生成树,贪心算法)