注意:
1、邻接表中以边为堆的构建用Edge类型的ESet数组存储,无向图存储边时只存储一次,即要保证V < W->Adj,有向图即存储两边。
2、存储完成后将当前边集合通过PerDown操作调整为最小堆;
3、以及在Kruskal算法中每次获取堆顶最小元素时的函数,GetMinEdge操作时,先将堆顶最小元素与最后一个元素交换,再将从堆顶为根的堆除了最后一个最小元素进行PerDown操作,调整为最小堆,返回当前最后一个元素的下标。
4、关于检查选的边是否会使MST成环,用并查集来做,并查集的查与并(路径压缩)(递归及非递归)
5、最小生成树:边数要等于顶点数-1
参考代码:
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
#define INF 0x3f3f3f3f
#define MAX 1010
using namespace std;
int S[MAX];
int dist[MAX];
int parent[MAX];//存储当前节点的父节点
typedef struct ENode
{
int V1, V2;
int Weight;
}*Edge;
struct AdjNode
{
int Adj;
AdjNode *Next;
int Weight;
};
typedef struct VNode
{
AdjNode *First;
int Data;
}*AdjList[MAX];
//邻接表
typedef struct LGNode
{
int Vertex_num;
int EdgeNum;
AdjList G;
}*LGraph;
//图初始化
LGraph InitLGraph(int VertexNum)
{
LGraph G;
G = (LGraph)malloc(sizeof(LGNode));
G->Vertex_num = VertexNum;
G->EdgeNum = 0;
for(int i = 0; i < G->Vertex_num; i++)
G->G[i]->First = NULL;
return G;
}
//图中边的插入
void InsertEdge(LGraph G, Edge E)
{
AdjNode *NewNode;
//为V2建立邻接点
NewNode = (AdjNode*)malloc(sizeof(AdjNode));
NewNode->Adj = E->V2;
NewNode->Weight = E->Weight;
//将V2插入V1的表头------头插法
NewNode->Next = G->G[E->V1]->First;
G->G[E->V1]->First = NewNode;
}
LGraph CreatLGraph(int Vertex)
{
LGraph G;
G = InitLGraph(Vertex); //图初始化
return G;
}
/*-------------------- 并查集定义开始 --------------------*/
void InitSet()
{
memset(S, -1, sizeof(S));
}
int FindSet(int X)
{
if(S[X] < 0) return X;
int R = X;
while(S[S[X]] >= 0)
{
R = S[X];
S[X] = S[R];
}
return S[X];
}
void UnionSet(int Root1, int Root2)
{
if(Root1 < Root2)
{
S[Root1] += S[Root2];
S[Root2] = Root1;
}
else
{
S[Root2] += S[Root1];
S[Root1] = Root2;
}
}
bool CheckCircle(int X, int Y)
{
int Root1 = FindSet(X);
int Root2 = FindSet(Y);
if(Root1 == Root2) return false;
else
{
UnionSet(Root1, Root2);
return true;
}
}
/*-------------------- 并查集定义结束 --------------------*/
/*-------------------- 边的最小堆定义 --------------------*/
//边存储到Edge类型的数组从0开始存储,进行PerDown操作按Weight使其变为最小堆,N为边数
void PerDown(Edge *ESet, int p, int N)
{
int Parent, Child;
Edge X = ESet[p];
//数组从0开始存储,左儿子为*2+1,边数从0开始存储
for(Parent = p; Parent*2+1 <= N-1; Parent = Child)
{
Child = Parent*2+1;
//左儿子不是最后一个边;找出两者最小值
if(Child != N-1 && ESet[Child]->Weight > ESet[Child + 1]->Weight)
Child++;
//若父亲小于左右最小值,结束循环
if(X->Weight <= ESet[Child]->Weight) break;
//若左右最小值比父亲还小,交换,下滤
else ESet[Parent] = ESet[Child];
}
}
//将邻接表的边存储入Edge的数组
void InitEdgeSet(LGraph G, Edge *ESet)
{
int V, ENum = 0;//边数
AdjNode *W;
for(V = 0; V < G->Vertex_num; V++)
{
for(W = G->G[V]->First; W; W = W->Next)
{
//无向图,只录入一次边,即V->W;
if(V < W->Adj)
{
ESet[ENum]->V1 = V;
ESet[ENum]->V2 = W->Adj;
ESet[ENum]->Weight = W->Weight;
ENum++;//边数累加
}
}
}
//将边数组进行调整使其变为最小堆
for(int i = G->EdgeNum / 2; i >= 0; i--)
{
PerDown(ESet, i, G->EdgeNum);
}
}
//从最小堆取得最小边,CurrentSize当前堆的大小,从0开始,最后一个为CurrentSize-1
int GetMinEdge(Edge *ESet, int CurrentSize)
{
//将堆顶与最后一个元素交换
swap(ESet[0], ESet[CurrentSize - 1]);
//继续调整,从堆顶开始,调整除了最后一个元素的剩下部分
PerDown(ESet, 0, CurrentSize - 1);
//返回当前堆的最小值编号,即最后一个编号
return CurrentSize - 1;
}
/*-------------------- 最小堆定义结束 --------------------*/
int Kruskal(LGraph G, LGraph MST)
{
//权重和
int TotalWeight = 0;
//最小边的编号以及MST已收录的边数记录
int NextEdge, EdgeNum = 0;
Edge *ESet;
//初始化顶点集合
InitSet();
//初始化边数组
ESet = (Edge*)malloc(G->EdgeNum*sizeof(ENode));
InitEdgeSet(G, ESet);
//初始化最小生成树
MST = CreatLGraph(G->Vertex_num);
NextEdge = G->EdgeNum;
//最小生成树:边数 = 顶点数-1
while(EdgeNum < G->Vertex_num - 1)
{
//获得当前堆的最小元素下标
NextEdge = GetMinEdge(ESet, NextEdge);
//边集合已空
if(NextEdge < 0) break;
//若此边的端点不是一个集合,即不会成环;
if(CheckCircle(ESet[NextEdge]->V1, ESet[NextEdge]->V2))
{
//将当前边插入MST
InsertEdge(MST, ESet[NextEdge]);
//总权重以及收录的边数进行累加
TotalWeight += ESet[NextEdge]->Weight;
EdgeNum++;
}
}
//若已收录边小于顶点数-1,即最小生成树不存在,标记为-1
if(EdgeNum < G->Vertex_num - 1) TotalWeight = -1;
return TotalWeight;
}
int main()
{
return 0;
}