【问题标题】:How to topologically sort this data struct如何对这个数据结构进行拓扑排序
【发布时间】:2015-08-21 07:55:30
【问题描述】:

我正在玩弄一些依赖关系的东西,我已经走了很远,但现在我被卡住了。

假设我有一个这样的数据结构:

map<string, vector<string>> deps;

其中映射中的每个键都是一个依赖节点,该键的值是一个被依赖节点所依赖的节点列表。

此外,假设地图有 4 个键(A、B、C 和 D),具有以下依赖项:

我正在寻找一种算法(某种拓扑排序?),它将产生一个字符串向量,使得字符串按以下顺序出现:

F, B, C, D, A
0  1  2  3  4

此列表表示应评估依赖项的顺序。

【问题讨论】:

  • 你能告诉我们为什么输出应该是这样的吗?
  • 比如F排在B之前有什么原因吗?或者它们是否可以轻松切换并输出为B, F, C, D, A
  • 我最近做了这样的事情。我发现这个算法很有帮助:electricmonk.nl/log/2008/08/07/dependency-resolving-algorithm
  • @AndyG - 帖子的最后一行说明了订单的原因。
  • @Dess:我有点不同意。您需要告诉我们排序背后的逻辑。 F 首先是因为它具有最多的依赖关系并且不依赖任何东西?给我们一个函数(即使是伪代码),它比较两个节点并告诉我们左侧是否应该排在右侧之前。

标签: c++ sorting dependencies


【解决方案1】:

我最近根据this algorithm提出了一个解决方案:

这是对您的数据结构稍作修改的版本:

#include <map>
#include <string>
#include <vector>
#include <iostream>
#include <algorithm>

/**
 * Performs dependency resolution using
 * a topological sort
 */
template<typename ValueType>
class Resolver
{
public:
    using value_type = ValueType;
    using value_vec = std::vector<value_type>;
    using value_map = std::map<value_type, value_vec>;

private:
    value_vec seen;
    value_map deps;

    void resolve(value_type const& d, value_vec& sorted)
    {
        seen.push_back(d);
        for(auto const& nd: deps[d])
        {
            if(std::find(sorted.begin(), sorted.end(), nd) != sorted.end())
                continue;
            else if(std::find(seen.begin(), seen.end(), nd) == seen.end())
                resolve(nd, sorted);
            else
            {
                std::cerr << "Circular from " << d << " to " << nd << '\n';
                continue;
            }
        }
        sorted.push_back(d);
    }

public:

    /**
     * Clear the resolver ready for new
     * set of dependencies.
     */
    void clear()
    {
        seen.clear();
        deps.clear();
    }

    /**
     * Items that don't depend on anything
     */
    void add(value_type const& a)
    {
        deps[a];
    }

    /**
     * Item a depends on item b
     */
    void add(value_type const& a, value_type const& b)
    {
        deps[a].push_back(b);
    }

    value_vec resolve()
    {
        value_vec sorted;
        for(auto const& d: deps)
            if(std::find(sorted.begin(), sorted.end(), d.first) == sorted.end())
                resolve(d.first, sorted);
        return sorted;
    }
};

int main()
{
    Resolver<std::string> resolver;

    resolver.add("A", "B");
    resolver.add("A", "C");
    resolver.add("A", "D");

    resolver.add("B", "F");

    resolver.add("C", "B");
    resolver.add("C", "F");

    resolver.add("D", "C");

    resolver.add("F");

    for(auto const& d: resolver.resolve())
        std::cout << d << '\n';
}

输出:

F
B
C
D
A

如果您发现任何错误(尚未经过很好的测试),请告诉我。

从 cmets 添加:

为了提高效率,在生产代码中,如果节点类型(字符串,在此 例如)可以充满标志以将节点标记为已查看/已排序, 然后对 std::find 的调用可以替换为设置 标志的看到/排序值。当然,在这个例子中,Galik 不能这样做,这就是使用 std::find 的原因。 - @Dess

【讨论】:

  • 这成功了,谢谢。后人注意:为了提高效率,在生产代码中,如果节点类型(在此示例中为字符串)可以充满标志以将节点标记为已查看/已排序,则对 std::find 的调用可以替换为设置标志的可见/排序值。当然,在这个例子中,Galik 无法做到这一点,这就是使用 std::find 的原因。
【解决方案2】:

正如您在问题中所描述的,映射中的每个键都是一个依赖节点,该键处的值是依赖项所依赖或依赖的节点列表。

因此,实际的依赖关系类似于(例如,'from -> to' 形式的数据集):

  1. B -> A
  2. C -> A
  3. D -> A
  4. F -> B
  5. B -> C
  6. F -> C
  7. C -> D

我们的想法是首先找到起点。起点永远不会在条目的“到”一侧。一旦我们找到起点,我们就可以简单地遍历给定的集合来按顺序打印行程。以下是步骤。

  1. 以上面显示的形式创建一组给定的条目对(调用此数据集)。 “数据集”的每个条目都是“从->到”的形式。
  2. 找到行程的起点。

    • 创建一个反向集。让反向是'reverseMap'。 “reverseMap”的条目是“to->from”的形式。以下是上述示例的“reverseMap”。

      A -> B
      A -> C
      A -> D
      B -> F
      C -> B
      C -> F
      D -> C
      
    • 遍历“数据集”。对于数据集的每个键,检查它是否位于“reverseMap”中表达式的左侧。如果一个键不存在,那么我们就找到了起点。在上面的例子中,“F”是起点。

    • 从上面找到的起点开始,并考虑以下规则遍历“数据集”以打印行程。

      • 当我们遇到一个节点出现在数据集中的多个“从”位置时,请检查表达式左侧的 reverseMap 中所有“到”对应项的频率,并选择带有最低频率。

      例如,当我们以“F”作为起始节点开始时,那么我们在数据集中有两个选项可以向前移动,即“F -> B”和“F -> C”。因此,我们检查表达式左侧的 reverseMap 中“C”和“B”的频率,结果分别为 2 和 1。因此,我们继续 B 的条目 (F -> B),并丢弃另一个条目 (F -> C)。以后将不再需要此条目。

我希望这会有所帮助。与我分享,如果你发现任何错误,或者有什么要补充的。

【讨论】:

  • 感谢您的解释。它很好地描绘了算法的工作原理。
猜你喜欢
  • 2013-08-20
  • 2014-10-03
  • 2010-12-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多