首页
社区
课程
招聘
[旧帖] [推荐][无责任凑发帖第四弹]算法扫盲之最小树形图 0.00雪花
发表于: 2011-7-19 07:26 1398

[旧帖] [推荐][无责任凑发帖第四弹]算法扫盲之最小树形图 0.00雪花

2011-7-19 07:26
1398
在招聘专区看到某企业 超能饮料 谜题 故有此贴
我们知道,Spanning Tree(生成树)是连接一个无向连通图的每个节点的一棵树,而 Minimum Spanning Tree 则是一个图的所有生成树中边权值最小的。Minimum Spanning Tree 的算法有很多,比如常见的 Prim's Algorithm、Kruskal's Algorithm 等,还有 Borůvka's Algorithm 等。这个 Borůvka's Algorithm 也叫做 Sollin's Algorithm,Sollin 在二十世纪六十年代发现,但实际上捷克人 Otakar Borůvka 在1926年就发现了,之后又有不少人重新发现。
  在学习最小生成树时,大家可能会有一个疑问,无向图有最小生成树,那么有向图呢?
有向图也有类似的树结构,叫做最小树形图,英文名称有很多,有 Minimum Arborescence、Minimum Branching 等多个名称,也有根据其特点称之为 Directed Minimum Spanning Tree(有向图的最小生成树)的。树形图是一棵有向树,它的根可以到达其他图的其他每个节点。最小树形图是一个图的所有树形图中边权值最小的。
  那么如何求最小树形图呢?Prim's Algorithm、Kruskal's Algorithm 等用于求无向图最小生成树的算法是无法解决这个问题的。
  解决该问题的第一个算法由朱永津和刘振宏在1965发表的,文章名为《关于最小树形图》。2年后,Jack Edmonds(就是那位发现了 Edmonds-Karp Algorithm 的数学家)也独立发现了同样的算法。这是一个由中国人的名字命名的算法!
  先对根节点做一次 Depth First Search 即可判断根节点能否到达图中其他节点,如果不能则不存在最小树形图。

下面是求最小树形图的一个实现,时间复杂度为 Ο(|V|^3)
值得敬佩的是,朱永津是一位盲人。

#include <algorithm>
#include <cfloat>
#include <cstdio>
using namespace std;

const int N = 100;
bool visited[N];
int n;
double a[N][N]; //邻接矩阵

void DFS(int x)
{
    visited[x] = true;
    for (int i = 0; i < n; ++i)
        if (!visited[i] && a[x][i] < DBL_MAX-1) DFS(i);
}

double ZhuYongJin_LiuZhenHong()
{ //朱永津、刘振宏
    static bool flag[N];
    static int prev[N];
    double res = 0;
    fill_n(flag, n, false);
    for(;;)
    {
        int i;
        for (i = 1; i < n; ++i)
        {
            if (flag[i]) continue; //该点已被删除
            prev[i] = i;
            a[i][i] = INT_MAX; //删除自环
            for (int j = 0; j < n; ++j)
                if (!flag[j] && a[j][i] < a[prev[i]][i])
                    prev[i] = j;
        }
        for (i = 1; i < n; ++i)
        {
            if (flag[i]) continue;

            //寻找环
            int j = i;
            visited[0] = true;
            fill_n(visited+1, n-1, false);
            do visited[j] = true, j = prev[j];
            while (!visited[j]);

            if (!j) continue; //包含根节点
            i = j;
            res += a[prev[i]][i];
for (j = prev[i]; j != i; j = prev[j])
            {
                flag[j] = true; //删除环中除了 i 之外的所有节点
                res += a[prev[j]][j];
            }

            //w(j, i) 减去 w(prev(i), i)
            for (j = 0; j < n; ++j)
                if (!flag[j] && a[j][i] < DBL_MAX-1)
                    a[j][i] -= a[prev[i]][i];

            //修改和环关联的边的权值
            for (int k = 0; k < n; ++k)
                if (!flag[k])
                    for (j = prev[i]; j != i; j = prev[j])
                    {
                        if (a[j][k] < a[i][k]) a[i][k] = a[j][k];
                        if (a[k][j] < DBL_MAX-1 && a[k][j]-a[prev[j]][j] < a[k][i]) a[k][i] = a[k][j]-a[prev[j]][j];
                    }
            break;
        }
        if (i == n) //不再存在环
        {
            for (i = 1; i < n; ++i)
                if (!flag[i]) res += a[prev[i]][i];
            break;
        }
    }
    return res;
}

int main()
{
    int m, u, v;
    double w;
    while (scanf("%d%d", &n, &m) != EOF)
    {
        for (int i = 0; i < n; ++i)
            fill_n(a[i], n, DBL_MAX);
        for (; m; --m)
        {
            scanf("%d%d%lf", &u, &v, &w);
            a[v] = w;
        }
        fill_n(visited, n, false);
        DFS(0);
        for (int i = 1; i < n; ++i)
            if (!visited[i])
            {
                puts("最小树形图不存在");
                goto L1;
            }
        printf("%lf\n", ZhuYongJin_LiuZhenHong());
L1:;
    }
}
res←0

对于除了根节点外的每个节点 i,求出权值最小的入(1)边 (prev[i], i)。如果这些入边不构成环,那么入边的集合 E 即为所求。假设其中的一个环为 v0 <- v1 <- …… <- vk <- v0,那么如下更新原图:
w(v[0], u) = min{w(v[j], u)} (0 <= j <= k)
w(u, v[0]) = min{w(u, v[j]) - w(prev[j], j)} (0 <= j <= k)
并且 res 的值增加环中所有边的权值

重复以上操作,直到 E 中不存在环。此时 res 就是最小树形图的边权值之和。
w(u, v0) 之所以要修改为 min{w(u, v[j]) - w(prev[j], j)},因为如果环外有个点 u 连向环中某一点 v[j],那么加上之前算入 res 总和的 w(prev[j], j),正好变成 w(u, v[j])。

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 0
支持
分享
最新回复 (11)
雪    币: 35
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
有空再更新吧 希望看雪能有更多人关注C/C++而不是一味关注底层技术
2011-7-19 07:35
0
雪    币: 22
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
俺是新人,学习了!多谢楼主分享!
2011-7-19 07:40
0
雪    币: 437
活跃值: (110)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
4
支持一下。
2011-7-19 07:49
0
雪    币: 35
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
咱也是新人 菜鸟一只 同勉
2011-7-19 08:08
0
雪    币: 35
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
谢谢
2011-7-19 08:09
0
雪    币: 678
活跃值: (101)
能力值: ( LV2,RANK:150 )
在线值:
发帖
回帖
粉丝
7
如果有去了解过ACM比赛,知道的算法可能更多,我觉得其实看雪是有专攻,C/C++技术已经遍布很多论坛了。
2011-7-19 09:03
0
雪    币: 35
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
毕竟**像看雪这样的好论坛太少...
我辈菜鸟就是希望好论坛能更全面一些...
因为作为菜鸟的我发现要找一个好的地方学习
并不是一件很容易的事情...
可能很多菜鸟有和我一样的困惑...
而看雪的专攻方向似乎也有些过偏...
当然对高手来说这不算什么...
但是菜鸟容易被误导...
以上纯属个人看法...
2011-7-19 21:59
0
雪    币: 35
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
作为菜鸟... 胡乱发表一些观点...
咱个人认为ASM Win32-API 甚至算法都不是编程真正的重心...
毕竟 写程序是要考虑开发效率和运行时效率的性价比的...
何况新手用汇编写程序不见得就比C快 用C写程序笔尖的就比STL快...
现代编程风格的重心毕竟是利用设计良好的封装在最短时间内写出效率尽可能高的程序...
当然 作为安全软件论坛 专攻底层本身无可厚非 菜鸟只是希望好论坛能做的更全面一些...
2011-7-19 22:07
0
雪    币: 1015
活跃值: (235)
能力值: ( LV12,RANK:440 )
在线值:
发帖
回帖
粉丝
10
正向来看,楼主说的很对。但是毕竟,论坛主要是逆向来看的,asm是必不可少的 ,当然编程是必不可少,同时也是有侧重的,和单纯的软件开发应该还是有区别的。个人拙见,请不要见笑。
2011-7-20 10:28
0
雪    币: 1015
活跃值: (235)
能力值: ( LV12,RANK:440 )
在线值:
发帖
回帖
粉丝
11
感谢楼主的分享,希望搂主能够分享够多的东西供我们学习= =
2011-7-20 10:31
0
雪    币: 35
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
嗯 这也确实是事实
2011-7-20 16:04
0
游客
登录 | 注册 方可回帖
返回
//