朱刘算法(Directed Minimum Spanning Tree/Directed MST/Minimum Arborescence/Optimum Branchings)

2023-05-16

概念

最小树形图:有向图所分离出的有向生成树,亦称为最小树形图,其应满足以下条件:
1. 恰好有一个入度为0的点,称为根结点
2. 其他结点的入度均为1
3. 可以从根结点到达其他结点

既然要找最小生成树,当然就是找权重越小的边越好。每一个点(除了根以外)各自找到权重最小的入边之后,有可能就刚好是一棵最小生成树了,但是也有可能形成几只“水母”。

由于每个点都仅有一条入边,如果形成环,环上一定只有出边,不会有入边。每个点都仅有一条入边,除了刚好形成一棵树以外,要不就是形成水母 —— 一只环再加上环上的点各是一棵树的树根,或者说是很多棵树的树根用环串起。

水母图

水母与最小树形图

最小生成树不能有环,所以水母是不合格的,然而水母是权重最小的连接方式。若有一棵恰当的最小生成树,其权重会略高于水母。由水母向最小生成树靠拢,可能有以下两种情况:

  1. 改变水母环上的边,让水母变成一棵树,尽管整体权重稍微变大,但仍可接受。
  2. 改变水母触手上的边,并没有比较好。不但让整体权重变大,而且水母环仍存在,并没有解决掉不合格的问题。

由此可得出结论:

只需要尝试打开水母环上的边就行了。打开环的时候,要同时考虑新加入的边的权重和取消的边的权重。选择差值最小者,可让权值增加最小。进入水母的环全部看一遍后,就能选出差值最小者。

朱刘算法(Chu-Liu/Edmonds Algorithm)

算法思想

根据Kruskal’s Algorithm提到的最小生成树相连性质,可以知道连接多只水母,就和连接多棵最小生成树的道理是一样的,以权重小的边來连接是最好的。唯一不同的是,Kruskal’s Algorithm一旦发现造成环的边,就直接舍弃;Chu-Liu/Edmonds Algorithm则是留下造成环的边(形成水母),并且尝试各种打开环的方式。

该算法设计思想巧妙,也较难理解。 Sasuke_SCUT 的blog讲原理解释得较清楚,特摘录如下:

判断是否存在树形图的方法很简单,只需要以v为根作一次图的遍历就可以了,所以下面的 算法中不再考虑树形图不存在的情况。
在所有操作开始之前,我们需要把图中所有的自环全都清除。很明显,自环是不可能在任何一个树形图上的。只有进行了这步操作,总算法复杂度才真正能保证是O(VE)。
首先为除根之外的每个点选定一条入边,这条入边一定要是所有入边中最小的。现在所有的最小入边都选择出来了,如果这个入边集不存在有向环的话,我们可以证明这个集合就是该图的最小树形图。这个证明并不是很难。如果存在有向环的话,我们就要将这个有向环所称一个人工顶点,同时改变图中边的权。假设某点u在该环上,并设这个环中指向u的边权是in[u],那么对于每条从u出发的边(u, i, w),在新图中连接(new, i, w)的边,其中new为新加的人工顶点; 对于每条进入u的边(i, u, w),在新图中建立边(i, new, w-in[u])的边。为什么入边的权要减去in[u],这个后面会解释,在这里先给出算法的步骤。然后可以证明,新图中最小树形图的权加上旧图中被收缩的那个环的权和,就是原图中最小树形图的权。
上面结论也不做证明了。现在依据上面的结论,说明一下为什么出边的权不变,入边的权要减去in [u]。对于新图中的最小树形图T,设指向人工节点的边为e。将人工节点展开以后,e指向了一个环。假设原先e是指向u的,这个时候我们将环上指向u的边 in[u]删除,这样就得到了原图中的一个树形图。我们会发现,如果新图中e的权w’(e)是原图中e的权w(e)减去in[u]权的话,那么在我们删除掉in[u],并且将e恢复为原图状态的时候,这个树形图的权仍然是新图树形图的权加环的权,而这个权值正是最小树形图的权值。所以在展开节点之后,我们得到的仍然是最小树形图。逐步展开所有的人工节点,就会得到初始图的最小树形图了。
如果实现得很聪明的话,可以达到找最小入边O(E),找环 O(V),收缩O(E),其中在找环O(V)这里需要一点技巧。这样每次收缩的复杂度是O(E),然后最多会收缩几次呢?由于我们一开始已经拿掉了所有的自环,我门可以知道每个环至少包含2个点,收缩成1个点之后,总点数减少了至少1。当整个图收缩到只有1个点的时候,最小树形图就不不用求了。所以我们最 多只会进行V-1次的收缩,所以总得复杂度自然是O(VE)了。由此可见,如果一开始不除去自环的话,理论复杂度会和自环的数目有关。

chu_liu and kruskal

算法步骤

  1. 找到除了root以为外其他点的权值最小的入边。用In[i]记录 ;
  2. 如果出现除了root以为存在其他孤立的点,则不存在最小树形图。;
  3. 找到图中所有的环,并对环进行缩点,重新编号。;
  4. 更新其他点到环上的点的距离 ;
  5. 重复3,4直到没有环为止。

chu_liu Algorithm

Codes

// 摘自:http://blog.csdn.net/ditian1027/article/details/21958821

#include <iostream>
#include <math.h>
#include <cstdio>
using namespace std;

#define N 105
#define INFINITE 999999999
#define MYTYPE double

struct _point
{
    int x;
    int y;
} point[N];

struct _edge
{
    int from;
    int to;
    MYTYPE cost;
} edge[N*N];

MYTYPE inw[N];  //最小入边
int vis[N]; //是否被访问 
int id[N];  //由当前图到重构图的映射 
int pre[N]; //前驱顶点

MYTYPE Directed_MST(int root, int NV, int NE)
{
    MYTYPE ret=0;
    while (1)   //开始迭代过程 
    {
        //1.确定最小入边集 
        for(int i=0; i<NV; ++i) inw[i]=INFINITE;
        for (int i=0; i<NE; ++i)
        {
            int from=edge[i].from;
            int to=edge[i].to;
            if (edge[i].cost<inw[to] && from!=to)   //from!=to忽略自环
            {
                inw[to]=edge[i].cost;
                pre[to]=from;
            }
        }
        //检查是否有不可达点
        for (int i=0; i<NV; ++i)
        {
            if(i==root) continue;   //除根之外
            if(inw[i]==INFINITE) return -1; //有不可达顶点,不可能生成最小树形图,退出
        }
        //2.找环
        for (int i=0; i<NV; ++i)
        {
            vis[i]=-1;
            id[i]=-1;
        }
        int newidx=0;
        inw[root]=0;
        for (int i=0; i<NV; ++i)    //有两个作用:计算最小入边集的权值和;检查是否有环,如果有,重新对点进行编号
        {
            ret+=inw[i];
            int v=i;
            while (vis[v]!=i && id[v]==-1 && v!=root)   
            //由v回溯。能回到根,即最后v==root,那么肯定不在环里;回不到根,v!=root,v有可能在环里,也有可能不在(回溯到一个环然后出不去了,同样也到不了根)。
            //若v在环里,则环上所有点的id[]值会被重新标号,不再是-1;若是后一种情况,它前驱的环上的点的id[]已被修改为非-1,不能通过“id[v]==-1”这个条件的检查。
            {
                vis[v]=i;
                v=pre[v];
            }
            if (v!=root && id[v]==-1)   //两个条件保证了:1.在环上2.这环没被处理过
            {
                //下面把环上所有的点的标号设置为同一个  
                for (int u=pre[v]; u!=v; u=pre[u])
                {
                    id[u]=newidx;
                }
                id[v]=newidx;
                ++newidx;
            }
        }
        if(newidx==0)   break;  //无环,ret就是答案,跳出迭代
        for (int i=0; i<NV; ++i)
        {
            if(id[i]==-1) id[i]=newidx++;   //给环外的点继续编号
        }
        //3.重新构图,准备下一次迭代
        for (int i=0; i<NE; ++i)
        {
            int to=edge[i].to;
            edge[i].from=id[edge[i].from];
            edge[i].to=id[edge[i].to];
            if(edge[i].from != edge[i].to)
            {
                edge[i].cost -= inw[to];    //算法的关键
            }
        }
        //为下一轮迭代赋初值
        NV=newidx;
        root=id[root];
    }
    return ret;
}

MYTYPE calcdist(int point_a, int point_b)
{
    MYTYPE delta_x=(MYTYPE)(point[point_a].x - point[point_b].x);
    MYTYPE delta_y=(MYTYPE)(point[point_a].y - point[point_b].y);
    return sqrt(delta_x*delta_x + delta_y*delta_y);
}

int main()
{
    int n, m, x, y, from, to;
    MYTYPE ans;
    while (scanf("%d", &n) != EOF)
    {
        scanf("%d", &m);
        for (int i=0; i<n; ++i) //n个顶点
        {
            scanf("%d%d", &x, &y);
            point[i].x=x;
            point[i].y=y;
        }
        for (int i=0; i<m; ++i) //m个边
        {
            scanf("%d%d", &from, &to);
            edge[i].from=from-1;
            edge[i].to=to-1;
            edge[i].cost=calcdist(from-1, to-1);
        }
        ans=Directed_MST(0, n, m);

        if (ans==-1)    printf("NO\n");
        else printf("%.2f\n", ans);
    }
    return 0;
}

参考:演算法笔记-Tree

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

朱刘算法(Directed Minimum Spanning Tree/Directed MST/Minimum Arborescence/Optimum Branchings) 的相关文章

  • 如何查看 SVN 中文件的版本树,显示从分支到主干的合并?

    我是 SVN 新手 但已经使用 Clearcase 多年 我的问题是我对一个分支进行了一些更改 我已使用 TortoiseSVN 重新集成分支 功能将其合并回主干 现在 当我查看版本树时 我没有看到从分支尖端到树干尖端渲染的任何边缘 这是我
  • QTableView 仅显示使用 QAbstractItemModel 实现的树模型的叶子

    假设我有一个树结构 树叶在bold 抱歉这些点 A A1 A2 B B1 B11 B2 C 存储在 QAbstractItemModel 中 具有设置的父 子关系 如何在 QTableView 中仅显示树叶 基本思想是实现一个 QSortF
  • 迭代多级提升树

    我的树看起来像这样 Library L ID 1 Book B ID 1 Title Moby Dick Book B ID 2 Title Jurassic Park Library L ID 2 Book B ID 1 Title Ve
  • 寻找成熟的 M-Tree 实现 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我正在寻找一个成熟的 java M Tree 实现 甚至任何 M Tree 实现 除了我找到的唯一实现 http en wikipedia
  • d3树计算所有孩子的数量

    我有一个基于以下内容的 d3 树 http bl ocks org mbostock 1093025 http bl ocks org mbostock 1093025 我如何计算所有孩子的数量 我已经尝试过这个 但是它计算了树中的所有行
  • Java hibernate/jpa 如何创建自相关的动态通用实体

    我想使用 JPA hibernate 创建动态和通用的超类 它将针对每个层次结构模型进行扩展 例如 角色 页面 目录 部门 权限 树 我想使用递归和java反射来创建这个对象动态树 它应该看起来像这样 该实体应该引用自身实体 我希望它是完全
  • 如何使用KDTrees实现最近邻搜索?

    所以 我正在实施一个KD Tree http en wikipedia org wiki Kd tree进行最近邻搜索 我已经构建了树部分 但我认为我没有完全理解搜索部分 关于遍历树来搜索邻居 维基百科文章如下 Starting with
  • java数据结构模拟数据树

    我需要帮助定义使用什么方法 我有一个 SOAP 响应 给我一个 xml 文件 我需要在屏幕上显示 3 个相关列表 当您在第一个列表中选择一个项目时 相应的选择将出现在第二个列表中 依此类推 我只对从 xml 流中提取数据后如何有效地组织数据
  • 两组点之间的最佳匹配

    I ve got two lists of points let s call them L1 P1 x1 y1 Pn xn yn and L2 P 1 x 1 y 1 P n x n y n 我的任务是找到它们点之间的最佳匹配 以最小化它
  • 将2-3-4树转换为红黑树

    我正在尝试将 2 3 4 树转换为 java 中的红黑树 但我无法弄清楚它 我将这两个基本类编写如下 以使问题简单明了 但不知道从这里到哪里去 public class TwoThreeFour
  • d3.js:修改树布局中的链接

    抱歉我的英语不好 我在这里使用这个例子 http bl ocks org mbostock 4339083 http bl ocks org mbostock 4339083构建树形图 但我用矩形更改了根的子级中的圆圈 现在该图有点混乱 因
  • 比较两棵树的伪代码

    这是我遇到过几次的问题 并且不确信我使用了最有效的逻辑 例如 假设我有两棵树 一棵是文件夹结构 另一棵是该文件夹结构的内存 模型 我希望比较两棵树 并生成一棵树中存在的节点列表 而不是另一棵树中存在的节点列表 反之亦然 是否有公认的算法来处
  • 算法 - 树中所有节点的最大距离

    所以 找到树中两个节点之间的最长路径相当容易 但我想要的是找到从节点出发的最长路径x到树中的另一个节点 对于所有x 这个问题也可以用以下方式表达 计算从给定的树中可以生成的所有有根树的高度 One way of course is to j
  • 将 Lambda 表达式树与 IEnumerable 结合使用

    我一直在尝试了解有关使用 Lamba 表达式树的更多信息 因此我创建了一个简单的示例 这是代码 如果作为 C 程序粘贴到 LINQPad 中 它可以工作 void Main IEnumerable
  • 如何从邻接列表构建嵌套树结构?

    考虑到我有 名为的相邻键 子级 父级 列表A 一个名为Tree存储自己的节点键 整数 和子节点 类 A 61 66 50 61 68 61 33 61 57 66 72 66 37 68 71 33 6 50 11 37 5 37 clas
  • 如何向 Ext.tree.TreePanel 添加复选框?

    我创建了这个简单的树 var children text My Layers children new Ext tree TreeNode text test1 leaf true new Ext tree TreeNode text te
  • ExtJs4 Json TreeStore?

    我正在将 ExtJs3 应用程序迁移到 ExtJs4 在 ExtJs3 中 我有一个树网格 它有一个加载器来加载树数据 如下所示 loader new Ext tree TreeLoader dataUrl Department Depar
  • javascript中的父子关系排序

    我有以下结构 category id 1 parent category null category id 2 parent category 1 category id 3 parent category 1 category id 4
  • (C) 获取矩阵中一行的 3 个最小元素,并随机选择一个

    我有一个 8x8 矩阵 在选择我想要的行后 我想获得它的三个最小元素 并随机选择这三个元素之一 问题是我不知道如何处理这三个要素 我只知道如何获取最小元素 即下面的代码 int piezas 8 8 0 2 2 5 3 2 1 1 0 4
  • XSLT 将平面树结构转换为列表

    我有一个描述eshop树结构的xml文件 我只需要获取所有子组的列表 我不知道结构中有多少个父 子级别 输入 xml 如下所示

随机推荐

  • MySQL show关键字用法

    SHOW DATABASES 列出 MySQL Server 上的数据库 SHOW TABLES FROM db name 列出数据库中的表 SHOW TABLE STATUS FROM db name 列出数据库的表信息 xff0c 比较
  • windows10共享文件夹挂载到Ubuntu

    程序开发人员一般都会把开发目录放在windows系统下 xff0c 开发环境却是linux 以前我是linux下文件挂载到windows xff0c 有同事前车之鉴 xff0c 万一虚拟机linux挂壁了 xff0c 很难恢复 现在准备把w
  • 闭包函数中use使用

    匿名函数中的use xff0c 其作用就是从父作用域继承变量 下例是最常见的用法 xff0c 如果不使用use xff0c 函数中将找不到变量 msg 1 2 3 4 5 6 7 8 lt php msg 61 1 2 3 func
  • for update秒杀

    Mysql InnoDB 排他锁 用法 xff1a select for update 例如 xff1a select from goods where id 61 1 for update 排他锁的申请前提 xff1a 没有线程对该结果集
  • UNION 和 UNION ALL

    UNION用的比较多union all是直接连接 xff0c 取到得是所有值 xff0c 记录可能有重复 union 是取唯一值 xff0c 记录没有重复 UNION 和 UNION ALL 的语法都是 xff1a SQL 语句 1 UNI
  • php网站压测(ab)

    一般来说核心页面都需要进行压测 xff0c 特别是秒杀页面 xff0c 从而知道网站的承受能力 xff0c 方便暴露一些问题 xff0c 更好的把控网站 压测工具有很多种 xff0c 最简单 方便的可以使用ApacheBench xff0c
  • CSV文件读取 C++版本

    代码 span class token comment 创建结构体 xff0c 把读取数据可以放入结构体成员中 span span class token keyword struct span span class token class
  • 四种常见的 POST 提交数据方式

    HTTP 1 1 协议规定的 HTTP 请求方法有 OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT 这几种 其中 POST 一般用来向服务端提交数据 xff0c 本文主要讨论 POST 提交数据
  • json_decode

    json 61 34 34 errorno 34 0 34 errormsg 34 34 可以 34 34 data 34 34 guid 34 34 5762340 34 34 username 34 34 wiu370468 34 34
  • csv乱码处理

    handle 61 fopen 34 war csv 34 34 r 34 row 61 1 while data 61 fgetcsv handle 1000 34 34 data 61 eval 39 return 39 iconv 3
  • OR和AND关键字一起使用的情况

    OR和AND关键字一起使用的情况 OR关键字和AND关键字 xff0c 可以一起使用 xff0c 需要注意 xff0c AND的优先级高于OR 因此 xff0c 当两者一起使用时 xff0c 应该先运算AND两边的条件表达式 xff0c 再
  • Ubuntu cron 定时执行任务

    cron xff0c 是一个Linux定时执行工具 xff0c 可以在无需人工干预的情况下运行作业 1 关于crontab 在Ubuntu server 下 xff0c cron是被默认安装并启动的 通过 etc crontab文件 xff
  • Linux服务器上监控网络带宽的18个常用命令

    本文介绍了一些可以用来监控网络使用情况的Linux命令行工具 这些工具可以监控通过网络接口传输的数据 xff0c 并测量目前哪些数据所传输的速度 入站流量和出站流量分开来显示 一些命令可以显示单个进程所使用的带宽 这样一来 xff0c 用户
  • Linux系统使用iftop查看带宽占用情况

    Linux系统下如果服务器带宽跑满了 xff0c 查看跟哪个ip通信占用带宽比较多 xff0c 可以通过iftop命令进行查询 xff0c 使用方法如下 xff1a 1 安装方法 软件官网地址 xff1a http www ex parro
  • linux基础命令

    1 curl amp wget 使用curl或wget命令 xff0c 不用离开终端就可以下载文件 如你用curl xff0c 键入curl O后面跟一个文件路径 wget则不需要任何选项 下载的文件在当前目录 代码如下 curl O we
  • find_in_set

    1 in查询相当于多个or条件的叠加 xff0c 例如 xff1a select from user where user id in 1 2 3 等效于 select from user where user id 61 1 or use
  • 集成Cortex-M0内核-- Integration and Implementation Manual手册学习

    根据使用场景 xff0c 配置并集成一个Cortex M0的内核 xff0c 暂时不涉及的实现的部分 目录 阅读手册 Chapter1 Introduction 1 1 About the processor 1 2 About integ
  • 在NVIDIA NX 配置OpenCV多版本冲突和解决的总结

    Nvidia Jetson NX 环境 直接刷JetPack5 1的镜像 xff0c 会得到如下环境 Ubuntu20 04cuda11 4TensorRT8 4cudnn8 4opencv4 5 4 而且这些源一般是从nv xxxx等源下
  • 一款入门级的飞控CC3D(一)

    很多在校的朋友想自己动手做一款旋翼无人机 xff0c 但是零件采购下来要花费不少大洋 xff0c 装配完成后又需要进行软件硬件调试 所以很多想做极客的梦就扼杀在摇篮里 本期我将开始更新一款入门级的飞控CC3D 首先 xff0c 放上CC3D
  • 朱刘算法(Directed Minimum Spanning Tree/Directed MST/Minimum Arborescence/Optimum Branchings)

    概念 最小树形图 xff1a 有向图所分离出的有向生成树 亦称为最小树形图 xff0c 其应满足以下条件 xff1a 1 恰好有一个入度为0的点 xff0c 称为根结点 2 其他结点的入度均为1 3 可以从根结点到达其他结点 既然要找最小生