图 - 最小生成树

发布时间 2023-11-16 15:33:43作者: ww0809

图的最小生成树

最小生成树:包含图的所有n个顶点,且有足以构成一个连通图的(n - 1)条边的极小连通子图。 但满足上述条件的极小连通子图不一定是最小生成树。
下述两个算法用于求无向图的最小生成树。有向图的最小生成树称为最小树形图,此处不做讨论。

Prim普里姆算法

Prim算法,也叫“加点法”,通过从一个结点连接到下一个结点的方式寻找图的最小生成树。

算法步骤

  1. 将图中所有结点分成AB两类,A表示已包含在树中的,B表示还没包含在树中的;
  2. 任选一个结点加入A中,作为起始点;
  3. 在B中所有结点中找到到A中结点的边权值最小的结点,并将其移到A中;
  4. 直到B中没有结点,此时A中包含的所有结点和走过的所有边就构成图的最小生成树。

辅助数据结构

辅助数组closedge用于记录从A到B具有最小权值的边
其中储存的每个元素包含两个域:
lowcost存储最小边的权值, adjvex存储最小边在A中的(那一个)结点

算法实现(严蔚敏 《数据结构》)

void MiniSpanTree_Prim(AMGraph G, VerTexType u) {
    k = LocateVex(G, u); // 定位到u的下标
    for(j = 0; j < G.vexnum; j++)
        if(j != k) closedge[j] = {u, G.arcs[k][j]}; // 初始化为到A中结点的边信息
    closedge[k].lowcost = 0;
    for(i = 1; i < G.vexnum; i++) {
        k = Min(closedge); // closedge[k]存的是当前最小边
        u0 = closedge[k].adjvex; v0 = G.vexs[k]; 
        cout << u0 << " " << v0; // 分别为最小边的两个结点
        closedge[k].lowcost = 0; // 第k个结点并入A
        for(j = 0; j < G.vexnum; j++) // 并入后找新的最小边
            if(G.arcs[k][j] < closedge[j].lowcost)
                closedge[j] = {G.vexs[k], G.arcs[k][j]};
    }
}

算法分析

第一个循环(初始化)时间复杂度O(n);
第二个循环遍历其余(n-1)个结点,时间复杂度O(n);
第二个大循环有两个内循环,一个是求当前最小边,一个是并入新结点后,重新寻找最小边。
所以时间复杂度为O(n²)
与图的边数无关,适用于处理稠密图

Kruskal克里斯卡尔算法

Kruskal算法,也叫“加边法”,通过逐步增加生成树的边来求得最小生成树。

算法步骤

  1. 初始化:将图中所有边按从小到大排序,可以认为每个顶点各自形成一个连通分量;
  2. 选择图中最小的边,如果此边的顶点不与已经有的部分生成树形成回路,就把这条边加入;否则舍弃这条边,选择下一个最小的边;
  3. 重复第二步,直到图中所有结点都位于同一连通分量上,就得到了最小生成树。

辅助数据结构

  1. 结构体数组Edge:存储边的起点终点和权值。
struct {
    VerTexType Head, Tail;
    ArcType lowcost;
} Edge[arcnum];
  1. Vexset[i]:标记各结点所属的连通分量。
    初始时Vexset[i] = i,表示各个结点各成一个连通分量。

算法实现

void MiniSpanTree_Kruskal(AMGraph G) {
    Sort(Edge);
    for(i = 0; i < G.vexnum; i++) Vexset[i] = i;
    for(i = 0; i < G.vexnum; i++) {
        v1 = LocateVex(G, Edge[i].Head), v2 = LocateVex(G, Edge[i].Tail);
        vs1 = Vexset[v1], vs2 = Vexset[v2];
        if(vs1 != vs2) {
            cout << Edge[i].Head << " " << Edge[i].Tail;
            for(j = 0; j < G.vexnum; j++)
                if(Vexset[j] == vs2) Vexset[j] = vs1;
        }
    }
}

算法分析

最耗时的循环是合并两个连通分量。
时间复杂度是O(elog2(e)),与边数相关。适合处理稀疏图。