zoukankan      html  css  js  c++  java
  • 次小生成树

    借鉴博客: https://www.cnblogs.com/Howe-Young/p/4911992.html

    https://www.cnblogs.com/bianjunting/p/10829212.html

    借鉴视频:https://www.bilibili.com/video/BV1CC4y1a7kD?from=search&seid=17813568887618384754

    一,定义

    权值第 2 小的生成树。

    广义上 次小生成树 可以和 最小生成树 权值一样

    严格上 次小生成树 不能和 最小生成树 权值一样

    二,前提知识 

    1,最小瓶颈生成树

    ① 定义: 给出加权无向图,求一棵生成树,使得最大边权值尽可能小

    ② 算法:从一个空图开始,按照权值从小到大的顺序依次加入各条边,则图第一次连通时,

    该图的最小生成树就是最小瓶颈生成树。

    ③ 结论:由 Kruskal 算法求出的 最小生成树 即为 最小瓶颈生成树

    2,最小瓶颈路

    ① 定义:给出加权无向图的两个结点 u 和 v,求出从 u 到 v 的一条路径,使得路径上的最长边尽量短。

    ② 算法:用 Kruskal 算法求出最小生成树,则树上 u 到 v 的路径就是我们要找的路径,路径上的最长边就是答案

    ③ 反证法:假设该路径 不是 最小生成树上的路径,则原图 u 到 v上必然存在一条路径,满足 两条路径不一样

    且 原图上的路径的最长边 必然小于 最小生成树的最长边 ,这与 Kruskal 算法违背,则假设不成立,算法得证。

    3,每对节点间的最小瓶颈路

    给出加权无向图,求每两个结点 u 和 v 之间的最小瓶颈路 的最大边长 f( u,v )

     解法 ①:先求出 最小生成树,用 DFS 遍历这个树,当访问一个节点时 u 时,考虑所有已经访问过的 结点 x ,

    更新 f(x,u) = max(f(x,v),w(v,u)),其中 v 是 u 的父结点,每个 f(u,v) 只需要常数时间计算,因此时间复杂度为 O(n2)。

    这里 将路径 x->u 分成两部分,

    一是 x->v , 这里 f(x,v),  代表 x->v 路径上的最大边长,

    一是 v->u , 这里 w(v,u),代表 边 v-u  的权值,

    这两部分中的较大值自然就是 路径 x->u 中的最大边权值。

    易得,当 x,u 代表的只有一条边时,f(x,u) 应该等于 w(v,u),所以 f() 应该 初始化为最小值或者 0

    解法 ②:用树上倍增的方法,统计每个点到 2的 j 次方 层祖先的最大距离

    4,可行交换与临集

    ① T 为图G 的一棵生成树,对于非树边a 和 树边b,插入 边a 并且删除 边b 的操作记为 (+a,-b)

    如果 T+a-b 仍然是 一棵生成树,称(+a,-b)是一个可行交换

    ② 由 T 进行一次 可行交换 后得到的 新生成树 的集合 称为 T的临集

    ③ 定理:次小生成树在最小生成树的临集中

    粗略证明:因为 最小生成树 已然是最小代价的边权和,所以每次多一条边不一样,则代价必然增加,所以只改变一条边是代价最小的方法了,

    大概吧 ε=ε=ε=ε=ε=ε=┌(; ̄◇ ̄)┘

    三,算法

    算法 ①:次小生成树 不会与 最小生成树相同,因此可以枚举 最小生成树 中的一条边,将 该边 认为不会在 次小生成树 中出现,

    然后在剩下的边里,求一次 最小生成树。注意 最小生成树只有 n-1 条边,所以只需要枚举 n-1 次。

    这种算法 需要求 n 次最小生成树,时间复杂度挺大的,不推荐。

    算法 ②:

    1.先求出来最小生成树。在求 最小生成树 的时候一并将 最小生成树任意两点之间路径当中的权值最大的那一条 求出来。

    2.尝试添加最小生成树外的每一条边,删除上面找到的边。其中的权值最小的就是次小生成树。

    why?

    原来 最小生成树加入一条边 (为什么是一条边呢?见上面的临集) 之后一定构成了回路,所以如果说 加入了边  ij , 此时 i 到 j 有两条路,这两条路构成一个回路,

    要想构成生成树的话,必然删除这条回路上的边,要想和原来不一样且最小,则必然要删除原先 最小生成树上 i 到 j 路径中的最大边。

    四,代码

    Prim  

    #define _CRT_SECURE_NO_WARNINGS
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #define inf 0x3f3f3f3f
    #define MIN(x,y) (x<y?x:y)
    #define MAX(x,y) (x>y?x:y)
    #define mem(a,b) memset(a,b,sizeof(a))
    #define N 105
    int a[N][N];    //邻接矩阵存图
    int f[N][N];  //表示最小生成树中 i 到 j 的最大边权
    int used[N][N]; //  TE ,最小生成树的边集
    int p[N];      // 路径       
    int dis[N];    // 集合 u 到 i 的最短距离
    bool vis[N];   // 标记集合 u 的点
    int n, m;       // m 是边数, n 是点数
    void init()
    {
        mem(a, 0x3f);
        mem(p, 0);
        mem(vis, 0);
        mem(dis, 0x3f);
        mem(used, 0);
        mem(f, 0);
    }
    int prim()  // 求 最小生成树
    {
        int s = 1;
        dis[1] = 0, vis[s] = 1, p[s] = 0;
        int sum = 0;
        for (int i = 1; i < n; i++)   // 边数 为点数减一
        {
            for (int j = 1; j <= n; j++)  // 更新距离,标记起点
            {
                if (vis[j] == 0 && dis[j] > a[s][j])
                {
                    dis[j] = a[s][j];
                    p[j] = s;
                }
            }
            int min = inf;
            for (int j = 1; j <= n; j++)  // 找到 最小的那条边
            {
                if (vis[j] == 0 && dis[j] < min)
                {
                    min = dis[j];
                    s = j;
                }
            }
            if (min == inf)
                return -1;
            vis[s] = 1;
            used[s][p[s]] = used[p[s]][s] = 1;   //  TE
            sum += min;
    
            for (int j = 1; j <= n; j++)   // 多了这一段
            {
                if (vis[j])
                    f[j][s] = f[s][j] = MAX(f[j][p[s]], a[p[s]][s]);   // 这里 dis[j] ==  a[p[s]][s]
            }
        }
        return sum;
    }
    int smst(int minT)  // 求 次小生成树
    {
        int ans = inf;
        for (int i = 1; i <= n; i++)   //枚举最小生成树之外的边
        {
            for (int j = i + 1; j <= n; j++)
            {
                if (a[i][j] != inf && !used[i][j])
                    ans = MIN(ans, minT + a[i][j] - f[i][j]);
            }
        }
        if (ans == inf)
            return -1;
        return ans;
    }
    int main(void)
    {
        int t; scanf("%d", &t);
        while (t--)
        {
            init();
            scanf("%d %d", &n, &m);
            for (int i = 0; i < m; i++)
            {
                int u, v, w; scanf("%d %d %d", &u, &v, &w);
                a[u][v] = a[v][u] = w;
            }
    
            int minT = prim();    // 最小生成树
            if (minT == -1)
                puts("Not Unique!");
            else 
            {
                int secT = smst(minT); // 次小生成树
                if (secT == minT)
                    printf("Not Unique!
    ");
                else
                    printf("%d
    ", minT);
            }
        }
        system("pause");
        return 0;
    }
    View Code

     Kruskal

    #define _CRT_SECURE_NO_WARNINGS
    #include<stdio.h>
    #include<stdlib.h>
    #include<vector>
    #include<algorithm>]
    using namespace std;
    #define N 111
    #define inf 0x3f3f3f3f
    #define MIN(x,y) (x<y?x:y)
    int p[N], f[N][N];
    int n, m;
    vector<int> g[N];
    struct edge
    {
        int from, to, w;
        int vis;      // 标记
    }e[123456];
    int cmp(const edge &a, const edge &b)
    {
        return a.w < b.w;
    }
    int find(int x)
    {
        if (x != p[x])
            p[x] = find(p[x]);
        return  p[x];
    }
    int join(int x, int y)
    {
        x = find(x), y = find(y);
        if (x == y)
            return 0;
        x = p[y];
        return 1;
    }
    void init()
    {
        for (int i = 1; i <= n; i++)
        {
            g[i].clear();
            g[i].push_back(i);
            p[i] = i;
        }
    }
    int Kruskal()
    {
        sort(e + 1, e + 1 + m, cmp);
        init();
        int sum = 0, cnt = 0;
        for (int i = 1; i <= m; i++)
        {
            if (cnt == n - 1)
                break;
    
            int x = find(e[i].from), y = find(e[i].to);
            if (x != y)
            {
                cnt++;
                e[i].vis = 1;
                sum += e[i].w;
                int lx = g[x].size(), ly = g[y].size();
                for (int j = 0; j < lx; j++)
                {
                    for (int k = 0; k < ly; k++)
                        f[g[x][j]][g[y][k]] = f[g[y][k]][g[x][j]] = e[i].w;
                }
                p[x] = y;
                for (int j = 0; j < lx; j++)
                {
                    g[y].push_back(g[x][j]);
                }
            }
        }
        return sum;
    }
    int smst(int minT)
    {
        int sum = inf;
        for (int i = 1; i <= m; i++)
        {
            if (e[i].vis == 0)
                sum = MIN(sum, minT + e[i].w - f[e[i].from][e[i].to]);
        }
        return sum;
    }
    int main(void)
    {
        int t; scanf("%d", &t);
        while (t--)
        {
            scanf("%d%d", &n, &m);
            for (int i = 1; i <= m; i++)
            {
                scanf("%d%d%d", &e[i].from, &e[i].to, &e[i].w);
                e[i].vis = 0;
            }
            int minT = Kruskal();
            int secT = smst(minT);
            if (secT != minT)
                printf("%d
    ", minT);
            else
                puts("Not Unique!");
        }
        system("pause");
        return 0;
    }
    View Code

    =========== ========= ======== ======= ======= ===== ==== === == =

    高凉村妇盼郎归情歌

    百里寻夫到天光 又到徐闻与海康

    走尽花街和柳巷 谁知夫在鸡婆床

    二八鸡婆巧梳妆 洞房夜夜换新郎

    一双玉臂千人枕 半点朱唇万客尝

    装成一身娇体态 扮做一副假心肠

    迎来送往知多少 惯作相思泪两行

    一生悲欢恨怨间 劝郎戒嫖把家还

    一觉扬州梦应醒 为妻待郎情无限。

  • 相关阅读:
    servlet 表单加上multipart/form-data后request.getParameter获取NULL(已解决)
    火狐浏览器通过配置文件锁定主页
    如何最快速的完成智慧树期末考试(有钱的大佬请绕道)记----智慧树考试生产力
    java代码发送邮箱验证码与qq邮箱smtp服务
    邮件服务器
    Android requestcode resultcode的作用
    伽卡拉他学生客户端无法运行任务管理器的解决方法
    Django自定义UserModel并实现认证和登录
    {%csrf_token%}的作用
    Producer Flow Control 和 vmQueueCursor
  • 原文地址:https://www.cnblogs.com/asdfknjhu/p/13252284.html
Copyright © 2011-2022 走看看