【问题标题】:Revisiting some nodes in a recursive function (DFS) over a tree在树上重新访问递归函数 (DFS) 中的某些节点
【发布时间】:2015-09-13 08:22:57
【问题描述】:

我以深度优先的方式遍历树的节点。假设树如下:

现在,假设我在节点 E 中,并且在某些情况下我想回到节点 C 并从那里继续。然后应该取消之前的遍历并且应该再次评估节点CDE。节点FG 不应被遍历两次,因为之前的递归导航已被取消!

Usual navigation : A B C D E F G
The desire navigation : A B C D E C D E F G

深度优先遍历的一般代码如下:

void DFS(node x)
{
    z = evaluate(x);
    // if (z != null) DFS(z) 
    // Z could be a node which has been already traversed, 
    // let's suppose it's an ancestor of x

    foreach (node y in c.children)
    {
        DFS(y);
    }
}

请帮助我如何在树中进行这样的导航?

【问题讨论】:

  • 您能否再解释一下,声明中并不清楚。您正在做 DFS,并且您现在在某个节点访问了一些节点,您评估了一些东西,为此您想重新评估上一级中的节点?
  • @Anup 是的,我应该取消递归并返回一个节点并继续从那里遍历
  • 好的,这意味着如果我在 E 中评估了某些东西,那么我必须重做 C、D、E、F,而对于 A,它会继续原样吗?
  • z 是否总是 x 的祖先(因为它会用于,例如,循环取消)?
  • @Ahmad Clear it man if you hit something at E then as you didn't evaluate F and G so you are not going to evaluate them and you will evaluate all the previous nodes but only on the current level因为你没有提到重新评估B或A。?是吗?

标签: algorithm recursion tree


【解决方案1】:

我将尝试使用全局变量 cancel 来概述伪代码。

boolean cancel = false;

void DFS(node x, parent p)
{
    if(!cancel) {
        foreach (node y in x.children) {
            DFS(y, x);
        }
    } else {
      cancel = false;
      DFS(p, findParent(p));
    }
}

但是,这种方法存在一个问题。一旦在 foreach 部分开始遍历,循环中对 DFS 方法的每个后续调用都会从父节点调用 DFS。为了解决这个问题,我建议您使用自己的堆栈来模拟深度优先遍历,而不是采用递归方法。这样,当cancel 变为true 时,您可以清除堆栈并确保来自父级的DFS 调用只发生一次。希望这会有所帮助!

以下几行中的某些内容应该有效:

boolean cancel = false;
Stack<Node> s;

void DFSIterative(Node x, Node p) {
    if(cancel) {
        resetDFS(p);
    } else {
        s.push(x);
        while(!s.isEmpty()) {
            x = s.pop();
            p = findParent(x);
            if(cancel) resetDFS;
            else {
                foreach(node y in x.children) {
                    s.push(y);
                }
            }
        }
    }
}

void resetDFS(Node p) {
    s.clear();
    cancel = false;
    DFSIterative(p, findParent(p));
}

我将 findParent() 辅助方法的实现留给您。请注意,您还需要注意将节点标记为已访问,然后在取消 DFS 时将相关节点取消标记为未访问。

【讨论】:

  • p在这里的作用是什么?
  • 表示被遍历的当前节点的祖先。
  • 能不能用两个栈在节点中来回移动?
【解决方案2】:

根据你想要备份的树多远,这样的事情应该可以工作。

DFS函数返回重试的层数:

  • 0 正常进行
  • 1 重试同一节点
  • 2 重试父...

代码:

int DFS(node x)
{
    if (some condition)
    {
        // return the number of parent levels you want to back up
        return 2;
    }

    for (int i = 0; i < x.children.size; ++i)
    {
        int redo = DFS(x.children[i]);

        if (redo == 1) {
            // redo == 1 means retry the current node
            --i;
        }
        if (redo > 1) {
        {
            // redo > 1 means retry an ancestor node
            return redo - 1;
        }
    }
    return 0;
}

显然你必须小心你的条件,否则你会陷入无限循环。

使用上面的基本代码,以下条件将返回A B C D E C D E F G

boolean retryE = true;

int DFS(node x)
{
    if (x.value == "E" && retryE)
    {
        retryE = false;
        return 2;
    }

    // remaining code as above
}

更新

再看一遍,如果您的评估函数返回一个祖先节点而不是多个级别,这可能更接近您最初想要的...如果返回的节点不是当前子节点的祖先,则会失败...

// returns null to continue DFS, or a node value to repeat from that node
Node DFS(Node x)
{
    Node z = evaluate(x)

    if (z != null)
    {
        return z;
    }

    for (int i = 0; i < x.children.size; ++i)
    {
        Node child = x.children[i];
        Node result = DFS(child);

        if (result != null)
        {
            if (result == child)
            {
                // current child is the one to retry so just
                // decrement the counter to retry it
                --i;
            } else {
                // retry a node but not this child so return it up the stack
                return result;
            }
        }
    }

    return null;
}

更新 2

使用相同的 DFS 函数,考虑这个评估函数,它在 E 和 F 第一次出现时返回 C

boolean retryE = true;
boolean retryF = true;

evaluate(Node x)
{
    if (x.value == "E" && retryE)
    {
        retryE = false;
        return C;
    }
    if (x.value == "F" && retryF)
    {
        retryF = false;
        return C;
    }
    return null;
}

这将使用--i 递减方法(返回A B C D E - C D E F - C D E F G)正常工作,但如果直接调用DFS(child) 则不能,除非以某种方式处理了第二次调用的结果。

干杯

【讨论】:

  • 非常感谢,看起来很有希望,我正在测试它
  • 要重试一个孩子,我不能只打电话给DFS(child)而不是--i吗?
  • @Ahmad 如果您调用 DFS(child),它将处理所有子级,但之后会从中断处恢复。所以在你上面的例子中,你会得到 ABCDE - CDEF - FG,所以你会得到一个额外的 F。上面两种方法的本质是递归回溯堆栈到节点重复,然后从那个点重新开始。
  • 我明白你关于回溯的观点,只是我的意思是你在哪里使用if (result == child)你减少了i,这在下一个循环中调用DFS(child),但你可以直接调用DFS(child) .
  • @Ahmad 您可以直接调用 DFS(child),但我认为它不会在所有情况下都有效,除非您还以某种方式处理了第二次调用的结果。查看上面更新 2 中的代码,其中节点 E 和节点 F 都会导致递归回溯。这适用于 --i 回溯,但是如果直接调用 DFS(child) ,您仍然需要在某处考虑第二次调用的结果......您可能仍然可以这样做,但需要更多思考.
【解决方案3】:

看到这里我可以看到你使用了一个 void DFS,你的函数没有返回任何东西,所以你可以使用该值来检查是否需要重新评估某些东西。

这样

int DFS(node x)
{
    int ret=0;
    z = evaluate(x);
    // if (z != null) DFS(z) Z could be a node which has been already traversed 
    foreach (node y in c.children)
    {
        ret=DFS(y);
        if(ret==1)
          break;
    }
    if(ret==1)
       DFS(x);

    if(z==(want to reevaluate))
        return 1;
    else
        return 0;
}

现在,如果您希望它在其所有子项上重做 DFS,您可以简单地返回到父 1,如果您希望它继续继续,您可以简单地返回 0。 如果在这种情况下 A 的任何子节点返回 1 所有子节点和该节点将被重新评估,并且它上面的节点将继续以与以前相同的方式。

所以你的形象。如果 E 返回 1,那么所有节点 C、D、E 将被重新评估。如果您将返回值固定为返回距离或其他东西,那么这也可以使用变量来完成,您只需将其地址发送给所有孩子并注意其值。

【讨论】:

  • 非常感谢,我认为另一个答案更笼统。你假设 z 是父级,而它可以是任何祖先
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-09-30
  • 1970-01-01
  • 2020-03-01
  • 2018-10-31
  • 1970-01-01
  • 2021-04-17
  • 1970-01-01
相关资源
最近更新 更多