最小生成树(Minimum-cost Spanning Trees)(Prim算法&&Kruskal算法)

最小生成树

源于华文慕课网

这里写图片描述

PRIM 算法

  • 从图中任意一点开始(例如V0),首先把这个顶点包括在MST,U=(V*,E*)里。【初始V*={0},E*={}
  • 一端已在MST里,另一端还不在MST里的边中,找一条权值最小的边VpVq,并将此Vq包括进MST里
  • 如此进行下去,每次往MST里加一个顶点和一条权值最小的边,直到把所有顶点都加入MST里

代码

void Prim(Graph& G, int s, Edge* &MST)          // s是起始点,MST存边
{                                               
    int MSTtag = 0;                             // 最小生成树的边计数
    MST = new Edge[G.VerticesNum()-1];          // 为数组MST申请空间
    Dist *D;                                    
    D = new Dist[G.VerticesNum()];              // 为数组D申请空间
    for (int i=0;i<g.verticesNum();i++){        // 初始化Mark和D数组
        G.Mark[i] = UNVISITED;
        D[i].index = i;
        D[i].length = INF;
        D[I].pre = s;
    }

    D[s].length = 0;                            // 开始顶点标记为VISITED
    G.Mark[s] = VISITED;
    int v = s;
    for (int i=0;i<G.VerticesNum()-1;i++){
        //  因为v的加入,需要刷新与v相邻接的顶点的D值
        for (Edge e = G.FirstEdge(v); G.IsEdge(e); e = G.NextEdge(e)){
            if (G.Mark[G.ToVertex(e)]!=VISITED && D[G.ToVertex(e)].length>e.weight){
                D[G.ToVertex(e)].length = e.weight;
                D[G.ToVertex(e)].pre = v;
            }
        }
        v = minVertex(G,D)                      // 在D数组中找最小值记为v
        if (v == -1) retrun;                    // 非连通,有不可达顶点
        G.Mark[v] = VISITED;                    //标记访问过
        Edge edge(D[v].pre, D[v].index, D[v].length);   // 保存边
        AddEdgetoMST(edge,MST,MSTtag++);                // 将边加入MST
    }
}

// 在Dist数组中找最小值
int minVertex(Graph& G,Dist* &D)
{
    int i,v = -1;
    int MinDist = INF;
    for (i=0; i<G.VerticesNum();i++){
        if((G.Mark[i] == UNVISITED) && (D[i] < MinDist)){
            v = i;              //保存当前发现的最小距离顶点
            MinDist = D[i];
        }
    }
    return v;
}

Prim算法的时间复杂度

  • Prim算法非常类似于Dijkstra算法
    • Prim算法框架与Dijkstrra算法相同
    • Prim算法中的距离值不需要累积,直接用最小边
  • 本算法通过直接比较D数组元素,确定代价最小的边就需要总时间O(n2);取出权最小的顶点后,修改D数组共需要时间O(e),因此共需要花费O(n2)的时间
    • 算法适用于稠密图
    • 对于稀疏图,可以像Dijkstra算法那样用来保持距离值

Kruskal 算法

  • 首先将G中的n个顶点看成独立的n个连通分量,这时的状态是有n个顶点而无边的森林,可以记为<V,{}>
  • 然后在E中选择代价最小的边,若该边依附于两个不同的分支,那么将这条边加入到T中,否则舍去这条边而选择下一条代价最小的边
  • 依此类推,直到T中所有顶点都在同一个连通分量中为止,此时得到图G的最小生成树

代码

void Kruskal(Graph& G, Edge* &MST)                  // MST存最小生成树的边
{
    ParTree<int> A(G.VerticesNum());                // 等价类
    MinHeap<Edge> H(G.EdgesNum());                  // 最小堆
    MST = new Edge[G.VerticesNum()-1];              // 为数组MST申请空间
    int MSTtag = 0;                                 // 最小生成树的边计数
    for (int i=0; i<G.VerticesNum(); i++){          // 将所有边插入最小堆H中
        for (Edge e=G.FirstEdge(i); g.IsEdge(e); e=g.NextEdge(e)){
            if(G.FromVertex(e) < G.ToVertex(e))     // 防重复边
                H.Insert(e);
        }
    }
    int EquNum = g.VerticesNum();                   // 开始有n个独立顶点等价类
    while (EquNum > 1){                             // 当等价类的个数大于1时合并等价类
        if (H.isEmpty()){
            cout<<"不存在最小生成树。"<<endl;
            delete [] MST;
            MST = NULL;                             //释放空间
            return;
        }
        Edge e = H.RemoveMin();                     // 取权值最小的边
        int from = G.FromVertex(e);                 // 记录该条边的信息
        int to = G.ToVertex(e);
        if (A.Different(from,to)){                  // 若边e的两个顶点不在一个等价类
            A.Union(from,to);                       // 合并边的两个顶点所在的等价类
            AddEdgetoMST(e,MST,MSTtag++);           // 将边e加到MST
            EquNum--;                               // 等价类的个数减1
        }
    }
}

Kruskal算法的代价

  • 使用了路径压缩,Different()和Union()函数几乎是常数
  • 假设可能对几乎所有边都判断过了
    • 则最坏情况下算法时间代价为Θ(elog e),即堆排序的时间
  • 通常情况下只找了略多于n次,MST就已经生成
    • 时间代价接近于 Θ(nlog e)

猜你喜欢

转载自blog.csdn.net/qq_29159273/article/details/53420032
今日推荐