【数据结构】基于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.图的存储与初始化

  1. 图的表示 :采用邻接矩阵(AMGraph结构)存储无向网,其中包含:

    • 顶点表(vexs):存储顶点的字符信息;
    • 邻接矩阵(arcs):arcs[i][j]表示顶点i与顶点j之间边的权值,若无边则为极大值MaxInt
    • 顶点数(vexnum)和边数(arcnum)。
  2. 图的构造 :通过CreateUDN函数输入顶点数、边数、顶点信息及边的权值,初始化邻接矩阵(初始值为MaxInt),再根据输入的边信息填充邻接矩阵(无向网对称填充,arcs[i][j] = arcs[j][i])。

2.Prim 算法核心流程

Prim 算法的核心思想是:从一个初始顶点出发,逐步将 "已加入生成树的顶点集" 与 "未加入的顶点集" 之间权值最小的边所连接的顶点纳入生成树,最终形成包含所有顶点的最小生成树(无环且权值和最小)。

步骤 1:初始化生成树顶点集
  • 定义数组vex1记录已加入生成树的顶点下标(初始值均为MaxInt,表示未使用);
  • 选择初始顶点(代码中默认从下标为0的顶点开始),将其存入vex1[0],即vex1[0] = 0
步骤 2:循环查找最小边并扩展生成树

循环的终止条件:已加入生成树的顶点数等于图的总顶点数(即vex1中有效顶点数等于G.vexnum)。

每次循环执行以下操作:

  1. 寻找当前最小边

    • 遍历vex1中所有已加入生成树的顶点(记为u);
    • 对每个u,遍历图中所有顶点(记为v),筛选出 "v未加入生成树"(通过FindInVex函数判断v不在vex1中)且 "uv之间存在边"(arcs[u][v] < MaxInt)的边;
    • 在所有符合条件的边中,找到权值最小的边,记录其权值(minEdge)、起点(vexBegin,即u)和终点(vexEnd,即v)。
  2. 扩展生成树

    • 将找到的最小边的终点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算法的核心思想。

相关推荐
追烽少年x4 小时前
Qt中使用C++日志库
c++·qt
木井巳4 小时前
[Java数据结构和算法] HashMap 和 HashSet
java·数据结构·1024程序员节
祈祷苍天赐我java之术5 小时前
解析常见的限流算法
java·数据结构·算法
摇滚侠5 小时前
IDEA 启动前端项目 IDEA 切换分支
java·ide·intellij-idea
元直数字电路验证5 小时前
Jakarta EE开发中,如何配置IntelliJ IDEA的远程调试?
java·eureka·intellij-idea
石头wang5 小时前
idea字体的问题(idea应用本身的字体问题)
java·ide·intellij-idea
mit6.8245 小时前
dp|拆分控制
c++
Shinom1ya_5 小时前
算法 day 34
算法
啊董dong5 小时前
课后作业-2025-10-26
c++·算法·noi