克鲁斯卡尔算法是一种常见的用于构造最小生成树的算法,其基本思想是选择n-1
(n为顶点数)条权值最小的,且不构成环的边。由于需要选择n-1
一条最小权值的边,故可以以这些边为元素依据边权值作堆排序来进行选择。另外,判断构成环与否的方式是利用并查集,检查一条边的两个顶点,若两个顶点都属于某个集合那么选择这条边必然产生回路,即边(i, j)
的根是k
,那么顶点i
到k
与顶点j
到k
分别有路径,加入边(i, j)
自然构成环了
kruskal算法思想非常简单,利用堆排序的方式,算法时间复杂度在O(|E|log|E|)
,对于边少的稀疏图非常有效。现在我们可以获得如下实现
// 新引入的数据结构,用于存储边的弧头、弧尾顶点以及权重
//typedef struct MSTarc {
// arcNode *adjArc;
// int arcHead;
//}MSTarc;
// 复用数据结构邻接表,见 https://www.cnblogs.com/RodneyTang/p/19010305
// 复用数据结构并查集,见 https://www.cnblogs.com/RodneyTang/articles/19014931
void _adjust_min(MSTarc *heap, int anum, int pos)
{
MSTarc arc = heap[pos];
int i = pos, k;
while (i < anum)
{
k = 2 * i + 1;
if (k + 1 < anum && heap[k].adjArc->weight > heap[k + 1].adjArc->weight)
++k;
if (k < anum && arc.adjArc->weight > heap[k].adjArc->weight)
{
heap[i] = heap[k];
i = k;
}
else
break;
}
heap[i] = arc;
}
void _create_minHeap(MSTarc *heap, int anum)
{
for (int i = (anum - 1) / 2; i >= 0; --i)
_adjust_min(heap, anum, i);
}
void kruskal(adjList &aGraph, MSTarc T[])
{
if (aGraph.vnum <= 1)
return;
// 初始化顶点并查集
int *S = (int *)malloc(sizeof(int) * aGraph.vnum);
init_disjointSet(S, aGraph.vnum);
// 构造边的小顶堆
int realEdge_num = aGraph.anum / 2;
MSTarc *heap = (MSTarc *)malloc(sizeof(MSTarc) * realEdge_num);
int count = 0;
for (int i = 0; i < aGraph.vnum; ++i)
for (arcNode *p = aGraph.vexlist[i].firstarc; p != nullptr; p = p->nextarc)
if (p->adjVex > i)
{ // 无向图只存储上三角矩阵以提高效率
heap[count].adjArc = p;
heap[count++].arcHead = i;
}
_create_minHeap(heap, realEdge_num); // Heapfy
int inMST = 0; // 记录已经加入边数量
for (int i = 0; i < realEdge_num && inMST < aGraph.vnum - 1; ++i)
{ // 终止条件是找到n - 1条边(n个顶点)或者边的小顶堆筛选完毕
int root1 = root_disjointSet(S, aGraph.vnum, heap[0].arcHead),
root2 = root_disjointSet(S, aGraph.vnum, heap[0].adjArc->adjVex);
if (root1 != root2)
{ // 如果不构成环则
union_disjointSet(S, aGraph.vnum, heap[0].arcHead, heap[0].adjArc->adjVex); // 加入此边,合并两个顶点对应集合
T[inMST++] = heap[0]; // we don't need deep copy because T[] is read only and reliability is asured by caller
}
heap[0] = heap[realEdge_num - 1 - i]; // heap[0]无用了直接覆盖掉,不需要像纯堆排序那样作堆首尾元素交换
if (realEdge_num - 1 - i > 0) // 保证对辅助函数的正确传参
_adjust_min(heap, realEdge_num - 1 - i, 0);
}
free(S);
free(heap);
}