【数据结构】基于Prim算法的最小生成树
【实验目的】
1.掌握图的邻接矩阵表示法。
2.掌握求解最小生成树的Prim算法。
【实验内容】
1、问题描述 假设 n 个城市之间构建通信网,那么怎样能够做到在最节省通信线路经费 的条件下建立这个通信网呢?可以用Prim算法构建连通这n个结点所需的n-1 条线路,从而最大可能的节省通信线路经费。
2、输入要求 多组数据,每组数据有m+3行。第一行为两个整数n和m,分别代表城市 个数n和路径条数m。第二行有n个字符,代表每个城市的编号。第三行到第 m+2行每行有两个字符a和b和一个整数d,代表从城市a到城市b的通信线 路经费。 第m+3行代表起始顶点编号。当n和m都等于0时,输入结束。
3、输出要求 第1行打印所构造的最小生成树通信线路的各条边,如0-5 5-4 第2行打印最小生成树通信线路的总经费 第3行开始打印最小生成树通信线路所对应的邻接矩阵。

输入样例:
在这里给出一组输入。例如:
7 9
0 1 2 3 4 5 6
0 1 28
0 5 10
1 2 16
1 6 14
2 3 12
3 4 22
3 6 18
4 5 25
4 6 24
0 0
输出样例:
在这里给出相应的输出。例如:
0-5 5-4 4-3 3-2 2-1 1-6
99
∞ 28 ∞ ∞ ∞ 10 ∞
28 ∞ 16 ∞ ∞ ∞ 14
∞ 16 ∞ 12 ∞ ∞ ∞
∞ ∞ 12 ∞ 22 ∞ 18
∞ ∞ ∞ 22 ∞ 25 24
10 ∞ ∞ ∞ 25 ∞ ∞
∞ 14 ∞ 18 24 ∞ ∞
实现步骤:
1.图的存储与初始化
-
图的表示:采用邻接矩阵(
AMGraph结构)存储无向网,其中包含:- 顶点表(
vexs):存储顶点的字符信息; - 邻接矩阵(
arcs):arcs[i][j]表示顶点i与顶点j之间边的权值,若无边则为极大值MaxInt; - 顶点数(
vexnum)和边数(arcnum)。
- 顶点表(
-
图的构造:通过
CreateUDN函数输入顶点数、边数、顶点信息及边的权值,初始化邻接矩阵(初始值为MaxInt),再根据输入的边信息填充邻接矩阵(无向网对称填充,arcs[i][j] = arcs[j][i])。
2.Prim 算法核心流程
Prim 算法的核心思想是:从一个初始顶点出发,逐步将 “已加入生成树的顶点集” 与 “未加入的顶点集” 之间权值最小的边所连接的顶点纳入生成树,最终形成包含所有顶点的最小生成树(无环且权值和最小)。
步骤 1:初始化生成树顶点集
- 定义数组
vex1记录已加入生成树的顶点下标(初始值均为MaxInt,表示未使用); - 选择初始顶点(代码中默认从下标为
0的顶点开始),将其存入vex1[0],即vex1[0] = 0。
步骤 2:循环查找最小边并扩展生成树
循环的终止条件:已加入生成树的顶点数等于图的总顶点数(即vex1中有效顶点数等于G.vexnum)。
每次循环执行以下操作:
-
寻找当前最小边:
- 遍历
vex1中所有已加入生成树的顶点(记为u); - 对每个
u,遍历图中所有顶点(记为v),筛选出 “v未加入生成树”(通过FindInVex函数判断v不在vex1中)且 “u与v之间存在边”(arcs[u][v] < MaxInt)的边; - 在所有符合条件的边中,找到权值最小的边,记录其权值(
minEdge)、起点(vexBegin,即u)和终点(vexEnd,即v)。
- 遍历
-
扩展生成树:
- 将找到的最小边的终点
vexEnd加入vex1数组(作为新的已加入顶点); - 记录该边(输出
vexBegin-vexEnd),并将其权值累加到总权值sum中。
- 将找到的最小边的终点
步骤 3:输出结果
- 循环结束后,输出最小生成树的所有边;
- 输出最小生成树的总权值
sum。
3.辅助函数的作用
LocateVex:根据顶点字符查找其在顶点表中的下标,用于构造邻接矩阵时定位顶点;Show:打印邻接矩阵,用于验证图的存储是否正确;LengthVex:计算vex1中已加入生成树的顶点数(统计非MaxInt的元素个数);FindInVex:判断顶点v是否已加入生成树(检查v是否在vex1中)。
C++完整代码:
#include<iostream>
using namespace std;
#define MaxInt 32767 //表示极大值
#define MVNum 100 //最大顶点数
typedef char VerTexType; //设置顶点类型为字符型
typedef int ArcType; //设置权重为整型
typedef struct
{VerTexType vexs[MVNum]; //顶点表 ArcType arcs[MVNum][MVNum]; //邻接矩阵 int vexnum, arcnum; //顶点数和边数
}AMGraph;
//从顶点表中查找顶点
int LocateVex(AMGraph G, VerTexType u);
//构造无向网
int CreateUDN(AMGraph& G);
void Show(AMGraph G); //打印邻接矩阵
int LengthVex(int vex1[]); // 返回当前vex数组的长度
int FindInVex(int vex[], int e); // 查看vex数组中是否有元素e,有则返回1,无则返回0
void MiniSpanTree_Prim(AMGraph G); // Prim算法最小生成树
int main()
{while (1){AMGraph G;CreateUDN(G);if (G.vexnum == 0 && G.arcnum == 0){break;}MiniSpanTree_Prim(G);Show(G);}return 0;
}
//从顶点表中查找顶点
int LocateVex(AMGraph G, VerTexType u)
{int i;for (i = 0;i < G.vexnum;i++){if (u == G.vexs[i]){return i;}}return -1;
}
//构造无向网
int CreateUDN(AMGraph& G)
{int i, j, k;cin >> G.vexnum >> G.arcnum; //输入顶点数和边数 for (i = 0;i < G.vexnum;i++){cin >> G.vexs[i]; //顶点表}for (i = 0;i < G.vexnum;i++){for (j = 0;j < G.vexnum;j++){G.arcs[i][j] = MaxInt; //初始化邻接矩阵 }}for (k = 0;k < G.arcnum;k++){ //构造邻接矩阵 VerTexType v1, v2;ArcType w;cin >> v1 >> v2 >> w;i = LocateVex(G, v1);j = LocateVex(G, v2);G.arcs[i][j] = w; //边(v1,v2)权重置为w G.arcs[j][i] = G.arcs[i][j]; //无向网,对称 }return 1;
}
void Show(AMGraph G) //打印邻接矩阵
{int i, j;for (i = 0;i < G.vexnum;i++){for (j = 0;j < G.vexnum;j++){if (G.arcs[i][j] == MaxInt){cout << "∞" << ' ';}else{cout << G.arcs[i][j] << ' ';}}cout << endl;}}
int LengthVex(int vex1[]) // 返回当前vex数组的长度
{int i, num = 0;for (i = 0; i < MVNum; i++){if (vex1[i] != MaxInt)num++;}return num;
}
int FindInVex(int vex[], int e) // 查看vex数组中是否有元素e,有则返回1,无则返回0
{int i;for (i = 0; i < MVNum; i++){if (vex[i] == e)return 1;}return 0;
}
void MiniSpanTree_Prim(AMGraph G) // Prim算法最小生成树
{int minEdge, sum = 0; // 记录权值最小的边 int vex1[MVNum]; // 记录已连入生成树的顶点下标,数组下标表示先后顺序 int i, j, k = 0;int vexBegin, vexEnd; // 边起点和边终点 for (i = 0; i < MVNum; i++) // 将数组中的值全部初始化为无限 {vex1[i] = MaxInt;}vex1[0] = 0; // 从顶点v0开始生成树while (k < LengthVex(vex1)){minEdge = MaxInt;for (i = 0; i < LengthVex(vex1); i++){for (j = 0; j < G.vexnum; j++){if (G.arcs[vex1[i]][j] < minEdge && !FindInVex(vex1, j))// 找还未被并入最小生成树的权值最小边 {minEdge = G.arcs[vex1[i]][j]; // 记录权值 vexBegin = vex1[i]; // 记下边的起点和终点 vexEnd = j;}}}k++;if (minEdge != MaxInt){vex1[k] = vexEnd; // 新顶点加入生成树 cout << vexBegin << "-" << vexEnd << " ";sum = sum + minEdge;}}cout << endl;cout << sum << endl;
}

Python完整代码:
import sysMaxInt = 32767 # 表示极大值
MVNum = 100 # 最大顶点数class AMGraph:def __init__(self):self.vexs = [] # 顶点表(存储顶点字符)self.arcs = [] # 邻接矩阵(二维列表)self.vexnum = 0 # 顶点数self.arcnum = 0 # 边数def LocateVex(G, u):"""从顶点表中查找顶点u的索引,找不到返回-1"""for i in range(G.vexnum):if G.vexs[i] == u:return ireturn -1def CreateUDN(G):"""构造无向网,返回1表示成功,输入0 0时终止"""# 读取顶点数和边数line = sys.stdin.readline()if not line:G.vexnum, G.arcnum = 0, 0return 0parts = line.strip().split()if len(parts) != 2:G.vexnum, G.arcnum = 0, 0return 0G.vexnum, G.arcnum = map(int, parts)# 若顶点数和边数都为0,退出if G.vexnum == 0 and G.arcnum == 0:return 0# 读取顶点表vexs_line = sys.stdin.readline().strip().split()G.vexs = vexs_line[:G.vexnum] # 确保只取指定数量的顶点# 初始化邻接矩阵为极大值G.arcs = [[MaxInt for _ in range(G.vexnum)] for _ in range(G.vexnum)]# 读取边信息并填充邻接矩阵for _ in range(G.arcnum):edge_line = sys.stdin.readline().strip().split()if len(edge_line) != 3:continue # 忽略格式错误的行v1, v2, w = edge_line[0], edge_line[1], int(edge_line[2])i = LocateVex(G, v1)j = LocateVex(G, v2)if i != -1 and j != -1:G.arcs[i][j] = wG.arcs[j][i] = w # 无向网对称return 1def Show(G):"""打印邻接矩阵"""for i in range(G.vexnum):row = []for j in range(G.vexnum):if G.arcs[i][j] == MaxInt:row.append("∞")else:row.append(str(G.arcs[i][j]))print(" ".join(row))def LengthVex(vex1):"""返回当前vex1数组中已加入生成树的顶点数(非MaxInt的元素个数)"""return sum(1 for x in vex1 if x != MaxInt)def FindInVex(vex1, e):"""判断顶点e是否在vex1数组中,存在返回True,否则返回False"""return e in vex1def MiniSpanTree_Prim(G):"""Prim算法求解最小生成树"""if G.vexnum == 0:returnmin_edge = MaxInttotal = 0 # 总权值vex1 = [MaxInt] * MVNum # 存储已加入生成树的顶点索引# 初始化:从索引为0的顶点开始vex1[0] = 0k = 0 # 循环计数器while k < LengthVex(vex1):min_edge = MaxIntvex_begin = -1vex_end = -1# 遍历已加入生成树的顶点current_len = LengthVex(vex1)for i in range(current_len):u = vex1[i]# 遍历所有顶点,找未加入且权值最小的边for v in range(G.vexnum):if G.arcs[u][v] < min_edge and not FindInVex(vex1, v):min_edge = G.arcs[u][v]vex_begin = uvex_end = vk += 1if min_edge != MaxInt:# 将新顶点加入生成树vex1[k] = vex_endprint(f"{vex_begin}-{vex_end} ", end="")total += min_edgeprint() # 换行print(total)def main():while True:G = AMGraph()if not CreateUDN(G):break # 输入0 0时退出MiniSpanTree_Prim(G)Show(G)if __name__ == "__main__":main()

Java完整代码:
import java.util.Scanner;public class PrimMST {private static final int MaxInt = 32767; // 表示极大值private static final int MVNum = 100; // 最大顶点数// 图的邻接矩阵表示static class AMGraph {char[] vexs; // 顶点表int[][] arcs; // 邻接矩阵int vexnum; // 顶点数int arcnum; // 边数public AMGraph() {vexs = new char[MVNum];arcs = new int[MVNum][MVNum];vexnum = 0;arcnum = 0;}}// 从顶点表中查找顶点u的索引,找不到返回-1private static int locateVex(AMGraph g, char u) {for (int i = 0; i < g.vexnum; i++) {if (g.vexs[i] == u) {return i;}}return -1;}// 构造无向网,返回1表示成功,输入0 0时返回0终止private static int createUDN(AMGraph g, Scanner scanner) {// 读取顶点数和边数int vexnum = scanner.nextInt();int arcnum = scanner.nextInt();g.vexnum = vexnum;g.arcnum = arcnum;// 若顶点数和边数都为0,终止if (vexnum == 0 && arcnum == 0) {return 0;}// 读取顶点表(忽略可能的换行符,确保读取正确)scanner.nextLine(); // 消耗上一行的换行String vexsLine = scanner.nextLine().trim();String[] vexsArr = vexsLine.split(" ");for (int i = 0; i < vexnum; i++) {g.vexs[i] = vexsArr[i].charAt(0); // 取单个字符作为顶点}// 初始化邻接矩阵为极大值for (int i = 0; i < vexnum; i++) {for (int j = 0; j < vexnum; j++) {g.arcs[i][j] = MaxInt;}}// 读取边信息并填充邻接矩阵for (int k = 0; k < arcnum; k++) {char v1 = scanner.next().charAt(0);char v2 = scanner.next().charAt(0);int w = scanner.nextInt();int i = locateVex(g, v1);int j = locateVex(g, v2);if (i != -1 && j != -1) {g.arcs[i][j] = w;g.arcs[j][i] = w; // 无向网对称存储}}return 1;}// 打印邻接矩阵private static void show(AMGraph g) {for (int i = 0; i < g.vexnum; i++) {for (int j = 0; j < g.vexnum; j++) {if (g.arcs[i][j] == MaxInt) {System.out.print("∞ ");} else {System.out.print(g.arcs[i][j] + " ");}}System.out.println();}}// 返回当前vex数组中已加入生成树的顶点数(非MaxInt的元素个数)private static int lengthVex(int[] vex1) {int num = 0;for (int i = 0; i < MVNum; i++) {if (vex1[i] != MaxInt) {num++;}}return num;}// 查看vex数组中是否有元素e,有则返回true,无则返回falseprivate static boolean findInVex(int[] vex1, int e) {for (int i = 0; i < MVNum; i++) {if (vex1[i] == e) {return true;}}return false;}// Prim算法求解最小生成树private static void miniSpanTreePrim(AMGraph g) {if (g.vexnum == 0) {return;}int minEdge;int sum = 0; // 总权值int[] vex1 = new int[MVNum]; // 存储已加入生成树的顶点索引// 初始化数组为极大值for (int i = 0; i < MVNum; i++) {vex1[i] = MaxInt;}vex1[0] = 0; // 从索引为0的顶点开始生成树int k = 0; // 循环计数器while (k < lengthVex(vex1)) {minEdge = MaxInt;int vexBegin = -1;int vexEnd = -1;// 遍历已加入生成树的顶点,寻找最小边int currentLen = lengthVex(vex1);for (int i = 0; i < currentLen; i++) {int u = vex1[i];// 遍历所有顶点,找未加入且权值最小的边for (int v = 0; v < g.vexnum; v++) {if (g.arcs[u][v] < minEdge && !findInVex(vex1, v)) {minEdge = g.arcs[u][v];vexBegin = u;vexEnd = v;}}}k++;if (minEdge != MaxInt) {vex1[k] = vexEnd; // 新顶点加入生成树System.out.print(vexBegin + "-" + vexEnd + " ");sum += minEdge;}}System.out.println();System.out.println(sum);}public static void main(String[] args) {Scanner scanner = new Scanner(System.in);while (true) {AMGraph g = new AMGraph();int flag = createUDN(g, scanner);if (flag == 0) {break; // 输入0 0时退出}miniSpanTreePrim(g);show(g);}scanner.close();}
}

总结:
本文介绍了使用Prim算法构建通信网最小生成树的实验过程。实验通过邻接矩阵存储城市间的通信线路费用,采用Prim算法逐步选择最小权值边,最终生成总费用最低的通信网络。实验内容包括图的初始化、Prim算法的实现步骤(初始化顶点集、查找最小边、扩展生成树)以及结果输出(最小生成树边、总费用和邻接矩阵)。代码实现涵盖C++、Python和Java三种语言版本,均支持多组数据输入,并验证了算法的正确性。该实验帮助学生掌握图的邻接矩阵表示和Prim算法的核心思想。
