最小生成树
总的来说,最小生成树覆盖图中所有顶点以及|V|-1条边。
与dijkstra算法类似,都需要对每一个顶点保存一个距离值dist[V]和parent[V],以及一个visit指标,标记是否已经过改点。parent[V]则表示该结点的父结点。不同点为,dist[V]的定义不同: dijkstra算法里dist[V]的定义是源点S到各个点之间的最短距离,而prim算法里的dist[V]则是顶点V到已有的最小生成树MST的最短距离。
下面我们对下面这幅图求其最小生成树:
假设我们从顶点v1开始,所以我们可以发现(v1,v3)边的权重最小,所以第一个输出的边就是:v1—v3=1
然后,我们要从v1和v3作为起点的边中寻找权重最小的边,首先了(v1,v3)已经访问过了,所以我们从其他边中寻找,发现(v3,v6)这条边最小,所以输出边就是:v3—-v6=4
然后,我们要从v1、v3、v6这三个点相关联的边中寻找一条权重最小的边,我们可以发现边(v6,v4)权重最小,所以输出边就是:v6—-v4=2.
然后,我们就从v1、v3、v6、v4这四个顶点相关联的边中寻找权重最小的边,发现边(v3,v2)的权重最小,所以输出边:v3—–v2=5
然后,我们就从v1、v3、v6、v4,v2这2五个顶点相关联的边中寻找权重最小的边,发现边(v2,v5)的权重最小,所以输出边:v2—–v5=3
最后,我们发现六个点都已经加入到集合U了,我们的最小生成树建立完成。
int Prim(MGraph Graph, LGraph MST)
{
MST = {s};
while (1){
V = 未收录顶点中dist最小者;
if ( 这样的V不存在 )
break;
将V收录进MST中;
TotalWeight += dist[V];
dist[V] = 0;
for ( V的每个邻接点W )
if (collected[W] == false && dist[V] + E<V, W> < dist[W]){
dist[W] = dist[V] + E<V, W>;
parent[W] = V;
}
}
if (MST中收录的顶点数 < |V|)
ERROR("生成的树不存在");
return TotalWeight;
}
****************************************/
/**** 用邻接矩阵存储图的Prinm算法 *******/
#define MaxvertexNum N //(N为顶点的最大个数)
#define INFINITY 65533
#defint ERROR -1
typedef int WeightType;
typedef int Vertex;
Vertex FindMinDist(MGraph Graph, int *dist)
{
Vertex V, MinV;
int MinDist = INFINITY;
for (V = 0; V < Graph->Nv; V++){
if (dist[V] != 0 && dist[V] < MinDist){
MinDist = dist[V];
MinV = V;
}
}
if (MinDist < INFINITY)
return MinV;
else return ERROR;
}
int Prinm(MGraph Graph, LGraph MST)
{//将最小生成树保存为邻接表存储的图MST,返回最小权重和TotalWeight
WeightType dist[MaxVertexNum], TotalWeight;
Vertex parent[MaxVertexNum], V, W;
int Vcount;//收录的顶点数
Edge E;
//初始化,默认初始点下标为0,即从初始顶点开始让小树长大
for (V = 0; V < Graph->Nv; V++){
//这里假设若V到W没有直接的边,则Graph->G[V][W] = INFINITY
dist[V] = Graph->G[0][V];
parent[V] = 0;//暂且定义所有顶点的父节点均为初始结点0
}
Totalweight = 0;//初始化权重和
Vcount = 0;//初始化收录的顶点数
//创建MST并初始化,即建立含有Graph->Nv个顶点但边为0的空MST,用邻接表存储
MST = CreateLGraph(Graph->Nv);
E = (Edge)malloc(sizeof (struct ENode));//建立空的边结点
//将初始顶点录入MST中
dist[0] = 0;
parent[0] = -1;//表示当前最小生成树的树根是初始顶点0
Vcount ++;
while (1){
V = FindMinDist(Graph, dist);//V = 未收录顶点中dist最小者
if (V == ERROR)
break;
//将V及相应的边<parent[V], V>收录进MST
E->V1 = parent[V];
E->V2 = V;
E->Weight = dist[V];
InsertEdge(MST, E);
TotalWeight += dist[V];
Vcount ++;//收录顶点数+1
dist[V] = 0;
for (W = 0; W < Graph->Nv; W++){
if (dist[W] != 0 && Graph->G[V][W] < INFINITY)
if (dist[V] + Graph-G[V][W] < dist[W]){
dist[W] = dist[V] + Graph->G[V][W];
parent[W] = V;
}
}
}//while结束
if (Vcount < Graph->Nv)//MST中收录的顶点个数小于|V|个
TotalWeight = ERROR;
return TotalWeight; //算法执行完毕,返回最小权值和或者错误标记
}
-
Kruskal算法思路
- 简单来说,就是将一个个单独的森林合并成一棵树。
- 其思想就是直接了当的贪心,每次都将权值最短的边收进来:可以将每个顶点都看成一个森林,然后将权值最短的边的顶点连接起来,在不构成回路的情况下,将这些森林合并成一棵树。
- 存在三个问题:
- 如何选择一条权重最小的边;因为权值是不变的,所以可以事先按照权值升序排列,这项工作可以采用效率较高的排序算法,时间复杂度为O(|E|log|E|);也可以使用最小堆先将图中的边存储起来,每次选择堆顶元素并删除重新调整为最小堆,时间复杂度为O(|E|log|E|)。
- 判断新加入一条边后会不会在MST中构成回路:这项工作可以使用并查集。
- 如何合并两个树:使用并查集中的Union。
- 下面我们对这幅图求最小生成树:
1.选取一条最小边<V1, V3>,权重为1 2.选取一条最小边<V4, V6>,权重为2 3.选取一条最小边<V2, V5>,权重为3 4.选取一条最小边<V3, V6>,权重为4 5.不能选取最小边<V3, V4>,权重为5,会构成回路 6.不能选取最小边<V1, V4>,权重为5,会构成回路 7.选取一条最小边<V2, V3>,权重为5 8.选取一条最小边<V5, V6>,权重为6 9.录入顶点数 = |V| 且录入边的数量 = |V| - 1,结束
-
伪代码
int Kruskal(Lgraph Graph, LGraph MST)
{
MST = 包含所有顶点但没有边的图; //邻接表存储;
while (MST中收集的边 < Graph->Nv && 原图的边集E非空){
从E中选择一条权重最小的边E<V, W>; //利用最小堆完成
从E中删除边E<V, W>;
if (E<V, W>不在MST中构成回路) //利用并查集完成
将E<V, W>加入MST;
else
彻底无视E<V, W>;
}
if (MST中边的个数 < Graph->Nv - 1)
ERROR("生成树不存在");
else
return 最小权重和;
}
由于该算法使用了较多的结构,在函数传参方面有点复杂,我在写代码片段的过程中边写边画思维导图,发现对理清思路有奇效,不至于产生混乱,但速度还是挺慢,还是基础不牢靠。下面贴一下我在敲代码过程中画的思维导图,主要作用是 理清每种结构参数相互之间的关系,有利于不同结构之间参数的传递。
/************** 最小堆的定义 ************
/*1.最小堆中存放的结点是原始图的边结点
/*2.若原始图是无向图,则需要避免重复存边结点
/*3.在找出权重最小的边后,需要从最小堆中将该边结点删除
*****************************************/
struct ENode {
Vertex V1, V2;
WeightType Weight;
};
typedef ENode *Edge;
//建堆
struct EHNode {
Edge *Data;//存储边结点的数组
int size;
};
typedef struct EHNode *EHeap
typedef EHeap MinEHeap;
MinEHeap BuildEHeap(MGraph Graph, MinEHeap EH )
{//建立存放边结点的最小堆,存储的包括边权重和边的两个顶点
//需要先将边按顺序存入堆,然后调整为最小堆
Vertex V, W;
for (V = 0; V < Graph->Nv; V++){
for (W = 0; W < Graph->Nv; W++){
if (V < W){//避免重复录入无向图的边
EH->Data[size].V1 = V;
EH->Data[size].V2 = W;
EH->Data[size].Weight = Graph->G[V][W];
size ++;
}
}
}//边结点按顺序存入堆,结束
//调整为最小堆,从最后一个父节点开始,到根结点0
for (int i = EH->size /2; i > 0; i-- )
PercDown(EH, i);
return EH;
}
void PercDown(MinEHeap EH, int p)
{//往下过滤,将EHeap中以EHeap->Data[p]为根的子堆调整为最小堆
int Parent, Child;
Edge X = EH->Data[p];
for (Parent = p; Parent*2 <= EH->size ; Parent = Child){
Childe = Parent * 2;
if ((Child != EH->size ) && (EH->Data[Child].Weight > EH->Data[Child + 1].Weight))
Child ++;
if (X.Weight > EH->Data[Child].Weight )
//下滤
EH->Data[Parent] = EH->Data[Child];
else break;
}
EH->Data[Parent] = X;//Parent即为X插入的位置
}
Edge DeleteMin(MinEHeap EH)
//堆的删除与堆的调整非常类似
{
Edge MinE = EH->Data[1];
//用最小堆中最后一个元素从根结点开始往上过滤下层结点
Edge X = EH->Data[EH->size --];//注意当前堆的规模要减小
EH->Data[1] = X;
PercDown(EH, 1);//从根结点开始往上过滤下层结点
return MinE;
}
/**********************并查集*******************************
/* 1.E<V, W>是从最小堆中选出来的权重最小边结点
/* 2.E<V, W>是否在MST中构成回路:利用并查集Find进行判断,然后用Union将不构成回路的两个定点结合
/* 3.将E<V, W>加入MST中:InsertEdge
************************************************************/
typedef Vertex SetType[MaxVertexNum];//假设集合元素下标从0开始
void InitiakizeVset(int N, SetType Vset)
//初始化顶点集合
{
for (int i = 0; i < N; i++)
Vset[i] = -1;
}
bool CheckCycle(SetType Vset, Edge E)
//判断E<V, W>是否在MST中构成回路
{
Vertex Root_1, Root_2;
Root_1 = Find(Vset, E->V1 );
Root_2 = Find(Vset, E->V2 );
if (Root_1 == Root_2)
return false;
else{
Union(Vset, Root_1, Root_2);
return true;
}
}
Vertex Find(SetType Vset, Vertex V)
//寻找顶点V的父节点
{
if (Vset[V] < 0)
return V;
else
return Find(Vset, Vset[V]);//路径压缩
}
void Union(SetType Vset, Vertex Root_1, Vertex Root_2)
//这里默认Root_1和Root_2是不同集合的根结点
//保证小集合并入大集合
{
if (Vset[Root_1] < Vset[Root_2]){//集合1比集合2大
Vset[Root_1] += Vset[Root_2];
Vset[Root_2] = Root_1;
}else {//集合2比集合1大
Vset[Root_2] += Vset[Root_1];
Vset[Root_1] = Root_2;
}
}
//主程序框架
int Kruskal(MGraph Graph, LGraph MST)
//将最小生成树保存为邻接表存储的图MST中,并返回最小权重和
{
WeightType TotalWeight;
Edge MinE = (Edge)malloc(sizeof (struct ENode));
MinEHeap EH = (MinEHeap)malloc(sizeof (struct EHNode));
EH->Data = (Edge)malloc(sizeof (struct ENode) * Graph->Ne);
EH->size = 1;
int cnt = 0;
SetType Vset = (Vertex)malloc(sizeof (Vertex) * Graph->Nv);
InitiakizeVset(Graph->Nv, Vset);
EH = BuildEHeap(Graph, EH );//针对原始图中的每条边,建最小堆
while ((cnt < Graph->Nv - 1) && (!IsEmpty(EH)) ){
MinE = DeleteMin(EH);
if ( CheckCycle(Vset, MinE) ){
InsertEdge(MST, MinE);
cnt ++;
TotalWeight += MinE->Weight ;
}
}
if (cnt < Graph->Nv - 1)
TotalWeight = -1;//设置错误标记,表示最小生成树不存在
return TotalWeight;
}