• 数据结构-图之最小生成树


    一、最小生成树及其性质

    1. 最小生成树(Minimum Spanning Tree,MST)是在给一个无向图中求一棵树T,使得这棵树拥有图G中的所有顶点,且边来自图中的边,并且满足整棵树的边权之和最小。
    2. 最小生成树是一棵树,因此边数等于顶点数减一,且树内一定不会有环。
    3. 对给定的图,其最小生成树可以不唯一,但是边权之和一定是唯一的。
    4. 由于最小生成树是在无向图上生成的,因此其根结点可以是这棵树上的任意一个结点。但是为了使得唯一,题目会给出根结点,因此直接用该结点求解最小生成树即可。
    5. 求解最小生成树,一般有两种算法Prim算法和Kruskal算法。

    二、Prim算法(普里姆算法)

    1. 基本思想:是对于图G(V, E)设置集合S来存放已被访问的顶点,然后执行n次下面的两个步骤:<1> 每次从未攻占的城市中选取距离集合S最近的一个顶点u加入,同时,把这条离集合S最近的边加入最小生成树中; <2>令顶点u作为集合S和其他剩余结点的接口,优化从u能够到达的剩余顶点的最短距离
    2. 具体实现: 集合S的实现和顶点Vi与集合S的最短距离。集合S和Djikstra算法中相同,使用bool数组vis[],来判断是否又被访问过,令int型数组d[],来存放顶点Vi与集合S的最短距离,初始起点d[] = 0,其他顶点为INF,表示不可达。
    3. 可以发现prim算法和Djikstra算法基本相同,只不过是数组d[]代表的含义不同,在Djikstra中d[],表示起点s到达顶点Vi的最短距离,而在prim算法中表示的是到集合的最短距离。
    4. 伪代码:
    //G为图,一般设置成全局变量,数组d为顶点与集合S的最短距离
    Prim(G, d[]){
        初始化
        for(循环n次){
            u = 使d[u]最小还未被访问的顶点编号
            记u已被访问
            for(从u出发能到达的所有顶点v){
                if(v未被访问&&u为中介点使得v与集合S的最短距离d[v]更优){
                    将G[u][v]赋值给v与集合S的最短距离d[v]
                }
            }
        }
    }
    
    邻接矩阵版本
    int n, G[MAXN][MAXN];
    int d[MAXN] //用于存放顶点Vi到集合S的最短距离
    bool vis[MAXN] = {false};
    
    int Prim(){
        fill(d, d+MAXN, INF);
        d[0] = 0;
        int ans = 0; //存放的最小边权之和
        for(int i = 0; i < n; i++){
            int u = -1, MIN = INF;
            for(int j = 0; j < n; j++){
                if(vis[j] == false && d[j] < MIN){
                    MIN = d[j];
                    u = j;
                }
            }
            if(u == -1) return -1;
            vis[u] = true;
            ans += d[u];
            for(int v = 0; v < n; v++){
                if(vis[v] == fasle && G[u][v] != INF && G[u][v] < d[v]){
                    d[v] = G[u][v];
                }
            }
        }
        return ans;
    }
    
    邻接表版
    struct node{
        int v, dis;//v为目标顶点,dis为边权
    }
    vector<node> Adj[MAXN];
    int n;
    int d[MAXN] //用于存放顶点Vi到集合S的最短距离
    bool vis[MAXN] = {false};
    
    int Prim(){
        fill(d, d+MAXN, INF);
        d[0] = 0;
        int ans = 0; //存放的最小边权之和
        for(int i = 0; i < n; i++){
            int u = -1, MIN = INF;
            for(int j = 0; j < n; j++){
                if(vis[j] == false && d[j] < MIN){
                    MIN = d[j];
                    u = j;
                }
            }
            if(u == -1) return -1;
            vis[u] = true;
            ans += d[u];
            for(int j = 0; j < Adj[u].size(); j++){
                int v = Adj[u][j].v;
                if(vis[v] == fasle && G[u][j].dis  < d[v]){
                    d[v] = G[u][v];
                }
            }
        }
        return ans;
    }
    

    三、Kruskal算法(克鲁斯卡尔算法)

    1. 采用边贪心策略
    2. 对于所有边按照边权大小从小到大进行排序
    3. 按顺序测试边,如果两个边不在一个连通块中,则把这条边加入最小生成树中,否则该边舍弃。
    4. 直到最小生成树中,边数等于顶点数减一。
    5. 复杂度为O(ElogE)
    6. 代码问题:
    //定义一个结构体,里面存放两个端点号和边权
    struct edge{
        int u, v;
        int cost;
    }E[MAXN];
    
    //定义一个排序函数让数组E边权从小到大排序
    bool cmp(edge a, edge b){
        return a.cost < b.cost;
    }
    
    //伪代码
    int kruskal(){
        令最小生成树的边权之和为ans、最小生成树的当前边数Num_Edge
        将所有边权从小到大排序
        for(从小到大枚举所有边){
            if(当前测试边不在相同的连通块中){
                将测试边加入最小生成树中
                ans += 测试边边权
                最小生成树的当前边数Num_Edge加1
                当前边数Num_Edge等于顶点数减一,结束循环
            }
        }
        return ans;
    }
    
    //具体代码
    int father[N]//并查集数组
    int findFather(int x){
        int a = x;
        while(x != father[x]){
            x = father[x];
        }
        while(a != father[a]){
            int z = a;
            a = father[a];
            father[z] = x;
        }
        return x;
    }
    //kruskal函数返回最小生成树的边权之和,参数n为顶点个数, m为图的边数
    int kruskal(int n, int m){
        //ans为所求边权之和,Num_Edge为当前生成树的边数
        int ans = 0, Num_Edge = 0;
        for(int i = 1; i <= n; i++){
            father(i) = i;//并查集初始化
        }
        sort(E, E+m, cmp);
        for(int i = 0; i < m; i++){//枚举所有边
            int faU = findFather(E[i].u);//查询测试边两个端点的所在集合的根结点
            int faV = findFather(E[i].v);
            if(faU != faV){
                father[faU] = faV;//合并集合
                ans += E[i].dis;
                Num_Edge++;
                if(Num_Edge == n-1) break;
            }
        }
        if(Num_Edge != n-1) return -1;
        else return ans;
    }
    
    作者:睿晞
    身处这个阶段的时候,一定要好好珍惜,这是我们唯一能做的,求学,钻研,为人,处事,交友……无一不是如此。
    劝君莫惜金缕衣,劝君惜取少年时。花开堪折直须折,莫待无花空折枝。
    曾有一个业界大牛说过这样一段话,送给大家:   “华人在计算机视觉领域的研究水平越来越高,这是非常振奋人心的事。我们中国错过了工业革命,错过了电气革命,信息革命也只是跟随状态。但人工智能的革命,我们跟世界上的领先国家是并肩往前跑的。能身处这个时代浪潮之中,做一番伟大的事业,经常激动的夜不能寐。”
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    python中的线程(zz)
    Bzoj1014 外星人Prefix
    ABC
    终于明白阿里百度这样的大公司,为什么面试经常拿ThreadLocal考验求职者了
    我去面试没带简历,你让我走人?
    利用Python框架pyxxnet_project实现的网络服务
    我以为我对Mysql索引很了解,直到我遇到了阿里的面试官
    CSS必备知识大全
    致 Python 初学者
    从入门到精通,Java学习路线导航
  • 原文地址:https://www.cnblogs.com/tsruixi/p/12438803.html
Copyright © 2020-2023  润新知