【问题标题】:Maintaining context of current node in a iterative DFS vs a recursive DFS在迭代 DFS 与递归 DFS 中维护当前节点的上下文
【发布时间】:2020-03-01 16:52:32
【问题描述】:

我遇到了一个问题,我在图中寻找一种特殊类型的节点。 该算法的工作原理如下:

bool findSpecial(Node n)
{
    if(isSpecial(n))
        return true;

    bool isSpecial = false;
    vector<Node> childs = getChildren(n);
    foreach(child, childs)
    {
       isSpecial |= findSpecial(child);
    }

    if(isSpecial)
      markCurrentNodeSpecial(n);

    return isSpecial;
}

上面是算法的模板,假设输入是DAG。 它在图中查找特殊节点,如果其 DFS 树中的任何节点是特殊的,则将当前节点标记为特殊。

该算法基本上是在可以到达的任何地方填充这个特殊属性。

然而,在极少数情况下,它可能会导致 Stack Overflow。

我试图弄清楚是否可以迭代地完成相同的事情。 迭代方法的问题是节点的所有子节点是否都已处理的信息不容易获得。

有什么建议吗?

【问题讨论】:

    标签: c++ recursion stack depth-first-search


    【解决方案1】:

    1) 最简单的解决方案 - std::stack&lt;Node&gt; 会起作用吗?您应该让它像程序堆栈一样工作 - 将起始 Node 推到那里,然后弹出一个节点,检查它是否特殊,如果不是 - 推它的子节点,重复。

    2) 你不检查你是否已经访问过一个节点——也许你可以加快一点速度。 (已编辑:我无法阅读)

    更新:是的,这是一只有趣的野兽。这段代码怎么样?虽然我没有测试它,但主要思想应该有效:将访问分为两个步骤 - 递归之前和之后。

    struct StackNodeEntry
    {
        Node cur;
        optional<Node> parent;
    
        enum class Pos
        {
            before_recursion, after_recursion
        } pos;
    };
    
    bool findSpecial(Node root)
    {
        stack<StackNodeEntry> stack;
    
        stack.push({ root, {}, StackNodeEntry::Pos::before_recursion });
    
        while (!stack.empty())
        {
            auto& e = stack.top();
    
            switch (e.pos)
            {
            case StackNodeEntry::Pos::before_recursion:
                if (isSpecial(e.cur))
                {
                    if (e.parent)
                        markCurrentNodeSpecial(*e.parent);
                    stack.pop();
                    break;
                }
    
                for (auto n : getChildren(e.cur))
                    stack.push({ n, e.cur, StackNodeEntry::Pos::before_recursion });
                e.pos = StackNodeEntry::Pos::after_recursion;
                break;
    
            case StackNodeEntry::Pos::after_recursion:
    
                if (isSpecial(e.cur))
                {
                    if (e.parent)
                        markCurrentNodeSpecial(*e.parent);
                }
                stack.pop(); // don't use e below this line
                break;
            }
        }
    
        return isSpecial(root);
    }
    

    【讨论】:

    • 我不明白你的第一点。在当前算法中,如果一个节点有五个邻居,那么在对这五个邻居进行处理后,可以看出当前节点是否特殊,并标记相同。在进行迭代时,如何在 std::stack 中维护该信息,我将推送邻居并弹出当前节点。该算法是一个模板,我会检查一个节点是否已经被访问过。
    • 是的,你说得对,我应该多考虑一下。我认为它需要堆栈与它包含的每个节点都有一些上下文 - 类似于递归 findSpecial() 当前执行上下文的位置。类似于 struct StackNodeEntry 的东西,它包含 Node、一些枚举和一个表示 isSpecial 变量的布尔值,如果枚举表示 findSpecial 中布尔值有效的位置。
    • 我已经添加了一些代码,但不知道如何 ping 你。
    • 看了你的代码,这是一个有趣的解决方案,所以基本上不是在处理节点时弹出节点,而是在处理其子节点时弹出节点。
    猜你喜欢
    • 2015-01-17
    • 1970-01-01
    • 2015-02-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-14
    • 2021-09-30
    相关资源
    最近更新 更多