一、基本概念
生成树:一个连通图的生成树是一个极小的连通子图,它含有图中全部的顶点,但只有足以构成一棵树的n-1条边。
最小生成树:把构造连通网的最小代价生成树称为最小生成树。(一棵生成树的代价就是树上各边的代价之和)。
二、经典算法
1、普里姆算法(主要针对顶点展开的,对于稠密图,即边数非常多的情况会更好一些)
主要思路如下图所示:
如上图所示,假设首先从零点出发,并将零点涂为黑色;
如上图所示,将蓝点与黑点之间的连线涂为紫色
在上面三个紫色的连线中最短的是权值为 1 的边,因此将这条边涂为红色并且将所对应的结点涂为黑色;
将黑色与蓝色点之间的边改为紫色,若一个蓝点与多个黑点有相连,取权值最小的边作为子边。在这里看到,1 号顶点实际上可以连接 0 号顶点或者 2 号顶点,但是由于1、2间的权值比0、1间的权值小,所以将1、2间的路径涂为紫色,而0、1间的路径涂为灰色(不予考虑);同理将将0、3间的路径涂为紫色,而2、3间的路径涂为灰色。
在紫边中选取权值最小的边,并将此边涂为红色,相邻结点涂为黑色。
同理确定剩下蓝点与黑点之间的线哪些是紫色的,哪些是灰色的。
选取最小权值所对应的边及顶点
剩下的过程原理相同,如上图所示,得到最小生成树。
2、普里姆算法实现
/**
* 实验题目:
* 采用普里姆算法求最小生成树
* 实验目的:
* 领会普里姆算法求带权连通图中最小生成树的过程和相关算法设计
* 实验内容:
* 编写程序,实现求带权连通图最小生成树的普里姆算法。对于如下
* 图所示的带权连通图,输出从顶点0出发的一颗最小生成树。
*/
#include
#include
#define INF 32767 //定义∞
#define MAXV 100 //最大顶点个数
typedef char InfoType;
/*-------------------------以下定义邻接矩阵类型---------------------------*/
typedef struct
{
int no; //顶点编号
InfoType info; //顶点信息
}VertexType; //顶点类型
typedef struct
{
int edges[MAXV][MAXV]; //邻接矩阵数组(用一个二维数组存放顶点间关系(边或弧)的数据)
int n; //顶点数
int e; //边数
VertexType vexs[MAXV]; //存放顶点信息(用一个一维数组存放图中所有顶点数据)
}MatGraph; //完整的图邻接矩阵类型
//邻接表表示法-将每个顶点的邻接点串成一个单链表
/*-----------以下定义邻接表类型--------------*/
typedef struct ArcNode
{
int adjvex; //该边的邻接点编号
struct ArcNode *nextarc; //指向下一条边的指针
int weight; //该边的相关信息,如权值(用整型表示)
}ArcNode; //边结点类型
typedef struct VNode
{
InfoType info; //顶点其他信息
int cnt; //存放顶点入度,仅用于拓扑排序
ArcNode *firstarc; //指向第一条边
}VNode; //邻接表结点类型
typedef struct
{
VNode adjlist[MAXV]; //邻接表头结点数组
int n; //图中顶点数
int e; //图中边数
}AdjGraph; //完整的图邻接表类型
/*-------------------------邻接矩阵的基本运算算法---------------------------*/
/*------------由边数组A、顶点数n和边数e创建图的邻接矩阵g--------------------*/
void CreateMat(MatGraph &g, int A[MAXV][MAXV], int n, int e)
{
int i, j;
g.n = n;
g.e = e;
for(i = 0; i < g.n; i++)
for(j = 0; j < g.n; j++)
g.edges[i][j] = A[i][j];
}
/*------------输出邻接矩阵g--------------------*/
void DispMat(MatGraph g)
{
int i, j;
for(i = 0; i < g.n; i++)
{
for(j = 0; j < g.n; j++)
{
if(g.edges[i][j] != INF)
printf("%4d", g.edges[i][j]);
else
printf("%4s", "∞");
}
printf("\n");
}
}
/*-------------------------邻接表的基本运算算法---------------------------*/
/*-------------------由边数组A、顶点数n和边数e创建图的邻接表G--------------------*/
void CreateAdj(AdjGraph *&G, int A[MAXV][MAXV], int n, int e)
{
int i, j;
ArcNode *p;
G = (AdjGraph *)malloc(sizeof(AdjGraph));
for(i = 0; i < n; i++) //给邻接表中所有头结点的指针域置初值NULL
{
G->adjlist[i].firstarc = NULL;
}
for(i = 0; i < n; i++) //检查邻接矩阵中的每个元素
{
for(j = n - 1; j >= 0; j--)
{
if(A[i][j] != 0 && A[i][j] != INF) //存在一条边
{
p = (ArcNode *)malloc(sizeof(ArcNode)); //创建一个结点p
p->adjvex = j; //邻接点编号
p->weight = A[i][j]; //边的权重
p->nextarc = G->adjlist[i].firstarc; //采用头插法插入结点p
G->adjlist[i].firstarc = p;
}
}
}
G->n = n;
G->e = e;
}
/*-------------------输出邻接表G--------------------*/
void DispAdj(AdjGraph *G)
{
ArcNode *p;
for(int i = 0; i < G->n; i++)
{
p = G->adjlist[i].firstarc;
printf("%d: ", i);
while(p != NULL)
{
printf("%3d[%d]->", p->adjvex, p->weight); //邻接点编号[权重]
p = p->nextarc;
}
printf("∧\n");
}
}
/*-------------------销毁图的邻接表G--------------------*/
void DestroyAdj(AdjGraph *&G)
{
ArcNode *pre, *p;
for(int i = 0; i < G->n; i++)
{
pre = G->adjlist[i].firstarc; //pre指向第i个单链表的首结点
if(pre != NULL)
{
p = pre->nextarc;
while(p != NULL) //释放第i个单链表的所有边结点
{
free(pre);
pre = p;
p = p->nextarc;
}
free(pre);
}
}
free(G); //释放头结点数组
}
/*--------------采用普里姆算法输出图g中从顶点v出发的一棵最小生成树----------------*/
/**
* 功能:
* 采用普里姆算法输出图g中从顶点v出发的一棵最小生成树(n个顶点,n-1条边)
* 备注:
* V:全部顶点的集合
* U:已被挑选出来的集合
*/
void Prim(MatGraph g, int v)
{
int lowcost[MAXV];
int min_weight;
int closest[MAXV];
int i, j;
int k;
for(i = 0; i < g.n; i++) //设置lowcost[]和closest[]的初值
{
lowcost[i] = g.edges[v][i];
closest[i] = v;
}
for(i = 1; i < g.n; i++) //找出n-1个顶点
{
min_weight = INF;
for(j = 0; j < g.n; j++) //在(V-U)中找出距离U最近的顶点k
{
if(lowcost[j] != 0 && lowcost[j] < min_weight)
{
min_weight = lowcost[j];
k = j;
}
}
printf(" 边(%d,%d)权值为:%d\n", closest[k], k, min_weight);
lowcost[k] = 0; //标记k已经加入U
for(j = 0; j < g.n; j++) //修改数组lowcost和closest
{
if(g.edges[k][j] !=0 && g.edges[k][j] < lowcost[j])
{
lowcost[j] = g.edges[k][j];
closest[j] = k;
}
}
}
}
int main(void)
{
int v = 0;
MatGraph g;
int n = 6;
int e = 10;
int A[MAXV][MAXV] = {
{0, 5, 8, 7, INF, 3},
{5, 0, 4, INF, INF, INF},
{8, 4, 0, 5, INF, 9},
{7, INF, 5, 0, 5, 6},
{INF, INF, INF, 5, 0, 1},
{3, INF, 9, 6, 1, 0}
};
CreateMat(g, A, n, e); //创建图的邻接矩阵
printf("图G的邻接矩阵:\n");
DispMat(g);
printf("普里姆算法求解最小生成树:\n");
Prim(g, v);
return 0;
}
输出结果:
图G的邻接矩阵:
0 5 8 7 ∞ 3
5 0 4 ∞ ∞ ∞
8 4 0 5 ∞ 9
7 ∞ 5 0 5 6
∞ ∞ ∞ 5 0 1
3 ∞ 9 6 1 0
普里姆算法求解最小生成树:
边(0,5)权值为:3
边(5,4)权值为:1
边(0,1)权值为:5
边(1,2)权值为:4
边(4,3)权值为:5