任务描述
本关任务:图的存储结构为邻接矩阵,要求编写函数利用普里姆(Prim)算法求图的最小生成树。
相关知识
图的生成树和最小生成树
在一个无向连通图G
中,如果取它的全部顶点和一部分边构成一个子图G′
,即V(G′)=V(G)
和E(G′)⊆E(G)
。
若边集E(G′)
中的边既将图G
中的所有顶点连通又不形成回路,则称子图G′
是原图G
的一棵生成树。
可以通过遍历方式产生一个无向图的生成树。 通过深度优先遍历产生的生成树称为深度优先生成树。 通过广度优先遍历产生的生成树称为广度优先生成树。
无向图进行遍历时: 连通图:仅需要从图中任一顶点出发,进行深度优先遍历或广度优先遍历便可以访问到图中所有顶点,因此连通图的一次遍历所经过的边的集合及图中所有顶点的集合就构成了该图的一棵生成树。
非连通图:它由多个连通分量构成的,则需要从每个连通分量的任一顶点出发进行遍历,每次从一个新起点出发进行遍历过程得到的顶点访问序列恰为各个连通分量中的顶点集。每个连通分量产生的生成树合起来构成整个非连通图的生成树。
由一个带权无向图可能产生多棵生成树, 把具有权之和最小的生成树称为图的最小生成树。
构造一个图的最小生成树主要有两个算法,即普里姆算法和克鲁斯卡尔算法。
普里姆算法思想
普里姆算法是一种构造性算法。 G=(V,E)⇒T=(U,TE)
是G
的从顶点v
出发构造的最小生成树,步骤如下:
(1)初始化U=v
。以v
到其他顶点的所有边为候选边; (2)重复以下步骤n−1
次,使得其他n−1
个顶点被加入到U
中: ① 从候选边中挑选权值最小的边加入TE
,设该边在V−U
中的顶点是k
,将k
加入U
中; ② 考察当前V−U
中的所有顶点j
,修改候选边:若(k,j)
的权值小于原来和顶点j
关联的候选边,则用(k,j)
取代后者作为候选边。
采用普里姆算法求最小生成树的过程:
普里姆算法实现步骤
定义记录从顶点集U到V-U的代价最小的边的辅助数组:
typedef struct min
{
VertexType adjvex;
VRType lowcost;
}minside[MAX_VERTEX_NUM];
minside closedge;
所有顶点分为U
和V−U
两个顶点集。 U
中的顶点i
:lowcost[i]=0;
V−U
中的顶点j
:lowcost[j]>0
。
由于是无向网,U
到V−U
的边与V−U
到U
的边相同。 这里考虑V−U
到U的边。 对于V−U
中的每个顶点j
,记录它到U
中的一条最小边:closedge[j].adjvex
存储该边依附的在U
中的顶点数据,closedge[j].lowcost
存储该边的权值。
U
中顶点的增量是一个一个添加的。
普里姆算法中有两重for循环,所以时间复杂度为O(n2)
,其中n
为图的顶点个数。 由于与e
无关,所以普里姆算法特别适合于稠密图求最小生成树。
编程要求
无向网G的存储结构为邻接矩阵,编写函数利用普里姆(Prim)算法求图的最小生成树:
- void MiniSpanTree_PRIM(MGraph G,VertexType u); // 用普里姆算法从第u个顶点出发构造网G的最小生成树T,输出T的各条边
测试说明
平台会对你编写的代码进行测试:
测试输入: 3
lt3.txt
0
输入说明: 第一行输入
3
,表示输入图的类型为无向网。 第二行输入文件名,该文件里保存了图的数据信息,内容如下: 5 8 0 1 2 3 4 0 1 1 0 2 3 0 3 4 0 4 7 1 2 2 2 3 5 2 4 8 3 4 6 第1行为图的顶点的个数n; 第2行为图的边的条数m; 第3行至第n+2行是n个顶点的数据; 第n+3行至第n+m+2行是m条边的数据;
第三行输入利用普里姆算法构造最小生成树的起点。
预期输出: 无向网
5个顶点8条边。顶点依次是: 0 1 2 3 4
图的邻接矩阵:
∞ 1 3 4 7
1 ∞ 2 ∞ ∞
3 2 ∞ 5 8
4 ∞ 5 ∞ 6
7 ∞ 8 6 ∞
用普里姆算法从g的第0个顶点出发输出最小生成树的各条边:
最小代价生成树的各条边为:
边(0,1),权值为1
边(1,2),权值为2
边(0,3),权值为4
边(3,4),权值为6
输出说明: 第一行输出图的类型。 第二行起输出图的顶点和边的数据信息。 最后分行输出从第0个顶点出发用普里姆算法构造的最小生成树的各条边。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<limits.h>
#include"MGraph.h"
typedef struct min
{ /* 记录从顶点集U到V-U的代价最小的边的辅助数组定义 */
VertexType adjvex;
VRType lowcost;
}minside[MAX_VERTEX_NUM];
int minimum(minside SZ,MGraph G); // 求SZ.lowcost的最小正值,并返回其在SZ中的序号
void MiniSpanTree_PRIM(MGraph G,VertexType u); // 用普里姆算法从第u个顶点出发构造网G的最小生成树T,输出T的各条边
int main()
{
MGraph g;
int n;
CreateGraphF (g); // 利用数据文件创建无向图
Display(g); // 输出无向图
scanf("%d",&n);
printf("用普里姆算法从g的第%d个顶点出发输出最小生成树的各条边:\n",n);
MiniSpanTree_PRIM(g,g.vexs[n]); // Prim算法从第1个顶点构造最小生成树
return 0;
}
int minimum(minside SZ,MGraph G)
{ //求SZ.lowcost的最小正值,并返回其在SZ中的序号
int i=0,j,k,min;
while(!SZ[i].lowcost)
i++;
min=SZ[i].lowcost; // 第一个不为0的值
k=i;
for(j=i+1;j<G.vexnum;j++)
if(SZ[j].lowcost>0&&min>SZ[j].lowcost) // 找到新的大于0的最小值
{
min=SZ[j].lowcost;
k=j;
}
return k;
}
void MiniSpanTree_PRIM(MGraph G,VertexType u)
{
// 用普里姆算法从第u个顶点出发构造网G的最小生成树T,输出T的各条边
/********** Begin **********/
int i,j,k;
minside closedge;
k=LocateVex(G,u);
for(j=0;j<G.vexnum;j++){
strcpy(closedge[j].adjvex,u);
closedge[j].lowcost=G.arcs[k][j].adj;
}
closedge[k].lowcost=0;
printf("最小代价生成树的各条边为:\n");
for(i=1;i<G.vexnum;i++){
k=minimum(closedge,G);
printf("边(%s,%s),权值为%d\n",closedge[k].adjvex, G.vexs[k], closedge[k].lowcost);
closedge[k].lowcost=0;
for(j=0;j<G.vexnum;j++)
if(G.arcs[k][j].adj<closedge[j].lowcost){
strcpy(closedge[j].adjvex,G.vexs[k]);
closedge[j].lowcost=G.arcs[k][j].adj;
}
}
/********** End **********/
}