最小生成树

本文深入探讨了最小生成树的概念及其应用,详细介绍了Kruskal算法和Prim算法的构建思路、性质及实现过程。通过算法的证明与代码实现,阐述了如何在无向连通图中寻找最小权重生成树,为解决实际问题提供了一种有效的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

        在一给定的无向图G = (V, E) 中,(u, v) 代表连接顶点 u 与顶点 v 的边(即),而 w(u, v) 代表此边的权重,若存在 T 为 E 的子集(即)且为无循环图,使得 w(T) 最小,则此 T 为 G 的最小生成树。最小生成树其实是最小权重生成树的简称。

140109_Tkqj_572632.jpg


        许多应用问题都是一个求无向连通图的最小生成树问题。例如:要在n个城市之间铺设光缆,主要目标是要使这 n 个城市的任意两个之间都可以通信,但铺设光缆的费用很高,且各个城市之间铺设光缆的费用不同;另一个目标是要使铺设光缆的总费用最低。这就需要找到带权的最小生成树。

140220_ydGU_572632.jpg

构建思路

       在每一次循环迭代前,A是某个最小生成树的一个子集.在算法的每一步确定一条边(u,v),使得其加入集合A后,集合A任然保持为一棵最小生成树,而每次加入的边(u,v)称为安全边(即是加入后不破坏循环不变式的边)

GENERIC-MST(G, w)
   A = 空集
   while A does not form a spanning tree
         find an edge (u,v) that is safe for A
         add (u, v) into A                   
return A

由上可见,寻找安全边是算法的关键,下面对最小生成树的部分性质进行证明。从而解决该问题.

最小生成树性质

143853_DAvG_572632.jpg

143903_ohck_572632.jpg

143912_T7Zn_572632.jpg

143921_5BBl_572632.jpg

143935_Y4VJ_572632.jpg

143944_Pv1Q_572632.jpg

算法实现

Kruskal(克鲁斯卡尔)算法

概述

      Kruskal算法是一种用来寻找最小生成树的算法,由Joseph Kruskal在1956年发表。用来解决同样问题的还有Prim算法和Boruvka算法等。三种算法都是贪婪算法的应用。和Boruvka算法不同的地方是,Kruskal算法在图中存在相同权值的边时也有效。

伪代码

下面代码是以算法导论中的kruskal的伪代码改写而来的,总体思路为:
1.按照权值由低到高的顺序寻找安全边,并将其加入最小生成树中.
2.在安全边的选择上还需注意当发现该边的端点都在已构建完成的最小生成树中时表明如果加入该边将形成环,因此这种情况认为该边不安全.(简而言之安全边就是不破环循环不变式的边)
4.该算法的成立依赖于前面证明的最小生成树的性质.

MST-KRUSKAL.G(G, w)
tree = 空
sort the edges of G.E into nondecreasing order by weight w
foreach  edge(u,v)  in G.E, taken in nondecreasing order by weight
    do if u and v are not both in the same tree
          add edge (u, v) into tree
          
return tree

095259_X955_572632.jpg

代码实现

/**
 * @brief code for spanning tree
 * @author xiyan
 * @date 2014/07/01
 *
 */
#include <vector>
#include <iostream>
#include <algorithm>
namespace sptree{
using namespace std;
class Edge{
    public:
        int lft;
        int rht;
        int weight;
};
struct edgeComapre {
      bool operator() (Edge lft, Edge rht) { return (lft.weight < rht.weight);}
} edgeCompareObj;
class Kruskal{
    public:
        Kruskal(void):tot(0) { tmpClean(); }
        int init(const int &posSize);
        int insert(const class Edge nedge);
        int build(void);
        virtual ~Kruskal(void){ tmpClean();  }
    private:
        void tmpClean(void);
        int findSet(const int &pos);
        void makeUnion(const int &lft, const int &rht);
        vector<class Edge> edges;
        int *fatherA;
        int  posSize;
        int tot;
};

int Kruskal::insert(const class Edge nedge) {
        if(nedge.lft >= posSize || nedge.rht >= posSize)
            return - 1;
        edges.push_back(nedge);
        return 0;
}

void Kruskal::tmpClean(){
        edges.clear();
        if(fatherA){
            delete  fatherA;
            fatherA = NULL;
        }
}

int Kruskal::findSet(const int &pos){
    int rootPos;
    int currPos = pos;
    while(fatherA[currPos] != currPos){
        currPos = fatherA[currPos];
    }
    rootPos = currPos;
    currPos = pos;
    while(currPos != rootPos){
            int fatherPos = fatherA[currPos];
            fatherA[currPos] = rootPos;
            currPos = fatherPos;
    }
    return rootPos;
}

int Kruskal::init(const int &iposSize){
    if(iposSize <= 0)
            return -1;
    posSize = iposSize;
    if( NULL == ( fatherA = new int[posSize])){
            return -1;
    }

    for(int currPos = 0; currPos  < posSize; currPos++){
            fatherA[currPos] = currPos;
    }
    return 0;
}

int Kruskal::build(void){
    int ret = 0;
    sort(edges.begin(), edges.end(), edgeCompareObj);
    for(vector<class Edge>::iterator iter =  edges.begin(); iter != edges.end(); iter++){
            int lftRoot = findSet(iter->lft);
            int rhtRoot = findSet(iter->rht);
            if(lftRoot == rhtRoot){
                continue;
            }
            makeUnion(lftRoot, rhtRoot);
            ret += iter->weight;
    }
    tmpClean();
    return ret;
}

void Kruskal::makeUnion(const int &lft, const int &rht){
        fatherA[lft] = rht;
}
}

using namespace sptree;
int main(){
    class Kruskal *krup  = new class Kruskal;
    class Edge nedge;
    if(!krup){
            cout << "new kruskal fail" << endl;
            return -1;
    }
    if(krup->init(20) < 0){
        cout << "set max pos fail" << endl;
        return -1;
    }
    cout << "please input info for every edge" << endl;
    while(cin >> nedge.lft >> nedge.rht >> nedge.weight){
                    krup->insert(nedge);
                    cout << "please input info for every edge" << endl;
    }
    cout << krup->build() << endl;
    return 0;
}


普里姆(Prim算法)算法

概述

      图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (graph theory)),且其所有边的权值之和亦为最小。该算法于1930年由捷克数学家沃伊捷赫&middot;亚尔尼克(英语:Vojtěch Jarn&iacute;k)发现;并在1957年由美国计算机科学家罗伯特&middot;普里姆(英语:Robert C. Prim)独立发现;1959年,艾兹格&middot;迪科斯彻再次发现了该算法。因此,在某些场合,普里姆算法又被称为DJP算法、亚尔尼克算法或普里姆-亚尔尼克算法。

伪代码

/*该算法依赖于定理23.2*/
MST-PRIM(G,w, r)          /*初始化非起始节点*/
for each u 属于 V - {r}
         u.key = 无穷大
         u.father = NIL
         push u into Q 
         
r.key = 0;                  /*初始化起始节点*/         
r.father = r;
push u into Q 

while Q != 空
        u =  pop element from Q wich with  min key
        for each edge (u, v)
              if v not in Q  and w(u , v) < v.key
                    v.key = w(u , v)
                    v.father = u

130430_QHGH_572632.jpg

Boruvka算法

参考文章

  1. <算法导论>

  2. http://baike.baidu.com/view/288214.htm?fr=aladdin

  3. http://www.cnblogs.com/biyeymyhjob/archive/2012/07/30/2615542.html

IN BUILDING


转载于:https://my.oschina.net/u/572632/blog/284010

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值