构造最小生成树Prim算法(C语言)

本文介绍Prim算法的基本原理及应用实例,通过分析问题特点,给出详细的代码实现过程,最终解决连接偏远地区村落间的道路最少花费问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

提示:通过用Prim算法构造最小生成树解决连接偏远地区村落间道路最少花费问题

前言

某地对偏远地区实行“村村通”工程,目标是使整个地区任何两个村落间都可以实现快速交通(但不一定有直接的快速道路相连,只要互相间接通过快速路可达即可)。现得到拟修建道路的费用,现请你编写程序,计算出全地区畅通需要的最低成本。

输入格式:

输入的第一行给出村庄数目N (1≤N≤20)和拟修建的道路数M

接下来的M行对应修建每条村庄间道路的成本,每行给出3个正整数,分别是两个村庄的编号(从1编号到N),此两村庄间道路的成本。

输出格式: 

输出需修建的道路,按prim算法从编号1开始得到的顺序,输出每条路,每行输出一条道路,形式如:道路1编号,道路2编号,费用。(编号小的放前面,编号大的放后面,逗号为英文状态下的逗号)


提示:以下是本篇文章正文内容,下面案例可供参考

一、什么是Prim算法

Prim算法采用“子树延伸”原理。从单节点树开始,沿着此节点最短边一个一个地向外生长,在构造过程中不会产生回路,直至将所有节点连起来,得到一颗生长树。   

 如上左图—>右图的过程

二、如何使用Prim算法解决问题

1.分析问题

根据题目有

假设输入为

4 6

1 2 1

1 3 4

1 4 1

2 3 3

2 4 2

3 4 5

则输出应是

1,2,1

1,4,1

2,3,3

具体分析如下

①画出无向带权图

②用邻接矩阵构造此图

代码如下(示例):

int L[25][25] = { 0 };//将邻接矩阵全部置零
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < n; j++)
		{
			if (i != j)
				L[i][j] = 1000;       //保留邻接矩阵中自环的值为零,其余边的花费为MAX=1000,一个相对极大的数
		}
	}
	for (int i = 0; i < M; i++)    //将两点间有直接相连的边权值存入,得到一个能表示一个节点与其它所有节点之间的花费如 ∞(MAX),0等
	{
		scanf("%d%d%d", &p, &q, &w);//构造邻接矩阵,将两点间有直接关系的边的花费存入

		L[p - 1][q - 1] = w;    //邻接矩阵从L[0][0]开始存储,但节点名从1开始,所以为了方便起见将节点名减一则刚好匹配
		L[q - 1][p - 1] = w;    //因为存储的是无向带权图,故在邻接矩阵中对称位置权值相等
	}

③设置边节点类型,用于存储

代码如下(示例)

typedef  struct  edge_node
{
	int  incr_vert, vertex;   //前者为生长点,后者为非生长点
	int  cost;  	//边的花费
}edge;   //边节点的类型结构

④构造初始待选边表(以0作为初始生长点)

开始时选择顶点(0)作为初始生长点,对其余每个非生长点v(共n-1个),将边(0,v)加入待选边表,如果边(0,v)不存在,则认为权值为∞,即上文所说的MAX(1000)。

int v, i, j, k;          
	edge  t, wait[100]; //存储待选边表的数组
	for (v = 0; v < n - 1; v++) 	  //以顶点0作为初始生长点建立初始待选边表
	{
		wait[v].incr_vert = 0;           //生长点
		wait[v].vertex = v + 1;   	  //非生长点
		wait[v].cost = L[0][v + 1];
	}

 ⑤循环n-2遍,非生长点个数从n-1变成1

先选择最短待选边

k = i;   //找最短的待选边(简单选择排序)
		for (j = i + 1; j < n - 1; j++)
		{
			if (wait[j].cost < wait[k].cost)k = j;
		}

再修改待选边表

for (i = 0; i < n - 2; i++) 
	{
		k = i;   //找最短的待选边(简单选择排序)
		for (j = i + 1; j < n - 1; j++)
		{
			if (wait[j].cost < wait[k].cost)k = j;
		}
		t = wait[k];  
		wait[k] = wait[i];  
		wait[i] = t;	//得到一条树边,换到数组前部
		v = wait[i].vertex; 		                 //v作为新的生长点
		for (j = i + 1; j < n - 1; j++) 		                 //修改待选边表 
			if (wait[j].cost > L[v][wait[j].vertex]) {
				wait[j].cost = L[v][wait[j].vertex];
				wait[j].incr_vert = v;
			}
	}

2.完整代码实现

代码如下(示例):

#define _CRT_SECURE_NO_DEPRECATE
#include<stdio.h>
typedef  struct  edge_node
{
	int  incr_vert, vertex;   //前者为生长点,后者为非生长点
	int  cost;  	//边的花费
}edge;   //边节点的类型结构

void  Prim(int M, int n)
{
	int p, q, w;
	int L[25][25] = { 0 };//将邻接矩阵全部置零
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < n; j++)
		{
			if (i != j)
				L[i][j] = 1000;       //保留邻接矩阵中自环的值为零,其余边的花费为MAX=1000,一个相对极大的数
		}
	}
	for (int i = 0; i < M; i++)    //将两点间有直接相连的边权值存入,得到一个能表示一个节点与其它所有节点之间的花费如 ∞(MAX),0等
	{
		scanf("%d%d%d", &p, &q, &w);//构造邻接矩阵,将两点间有直接关系的边的花费存入

		L[p - 1][q - 1] = w;    //邻接矩阵从L[0][0]开始存储,但节点名从1开始,所以为了方便起见将节点名减一则刚好匹配
		L[q - 1][p - 1] = w;    //因为存储的是无向带权图,故在邻接矩阵中对称位置权值相等
	}
	for (int i = 0; i < n; i++)//输出该邻接矩阵
	{
		for (int j = 0; j < n; j++)
		{
			printf("%d ", L[i][j]);
		}
		printf("\n");
	}
	int v, i, j, k;          
	edge  t, wait[100]; //存储待选边表的数组
	for (v = 0; v < n - 1; v++) 	  //以顶点0作为初始生长点建立初始待选边表
	{
		wait[v].incr_vert = 0;           //生长点
		wait[v].vertex = v + 1;   	  //非生长点
		wait[v].cost = L[0][v + 1];
	}
	for (i = 0; i < n - 2; i++) 
	{
		k = i;   //找最短的待选边(简单选择排序)
		for (j = i + 1; j < n - 1; j++)
		{
			if (wait[j].cost < wait[k].cost)k = j;
		}
		t = wait[k];  
		wait[k] = wait[i];  
		wait[i] = t;	//得到一条树边,换到数组前部
		v = wait[i].vertex; 		                 //v作为新的生长点
		for (j = i + 1; j < n - 1; j++) 		                 //修改待选边表 
			if (wait[j].cost > L[v][wait[j].vertex]) {
				wait[j].cost = L[v][wait[j].vertex];
				wait[j].incr_vert = v;
			}
	}
	for (i = 0; i < n - 1; i++)
	{
		printf("%d,%d,%d\n", (wait[i].incr_vert + 1)<(wait[i].vertex + 1)? (wait[i].incr_vert + 1): (wait[i].vertex + 1), (wait[i].incr_vert + 1) > (wait[i].vertex + 1) ? (wait[i].incr_vert + 1) : (wait[i].vertex + 1), wait[i].cost);
//此处用了三目运算符使得较小编号先输出

	}
}


int main()
{
	int N, M;
	scanf("%d%d", &N,&M);//N为村庄数,M为计划修建道路数
	
	Prim(M, N);

	return 0;

}


二、总结

Prim算法思想是选最短边,且只带待选边表中选最短边。

Prim算法不需要判断是否产生回路。

时间复杂度是O(n^2),耗时取决于顶点数n。

Prim算法是一种用于求解无向加权图中最小生成树的贪心算法。在C语言实现Prim算法通常涉及以下几个步骤: 1. **初始化**: - 创建一个集合S,开始时只包含起点(通常是边集中权重最小的边所连接的顶点)。 - 初始化一个优先级队列(可以用二叉堆),将起点及其所有邻接点的边权重放入队列。 2. **循环过程**: - 取出队列中的当前最小边(即队首元素),这条边连接的两个节点称为u和v。 - 如果v不在集合S中,则将其加入S,并更新它到起始点的所有未访问邻居的边权重(如果更短的话)。 - 将这些更新的边添加回队列。 - 继续此过程,直到集合S包含了图中所有节点。 3. **结束条件**: - 当队列为空或者S包含了所有节点时,停止循环,此时S中的边构成的就是最小生成树。 以下是简单的C语言伪代码示例: ```c #include <stdio.h> #include <stdlib.h> // for heap functions #include <stdbool.h> // 定义结构体表示边和优先级队列 typedef struct { int u, v; // 边的两端节点 int weight; // 边的权重 } Edge; // 优先级队列(最小堆) typedef struct { Edge* array; int size; } PriorityQueue; // 算法主体函数 void prim(int numVertices, int graph[][numVertices], PriorityQueue* priorityQueue) { bool visited[numVertices] = {false}; int distance[numVertices]; memset(distance, INT_MAX, sizeof(distance)); distance[0] = 0; while (!isFull(priorityQueue)) { Edge minEdge = getMinElement(priorityQueue); int u = minEdge.u; int v = minEdge.v; if (visited[v] == false && distance[u] + graph[u][v] < distance[v]) { distance[v] = distance[u] + graph[u][v]; // 更新距离 updatePriorityQueue(priorityQueue, v, distance[v]); // 更新优先级队列 visited[v] = true; } removeMinElement(priorityQueue); // 移除已处理的边 } // 输出最小生成树的边 printf("Minimum spanning tree edges:\n"); printEdgesInOrder(distance, graph, visited); } int main() { // 实现细节省略... return 0; } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值