【问题标题】:Boost.Graph how to merge two vertices/contract edgeBoost.Graph如何合并两个顶点/合同边
【发布时间】:2013-07-19 16:57:46
【问题描述】:

如何在 Boost.Graph 中合并两个顶点/合约边?

我需要将边从顶点 A 移动到顶点 B,并删除顶点 A - 是否有任何内置函数?或者也许 adjacency_list 有什么特别之处?

如果没有这样的功能 - 那为什么?我认为这是常见的图形操作。

编辑:我确实知道可以手动完成,但是有一些极端情况(例如保留边缘属性),这就是为什么它是图书馆的好人选。

我最想知道 Boost.Graph 是否已经有这个操作(也许有一些花哨的名字?)。如果不是 - 为什么这种原始操作/算法不在 Graph 库中。也许我遗漏了一些东西,并且该操作不是原始的或很少使用。

我不需要半生不熟的快速概念验证

【问题讨论】:

  • downvoter,问题有什么问题?
  • 大喊“我不需要半生不熟的快速概念验证”可能是这个问题的问题所在。 (我知道,你并没有那么坏,甚至授予赏金,只是说)。

标签: c++ boost graph boost-graph


【解决方案1】:

半生不熟的快速概念验证

您可以在以adjacency_list 定义的图形上使用add_edge()remove_vertex()

#include <iostream>
#include <iterator>
#include <boost/graph/adjacency_list.hpp>

using V = unsigned;
using E = std::pair<V, V>;
using G = boost::adjacency_list<boost::vecS, boost::vecS>;

void print_graph(G const& g)
{
    auto vs = boost::vertices(g);
    for (auto vit = vs.first; vit != vs.second; ++vit) {
        auto neighbors = boost::adjacent_vertices(*vit, g);
        for (auto nit = neighbors.first; nit != neighbors.second; ++nit)
            std::cout << "{" << *vit << "," << *nit << "}" << ", ";
    }
    std::cout << "\n";
}

void contract_vertices(V b, V a, G& g)
{
    auto be = boost::adjacent_vertices(b, g);
    for (auto beit = be.first; beit != be.second; ++beit)
        add_edge(a, *beit, g);
    remove_vertex(b, g);
}

int main()
{
    // named vertices
    auto const A = V { 1 };
    auto const B = V { 2 };

    // construct the graph
    auto e = std::vector<E> { { A, 3 }, { B, 4 } };
    auto g = G { std::begin(e), std::end(e), 4 };

    print_graph(g);
    contract_vertices(B, A, g);    
    print_graph(g);
}

Live example 打印

{1,3}, {2,4},
{1,2}、{1,3}、

输出并不完全符合您的预期,因为顶点的标签也被更新以反映 B 的删除,这导致节点 3 和 4 现在被标记为 2 和 3。

库质量代码的要求

用于收缩顶点 uv 的通用库质量算法通常应至少考虑以下极端情况

  • 移除 (u,v) 和 (v,u) 边;
  • 将所有 u 和 v 外边缘与共同目标合并;
  • 将所有 u 和 v 入边与公共源合并;
  • 将其余的 u 外边缘移至 v;
  • 将剩余的 u 内边移到 v;

Boost.Graph 为此类操作提供了所有必需的原语:in_edges()out_edges()add_edge()clear_vertex()remove_vertex()。对于无向图,其中几个项目可以在一个步骤中完成,而对于有向图,通常需要两个步骤。

除了这些算法步骤之外,还应该定义合并或移动边缘的语义。例如。他们的财产应该怎么办?这类似于例如合并两家公司:合资公司应以哪个名称运营?

为什么 Boost.Graph 没有(还)提供contract_vertices()

TL;DR我不知道。但我可以推测。主要是应该指定一个假定的contract_vertices() 的接口。除了要收缩的两个顶点以及它们所属的图类型之外,还应该定义边缘属性的合并和移动操作。理论上,应该可以使用适合通用算法的模板参数来做到这一点。

【讨论】:

  • 1.我认为 collapse_vertices 应该有签入循环以避免循环——比如if(a != *beit) add_edge(a, *beit, g);。 2. 我知道手动操作是可能的——我更感兴趣的是为什么 Boost.Graph 没有内置插件? (或者它可能有?有一些不明显的名字?) 3. 这不仅是关于将删除顶点的边添加到另一个,而且还关于保留边数据(Boost.Graph 具有“属性”) - 显然它需要更多手动代码 + 还有一些其他极端情况,这就是为什么它是图书馆的好人选。
  • 是的,你应该在生产代码中做这三件事。在任何情况下,Boost.Graph 都提供了编写 collapse_vertices 的所有原语
  • “理论上,应该可以使用适合通用算法的模板参数来做到这一点。” - 是的,完全正确 - 将责任委托给用户。良好的基于​​库的 contract_vertices 将迫使用户考虑极端情况(通过强制参数),否则很容易被忽略。接受最详细的答案,谢谢!
  • @qble 很高兴能帮上忙!
【解决方案2】:

手动操作,您应该手动删除每个b 边,而不是顶点:

void collapse_vertices(V b, V a, G& g)
{
    auto be = boost::adjacent_vertices(b, g);
    for (auto beit = be.first; beit != be.second; ++beit)
    {
        add_edge(a, *beit, g);
        remove_edge(b, *beit, g);
    }
}

给出你想要的{1,3}, {1,4},

我不知道为什么(据我所知)它不包含在 BGL 中,但是这个函数就是它的作用。

【讨论】:

  • 如果您要添加的边已经存在怎么办?如果您要删除的边有属性怎么办?
  • @ravenspoint 它的行为符合预期,以最自然的方式:如果添加已经存在的边缘,则添加它,并且您始终可以检查和删除欺骗;它是手动完成的,因此传递属性由您负责。
【解决方案3】:

库中没有泛型函数,因为泛型函数不可能知道在“极端情况”中需要做什么。如果顶点 X 对顶点 A 和 B 都有边怎么办?该函数应该简单地删除 X-A,还是应该删除 X-B 并将 X-A 移动到 X--B?如果从 X 到 A 的边(被删除的顶点)具有必须保留或修改的属性怎么办?只有应用程序代码知道在删除或移动边时如何处理属性

正如 qble 所建议的那样,“委托”这些决定是没有意义的。如果关于如何处理已删除边的属性的决定“委托”给应用程序代码,那么应用程序代码将不得不查找并遍历必须删除的边。所以它必须重复通用函数所做的相同工作。它还不如自己删除边,一旦它完成了每个删除边的属性,而不是调用泛型函数。

【讨论】:

  • 确实存在必须在通用函数中处理的极端情况。但这并不意味着可以做到。当在极端情况下有选择时 - 应该通过参数将决定委托给用户。 Boost.Graph 已经将许多决策委托给了用户(例如带有 adjacency_list 的自定义容器),这就是为什么它是通用的。
  • “那么应用程序代码将不得不查找并遍历必须删除的边缘” - 不是,它不必这样做。如有疑问 - 通用版本可以只使用作为参数传递的用户仿函数。
猜你喜欢
  • 2021-02-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-14
  • 1970-01-01
  • 1970-01-01
  • 2011-11-18
  • 1970-01-01
相关资源
最近更新 更多