【实验目的】
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++完整代码:
cpp
#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完整代码:
python
import sys
MaxInt = 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 i
return -1
def CreateUDN(G):
"""构造无向网,返回1表示成功,输入0 0时终止"""
# 读取顶点数和边数
line = sys.stdin.readline()
if not line:
G.vexnum, G.arcnum = 0, 0
return 0
parts = line.strip().split()
if len(parts) != 2:
G.vexnum, G.arcnum = 0, 0
return 0
G.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] = w
G.arcs[j][i] = w # 无向网对称
return 1
def 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 vex1
def MiniSpanTree_Prim(G):
"""Prim算法求解最小生成树"""
if G.vexnum == 0:
return
min_edge = MaxInt
total = 0 # 总权值
vex1 = [MaxInt] * MVNum # 存储已加入生成树的顶点索引
# 初始化:从索引为0的顶点开始
vex1[0] = 0
k = 0 # 循环计数器
while k < LengthVex(vex1):
min_edge = MaxInt
vex_begin = -1
vex_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 = u
vex_end = v
k += 1
if min_edge != MaxInt:
# 将新顶点加入生成树
vex1[k] = vex_end
print(f"{vex_begin}-{vex_end} ", end="")
total += min_edge
print() # 换行
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完整代码:
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的索引,找不到返回-1
private 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,无则返回false
private 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算法的核心思想。