一、图的最小生成树
由生成树的定义可知,无向连通图的最小生成树不是唯一的,连通图的一次遍历所经过的边的集合及图中所有顶点的集合构成了该图的一棵生成树,对连通图的不同遍历,可能的到不同的生成树。
如果无向连通图是一个网,那么,它所有生成树中必有一棵边的权值总和最小的生成树,我们称这棵生成树为最小生成树,简称最小生成树。
二、构造最小生成树的Prim算法
假设G(V,E)为一网图,其中V为所有顶点的集合,E为网图中所有边的集合。设置两个新的集合U和T,其中集合U用于存放G的最小生成树中顶点,集合T存放G的最小生成树的边。Wuv为顶点为u与v的权值。
Prim算法可用下述过程描述:
1、U = {u1} , T = {} ;
2、while(U!=V)do
(u,v) = min{Wuv , u属于U,v属于(U-V)}
T = T + {(u , v)}
U = U+{v} ;
3、结束
为实现Prim算法,需设置两个辅助一维数组lowcost和closevertex,其中lowcost用来保存集合V-U中各项顶点与集合U中各项顶点构成的边中具有最小权值的边的权值;数组closevertex用来保存依附于该边的在集合U中的顶点。
算法:
/*
*Prim算法求最小生成树
*从序号为0的顶点出发,建立的最小生成树存于数组closevertex中
*/
void Prim(int gm[][MaxVertexNum] , int n , int closevertex[])
{
int lowcost[MaxVertexNum] , mincost ;
int i , j , k ;
for(i = 1 ; i < n ; i++)
{
lowcost[i] = gm[0][i] ;
closevertex[i] = 0 ;
}
lowcost[0] = 0 ;
closevertex[0] = 0 ;
//寻找当前最小权值的边的顶点
for(i = 1 ; i < n ; i++)
{
mincost = MAX ;
j = 1 ;
k = 1 ;
while(j < n)
{
if(lowcost[j] < mincost && lowcost[j] != 0)
{
mincost = lowcost[j] ;
k = j ;
}
j++ ;
}
printf("顶点的序号 = %d 边的权值 = %d \n" , k , mincost) ;
lowcost[k] = 0 ;
for(j = 1 ; j < n ; j++)
{
//当存在距离更小时,将lowcost改变,并改变closevertex的值
if(gm[k][j] < lowcost[j])
{
lowcost[j] = gm[k][j] ;
closevertex[j] = k ;
}
}
}
}
三、构造最小生成树的Kruskal算法
Kruskal算法是一种按照网中边的权值递增的顺序构造最小生成树的方法。
基本思想:
设无向连通网G = (V , E),令G的最小生成树为T,其初态为T = (V , {}),即开始时,最小生成树T由图G中的n个顶点构成,顶点之间设有一条边,这样T中各项顶点各自构成一个连通分量。然后按照边的权值从小到大的顺序,考察G的边集E中的各条边。若被考察的边的两个顶点属于T的两个不同的连通分量,则将此边作为最小生生成树的边加入到T中,同时把两个连通分量连接为一个连通分量;若被考察的两个顶点属于同一个连通分量,则舍去此边,以免造成回路,如此下去,当T中的连通分量个数为1时,此时连通分量便为G的一棵最小生成树。
如下图为构造过程:
下面介绍算法的实现:
设置一个结构数组Edges存储网中所有的边,边的结构类型包括构成顶点信息和边的权值。
定义如下:
/*
*为kruskal定义边表
*/
typedef struct
{
int v1 , v2 ; //边的两个顶点
int cost ; //边上的权值
} SEdgeType ;
SEdgeType S[MaxVertexNum] ;
为了方便选取当前权值最小的边,事先将数组S中的各元素按照其cost域值从大到小顺序排列。
对于有n个顶点的网,设置一个数组father[n],其初值为father[i]=-1{i = 0,1…,n-1},表示各个顶点在不同的连通分量上面,然后依次取出edges数组中的每条边的两个顶点,查找他们所属的连通分量,假设v1与v2为两个顶点所在的树的根节点在father数组中的序号,若v1不等于v2,表明这两条边的两个顶点不属于同一分量,则将这条边作为最小生成树的边输出,并合并他们所属的连通分量。
算法:
/*
*根据Kruskal算法求得最小生成树
*/
int find(int father[] , int v)
{
int t ;
t = v ;
while(father[t] >= 0)
{
t = father[t] ;
}
return t ;
}
void Kruskal(SEdgeType edges[] , int n , int e)
{
int father[MaxVertexNum] ;
int i , j , v1 , v2 ;
for(i = 0 ; i < e ; i++)
{
father[i] = -1 ;
}
i = 0 ;
j = 0 ;
while((i < e) && (j < n - 1))
{
v1 = find(father , edges[i].v1) ;
v2 = find(father , edges[i].v2) ;
if(v1 != v2)
{
father[v2] = v1 ;
j++ ;
printf("%3d %3d \n" , edges[i].v1 , edges[i].v2) ;
}
i++ ;
}
}
四、测试代码
/*
*构建最小生成树Prim算法,通过邻接矩阵表示图
*/
#include<stdio.h>
#include<malloc.h>
#define MAX 9999
#define MaxVertexNum 100 //最大定点数设为100
typedef char VertexType ; //顶点字符设为字符型
typedef int EdgeType ; //边上权值设为整型
/*
*定义图储存的数据结构
*/
typedef struct
{
VertexType vexs[MaxVertexNum][3] ; //顶点表
EdgeType edges[MaxVertexNum][MaxVertexNum] ; //边表
int n , e ; //顶点数与边数
} MGraph ;
/*
*为kruskal定义边表
*/
typedef struct
{
int v1 , v2 ; //边的两个顶点
int cost ; //边上的权值
} SEdgeType ;
/*
*建立一个无向图的邻接矩阵存储
*/
void CreateMGraph(MGraph *G , SEdgeType S[])
{
int i , j , k , w ;
printf("请输入定点数与边数(输入格式为:顶点数,边数):") ;
scanf("%d,%d" , &G->n , &G->e) ;
printf("请输入顶点信息(输入格式为:顶点号<CR>):\n") ;
for(i = 0 ; i < G->n ; i++)
{
scanf("%s" , G->vexs[i]) ;
}
//初始化邻接矩阵
for(i = 0 ; i < G->n ; i++)
{
for(j = 0 ; j < G->n ; j++)
{
G->edges[i][j] = MAX ;
}
}
printf("请输入每条边对应的两个顶点序号以及权值(输入格式为:i,j,w):\n") ;
for(k = 0 ; k < G->e ; k++)
{
scanf("%d,%d,%d" , &i , &j , &w) ;
S[k].v1 = i ;
S[k].v2 = j ;
S[k].cost = w ;
G->edges[i][j] = w ;
G->edges[j][i] = w ;
}
}
/*
*Prim算法求最小生成树
*从序号为0的顶点出发,建立的最小生成树存于数组closevertex中
*/
void Prim(int gm[][MaxVertexNum] , int n , int closevertex[])
{
int lowcost[MaxVertexNum] , mincost ;
int i , j , k ;
for(i = 1 ; i < n ; i++)
{
lowcost[i] = gm[0][i] ;
closevertex[i] = 0 ;
}
lowcost[0] = 0 ;
closevertex[0] = 0 ;
//寻找当前最小权值的边的顶点
for(i = 1 ; i < n ; i++)
{
mincost = MAX ;
j = 1 ;
k = 1 ;
while(j < n)
{
if(lowcost[j] < mincost && lowcost[j] != 0)
{
mincost = lowcost[j] ;
k = j ;
}
j++ ;
}
printf("顶点的序号 = %d 边的权值 = %d \n" , k , mincost) ;
lowcost[k] = 0 ;
for(j = 1 ; j < n ; j++)
{
//当存在距离更小时,将lowcost改变,并改变closevertex的值
if(gm[k][j] < lowcost[j])
{
lowcost[j] = gm[k][j] ;
closevertex[j] = k ;
}
}
}
printf("最小生成树为:\n") ;
for(i = 0 ; i < n ; i++)
{
printf("%d " , closevertex[i]) ;
}
printf("\n") ;
}
/*
*根据Kruskal算法求得最小生成树
*/
int find(int father[] , int v)
{
int t ;
t = v ;
while(father[t] >= 0)
{
t = father[t] ;
}
return t ;
}
void Kruskal(SEdgeType edges[] , int n , int e)
{
int father[MaxVertexNum] ;
int i , j , v1 , v2 ;
for(i = 0 ; i < e ; i++)
{
father[i] = -1 ;
}
i = 0 ;
j = 0 ;
while((i < e) && (j < n - 1))
{
v1 = find(father , edges[i].v1) ;
v2 = find(father , edges[i].v2) ;
if(v1 != v2)
{
father[v2] = v1 ;
j++ ;
printf("%3d %3d \n" , edges[i].v1 , edges[i].v2) ;
}
i++ ;
}
}
void out(MGraph G)
{
int i , j ;
printf("图G的顶点数与边数为 %d , %d \n" , G.n , G.e) ;
printf("图G的顶点为:");
for(i = 0 ; i < G.n ; i++)
{
printf("%s " , G.vexs[i]) ;
}
printf("\n") ;
printf("图G的邻接矩阵为:\n") ;
for(i = 0 ; i < G.n ; i++)
{
for(j = 0 ; j < G.n ; j++)
{
printf("%04d " , G.edges[i][j]) ;
}
printf("\n") ;
}
}
void sort(SEdgeType edges[] , int e)
{
int i , j ;
SEdgeType temp ;
for(i = 0 ; i < e - 1 ; i++)
{
for(j = i + 1 ; j < e ; j++)
{
if(edges[i].cost > edges[j].cost)
{
temp = edges[j] ;
edges[j] = edges[i] ;
edges[i] = temp ;
}
}
}
}
/*
*进行测试
*/
void main()
{
MGraph *G ;
SEdgeType S[MaxVertexNum] ;
int closevertex[MaxVertexNum] ;
G = (MGraph*)malloc(sizeof(MGraph)) ;
CreateMGraph(G , S) ;
//输出该图信息
out(*G) ;
//运用Prim算法求最小生成树
printf("Prim: \n") ;
Prim(G->edges , G->n , closevertex) ;
//运用Kruskal算法求最小生成树
sort(S , G->e) ;
Kruskal(S , G->n , G->e) ;
free(G) ; //回收内存
}