RodneyX

博客园 首页 新随笔 联系 订阅 管理

克鲁斯卡尔算法是一种常见的用于构造最小生成树的算法,其基本思想是选择n-1(n为顶点数)条权值最小的,且不构成环的边。由于需要选择n-1一条最小权值的边,故可以以这些边为元素依据边权值作堆排序来进行选择。另外,判断构成环与否的方式是利用并查集,检查一条边的两个顶点,若两个顶点都属于某个集合那么选择这条边必然产生回路,即边(i, j)的根是k,那么顶点ik与顶点jk分别有路径,加入边(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);
}
posted on 2025-08-02 14:27  RodneyX  阅读(23)  评论(0)    收藏  举报