赞
踩
首先,我们来看一下图的一些基本知识点:
图:图 G=(V,E) 由顶点集 V 和边集 E 组成。每条边对应一个点对 (v,w),其中 v,w 属于 V 。如果图中的点对是有序的,那么该图就是有向图,反之为无向图。
邻接点:若顶点 v 与 w 之间存在一条边,则认为顶点 v 与 w 邻接。
权:图中的每条边都可以对应一个数值,这种与边相关的数值称为权。
路径:在图 G 中,顶点 v1 到 vk 的路径是一个顶点序列 v1,v2,···,vk。
连通图:在无向图 G 中,若两个顶点之间存在路径,则认为这两个顶点是连通的。如果在无向图 G 中,任意两个顶点都是连通的,则称 G 是连通图。
完全图:若图中任意两个顶点之间都存在一条边,则该图为完全图。
稀疏图和稠密图:当图中边的数量比较少时,称该图为稀疏图;而当图接近完全图时,称该图为稠密图。
生成树:一个连通图的生成树是一个极小连通子图,它含有图的全部顶点,但只有足以构成一棵树的 n-1 条边。
最小生成树:对于边上带有权值的图,在图的所有生成树中,树的权值最小的生成树称为最小生成树。
接下来我们进入正题,来看看如何用普里姆算法实现最小生成树。
普里姆算法的思想:
普里姆算法的初始状态仅有一个顶点在最小生成树的顶点集合 U 中,其他顶点都在另一个由不在最小生成树上的顶点构成的集合 V 中。在后续的每一步中,通过选择所有连接最小生成树上的顶点 u 和不在树上的顶点 v 之间的边中权值最小的边 (u,v) ,将对应的顶点 v 拉入最小生成树的顶点集合中。当图中所有的顶点都已加入到树中,算法运算结束后,此时得到的 n 个顶点和 n-1 条边就构成了一棵最小生成树。

实现方法:
以上方无向有权图为例。我们创建三个数组 Adjvex[],Lowcost[],Flag[]。Adjvex[]表示权值最小的边 (u,v)中位于最小生成树中的顶点; Lowcost[]保存不在最小生成树中的各点到最小生成树中的点的边的最小权值(初始化为无限大); Flag[]标注点是否已加入最小生成树中(初始化为0)。用图表示过程如下:
1、初始状态,我们从 1 号点出发,将1加入树中,Flag[1] 置为1。然后我们遍历点 1 的所有邻接点,把相应权值填入Lowcost中,这里点 1 到点 1 的权值我们视为0。如图:
| 编号 | 1 | 2 | 3 | 4 | 5 |
| Adjvex | 1 | 1 | 1 | 1 | 1 |
| Lowcost | 0 | 5 | 6 | 3 | |
| Flag | 1 | 0 | 0 | 0 | 0 |
2、我们从 Lowcost 中选出权值最小的边对应的点(该点不在树中,即Flag对应为0),这里为点4,将其加入树中,Flag[4]置为1。
| 编号 | 1 | 2 | 3 | 4 | 5 |
| Adjvex | 1 | 1 | 1 | 1 | 1 |
| Lowcost | 0 | 5 | 6 | 3 | |
| Flag | 1 | 0 | 0 | 1 | 0 |
3、接着遍历点 4 的所有邻接点,与原来的权值作比较,把较小的权值填入Lowcost 中,若新的权值取代了旧的权值,还要同时改变 Adjvex 的值为 4。
| 编号 | 1 | 2 | 3 | 4 | 5 |
| Adjvex | 1 | 1 | 4 | 1 | 4 |
| Lowcost | 0 | 5 | 2 | 3 | 7 |
| Flag | 1 | 0 | 0 | 1 | 0 |
4、重复2、3步,直到所有的点均已加入最小生成树中。此时 Lowcost 行的和即为最小生成树的值,最终表格如下,最小生成树的值=1+2+3+4=10。
| 编号 | 1 | 2 | 3 | 4 | 5 |
| Adjvex | 1 | 5 | 4 | 1 | 3 |
| Lowcost | 0 | 1 | 2 | 3 | 4 |
| Flag | 1 | 1 | 1 | 1 | 1 |
程序代码:
- # include <iostream>
- # define SIZE 20
- # define NEWSIZE 20
- # define maxn 100000000 //表示无限大
- using namespace std;
- typedef struct ArcNode { //边的结点结构类型
- int adjvex; //该边的终点编号
- int weight; //该边的权值
- struct ArcNode* nextarc; //指向下一条边的指针
- }ArcNode;
- typedef struct VexNode { //顶点结构
- char data;
- ArcNode* firstarc; //指向第一条与该顶点有关的边的指针
- }VexNode;
- typedef struct Graph { //邻接表结构类型
- VexNode* VNode; //定义邻接表
- int vexnum, arcnum; //顶点数和边的个数
- int size; //邻接表的大小
- }Graph;
-
- int* Adjvex; //保存在最小生成树中的顶点
- int* Lowcost; //保存不在最小生成树中的各点到最小生成树中的点的边的最小权值
- int* Flag; //标注该点是否已加入最小生成树
-
- void Create_Graph(Graph& G) //创建图(邻接表)
- {
- cout << "顶点的个数:";
- cin >> G.vexnum;
- G.VNode = (VexNode*)malloc(SIZE * sizeof(VexNode));
- G.size = SIZE;
- while (G.size <= G.vexnum) { //根据点的个数动态分配空间
- G.VNode = (VexNode*)realloc(G.VNode, (G.size + NEWSIZE) * sizeof(VexNode));
- G.size += NEWSIZE;
- }
- Adjvex = (int*)malloc((G.size + 10) * sizeof(int));
- Lowcost = (int*)malloc((G.size + 10) * sizeof(int));
- Flag = (int*)malloc((G.size + 10) * sizeof(int));
- cout << "请输入各顶点的名称:";
- for (int i = 1; i <= G.vexnum; i++) {
- cin >> G.VNode[i].data;
- G.VNode[i].firstarc = NULL; //邻接表初始化,所有单向链表均为空表
- }
- cout << "请输入边的个数:";
- cin >> G.arcnum;
- cout << "请输入各边起点和终点的编号及权重:" << endl;
- int x, y, w; //x:起始点,y:终点,w:权重
- ArcNode* p, * q;
- for (int i = 1; i <= G.arcnum; i++) {
- cin >> x >> y >> w;
- p = (ArcNode*)malloc(sizeof(ArcNode)); //创建一个用于存放当前边的结点p
- p->nextarc = NULL;
- p->adjvex = y;
- p->weight = w;
- q = G.VNode[x].firstarc;
- //将边按顺序插入到链表末尾
- if (q == NULL) {
- G.VNode[x].firstarc = p;
- }
- else {
- while (q->nextarc != NULL) {
- q = q->nextarc;
- }
- q->nextarc = p;
- }
- p = (ArcNode*)malloc(sizeof(ArcNode));
- p->nextarc = NULL;
- p->adjvex = x;
- p->weight = w;
- q = G.VNode[y].firstarc;
- if (q == NULL) {
- G.VNode[y].firstarc = p;
- }
- else {
- while (q->nextarc != NULL) {
- q = q->nextarc;
- }
- q->nextarc = p;
- }
- }
- }
-
- void MinSpanningTree_Prim(Graph& G, int v) //从顶点v出发求图的最小生成树
- {
- for (int i = 1; i <= G.vexnum; i++) { //初始化
- Adjvex[i] = v;
- Lowcost[i] = maxn;
- Flag[i] = 0;
- }
- Lowcost[v] = 0; //初始点对应的权置为0
- Flag[v] = 1; //标记初始点(即将初始点加入树中)
- cout << G.VNode[v].data; //输出初始点的值
- int num = 1; //记录目前最小生成树中的点的数目
- ArcNode* p;
- while (num < G.vexnum) {
- p = G.VNode[v].firstarc; //p为新加入树的点的第一个邻接点
- while (p != NULL) { //由于新点加入,更新各不在最小生成树中的点到最小生成树中点的最小距离
- if (!Flag[p->adjvex] && Lowcost[p->adjvex] > p->weight) {
- Lowcost[p->adjvex] = p->weight;
- Adjvex[p->adjvex] = v;
- }
- p = p->nextarc;
- }
- int min = maxn;
- for (int i = 1; i <= G.vexnum; i++) { //寻找目前到最小生成树距离最小的点(该点不在最小生成树中)
- if (!Flag[i] && Lowcost[i] < min) {
- min = Lowcost[i];
- v = i; //更新v为目前到最小生成树距离最小的点(该点不在最小生成树中)
- }
- }
- Flag[v] = 1; //标记该点(即将该点加入最小生成树中)
- cout << G.VNode[v].data; //输出新加入树的点的值
- num++; //最小生成树中的点的数目+1
- }
- }
-
- int main()
- {
- Graph G;
- Create_Graph(G);
- int sum = 0;
- cout << "最小生成树为:";
- MinSpanningTree_Prim(G, 1); //从1号点出发
- cout << endl;
- for (int i = 1; i <= G.vexnum; i++) {
- cout << Adjvex[i] << " ";
- }
- cout << endl;
- for (int i = 1; i <= G.vexnum; i++) {
- cout << Lowcost[i] << " ";
- sum += Lowcost[i]; //求最小生成树的值
- }
- cout << endl;
- for (int i = 1; i <= G.vexnum; i++) {
- cout << Flag[i] << " ";
- }
- cout << endl;
- cout << "最小生成树的值为:" << sum << endl;
- return 0;
- }

运行结果:
- 顶点的个数:5
- 请输入各顶点的名称:A B C D E
- 请输入边的个数:7
- 请输入各边起点和终点的编号及权重:
- 1 2 5
- 1 3 6
- 1 4 3
- 2 5 1
- 3 4 2
- 3 5 4
- 4 5 7
- 最小生成树为:ADCEB
- 1 5 4 1 3
- 0 1 2 3 4
- 1 1 1 1 1
- 最小生成树的值为:10

总结:普里姆算法的核心部分是一个双重循环。第一层循环是对所有的顶点;第二层有两个循环,一个是遍历邻接点更新数组 ,另一个是寻找 Lowcost 中的最小值。因此,普里姆算法的时间复杂度是O(n^2)。
以上是我的学习成果,很高兴与大家分享。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。