生成树:在一个连通图G中,取它全部顶点和一部分边构成该图的一个子图G’,若子图的所有边能够使全部顶点连通而不形成任何环,则称子图G’是图G的一个生成树。一个有n个顶点的生成树只有n-1条边。
最小生成树:在一个连通网中,每条边的权值会有不同,各边权值之和最小的生成树成为最小生成树。
构造最小生成树有多种算法,大多数算法利用了最小生成树的MST性质,即:假设**N=(V,{E})**是一个连通网,U是顶点集V的一个非空子集,若(u,v)是一条具有最小权值的边,u∈U,v∈V-U,则必存在一棵包含边(u,v)的最小生成树。
Prim算法和Kruskal算法就是两个利用MST性质构造最小生成树的算法
Prim(普里姆)算法
两个集合:顶点集合U,最初只有一个随机取的顶点;边集合TE,最初是个空集。
重复执行下面操作:在所有u∈U,v∈V-U的边(u,v)∈E中,取一条权值最小的边(u0,v0)放入集合TE,同时v0放入集合U。直至U=V。
//图结构使用前面写过的邻接矩阵的代码
//定义一个辅助数组closedge[],数组的每一个元素代表图中一个顶点。每个顶点有两个属性,adjvex代表该顶点与U中哪个元素相连(可以使与之相连的弧的权值最小),lowcost代表与之相连的弧的权值
typedef struct {
Elemtype adjvex; //U中元素
int lowcost; //权值
}closedge[MAX_VERTEX_NUM];
void MiniSpanTree_Prim(MGraph G,Elemtype u)//从第u个顶点出发构造G的最小生成树
{
int initVex = LocateVex(&G, u);
closedge my_closedge;
for(int i = 0;i<G.vexnum-1;i++) //辅助数组初始化
if (i != initVex)
my_closedge[i] = { u, G.arcs[initVex][i].adj };//与u相连则为权值,不相连则权值为INFINITY
my_closedge[initVex].lowcost = 0; //lowcost设为0标志该顶点已经并入U中
int min = 0;
for (int i = 1; i < G.vexnum; i++) //选择其余G.vexnum-1个顶点
{
for (int n = 0; n < MAX_VERTEX_NUM - 1; n++)//选出与U中顶点相连的、具有最小权值的顶点
{
if (my_closedge[n].lowcost < my_closedge[min].lowcost)
min = n; //序号记在min中
}
printf(。。。);//输出生成树的边
my_closedge[min].lowcost = 0; //将该顶点并入U集
for (int n = 0; n < G.vexnum - 1; n++)//新顶点并入U后更新V-U中顶点的权值和顶点信息
if (G.arcs[min][n].adj < my_closedge[n].lowcost)
my_closedge[n] = { min,G.arcs[min][n].adj };
}
}
Kruskal(克鲁斯卡尔)算法
一个非连通图集合T=(V,{}):拥有全部顶点,没有弧,每个顶点自成一个连通分量
重复执行下面操作:在E中选择权值最小的边,若该边依附的两个顶点位于此时T中两个不同的连通分量中,则将此边加入T中,否则舍弃此边而寻找下一条权值最小的边。直至T中只有一个连通分量。
//Kruskal算法的图结构借助边集数组实现,边集数组由两个一维数组构成。
//一个一维数组存储顶点信息,另外一个一维结构体数组存储边的信息。
//以下是边集数组的实现
#define MAX_ARCTEX_NUM 20
#define INFINITY 65535
typedef char Elemtype;
typedef struct Edge{
int head; //有向图时为弧头指向的顶点序号
int tail; //无向图时为弧的两端连接的顶点序号
int weight; //权重
}Edge,EdgeVec[MAX_ARCTEX_NUM];
//定义图结构
typedef struct EdgeGraph{
Elemtype vexs[MAX_VERTEX_NUM]; //顶点数组存储顶点信息
EdgeVec arcs; //弧数组存储弧信息
int vexnum,arcnum; //顶点数,弧数
}EdgeGraph;
int LocateVex(EdgeGraph *G,Elemtype info)//确定顶点info在顶点数组中的序号
{
for(int i = 0; i<G->vexnum; i++)
if(G->vexs[i] == info)
return i;
}
void CreateEdgeGraph(EdgeGraph *G)
{
printf("请输入图的顶点数量和边的数量:");
scanf_s("%d %d",&(G->vexnum),&(G->arcnum));
for(int i = 0; i < G->vexnum; i++)
{
printf("输入顶点值:");
scanf_s("%c",&(G->vexs[i]));
}
for(int i = 0; i<G->vexnum; i++) //顶点数组初始化
G->arcs[i].weight = INFINITY;
for(int i = 0; i<G->arcnum; i++) //边集数组赋值
{
int weight;
Elemtype head,tail;
printf("请输入边的起点终点和权值:");
scanf_s("%c %c %d",&tail,&head,&weight);
int v1 = LocateVex(G,tail);
int v2 = LocateVex(G,head);
G->arcs[i].weight = weight;
G->arcs[i].head = v2;
G->arcs[i].head = v1;
}
}
//接下来是Kruskal算法的实现
void MiniSpanTree_Kruskal(EdgeGraph *G)
{
Sort(G); //对EdgeVec[]即边集数组进行排序,得到权值从小到大的数组
int *Vexset; //需要设置一个辅助数组,用于记录各顶点是否在同一个连通分量
Vexset = (int*)malloc(sizeof(int)*G.vexnum); //为辅助数组动态分配内存
for (i = 0; i < G.vexnum-1; i++)
Vexset[i] = i;
int v1,v2,vs1,vs2;
for(int i = 0; i<G.arcnum-1; i++)
{
v1 = LocateVex(&G,EdgeVec[i].head); //弧连接的顶点的序号
v2 = LocateVex(&G,EdgeVec[i].tail);
vs1 = Vexset[v1]; //顶点所在连通分量的标记
vs2 = VexSet[v2];
if(vs1 != vs2) //弧的两端不在同一连通分量
{
printf("%c -> %c \n",EdgeVec[i].head,EdgeVec[i].tail);
for(int j = 0;j<G.vexnum-1;j++)
{
if(Vexset[j] == vs2) //所有和vs2在同一连通分量的顶点加入连通分量vs1
Vexset[j] = vs1;
}
}
}
}
//快速排序算法,将边集数组按权值大小从新排序
void Sort(EdgeGraph *G)
{
int min_weight,addr;
EdgeVec temp;
for (int i = 0; i < G.arcnum-1; i++)
{
min_weight = EdgeVec[i].weight;
addr = i;
for (int j = i + 1; j<G.arcnum-1;j++)
{
if (min_weight>EdgeVec[j].weight)
{
min_weight = EdgeVec[j].weight;
addr = j;
}
}
if (addr != i)
{
temp = EdgeVec[i];
EdgeVec[i] = EdgeVec[addr];
EdgeVec[addr] = temp;
}
}
}