【问题标题】:using backtracking with tree使用树回溯
【发布时间】:2011-07-06 23:56:24
【问题描述】:

感谢堆栈(带有推送和弹出),我在树中使用回溯算法。 它有效,但我有一个问题。堆栈给出的路径是“错误的”。

bool Prefix(Node*root, stack *tas, char *search)
{
    if (!isEmpty(root))
    {
        if (strcmp(search,root->data) != 0)
            push(tas, root->data, search, root);

        if (strcmp(search,root->data) == 0)
        {
            return True ;
        }

        Prefix(root->left, tas, search);
        Prefix(root->right, tas, search);
    }
    return False;
}

例如,我有一棵树:

     Root
    /    \    
   A      B
  / \    / \
 C   D  E   F    

例如,当我想要 C 的路径时,此函数返回 R A B(适用于 R 和 A,而不是 B)。

不知道是来自这个函数还是push()函数。 如果你在函数中没有看到任何错误,我会粘贴 push() 但它很长。

编辑:我现在更好理解了, 我已添加到函数中:
如果节点是叶子,pop()。 如果我搜索 F,它会返回 R A B F 而不是 R B F。我不知道如何避免将 A 保留在堆栈中。

编辑2:

这里是代码:(返回 R A B F 而不是 R B F)

bool Prefix(Node*root, stack *tas, char *search)
{
    if (!isEmpty(root))
    {
        if (strcmp(search,root->data) == 0)
            return True ;

        if (LeafOrNot(root) == True ){  //it's a leaf, pop()
            pop(tas);

        if (Prefix(root->left, tas, search))
            return True;
        if (Prefix(root->right, tas, search))
            return True;
    }
    return False;
}

我不明白如何弹出遍历子节点以获得好的结果,也许添加 if (Prefix(root->left, tas, search)) 的 else ?有人有想法吗?

谢谢!

【问题讨论】:

  • 你快到了。如果您不仅在叶节点处而且在遍历任何子节点之后从堆栈中弹出,这将为您提供所需的结果。只要确保找到节点时不要从堆栈中弹出即可。如果您正在搜索 D,它将是 push Root、push A、push C、pop C、push D、found...展开调用链。如果您正在搜索 F:push Root、push A、push C、pop C、push D、pop D、pop A、push B、push E、pop E、push F、found、unwind... 希望这会有所帮助.
  • 我尝试过另一个 pop(tas) 但它不起作用。我需要更改代码的结构吗?谢谢

标签: c tree stack backtracking


【解决方案1】:

至少有一个问题是您没有检查对Prefix 的调用的返回值,因此您不知道递归调用是否“完成”(并且您应该停止探索)。

查看这一点的简单方法是遍历函数调用(给定您的示例树):

前缀(“根”) 找到“C”? 否:前缀(“A”) 找到“C”? 否:前缀(“C”) 找到“C”? 是:返回真 (不检查前缀(“C”)):前缀(“D”) 找到“C”? 否:返回假 前缀(“B”) 找到“C”? 否:前缀(“E”) 找到“C”? 否:返回假 前缀(“F”) 找到“C”? 否:返回假 返回假 返回假

这显示了调用的顺序,并且缩进大致对应于调用堆栈上的帧。

您可以查看检查对Prefix 的调用是否返回true 将允许您在适当的时间退出。

【讨论】:

  • 另外,发帖者需要添加两个分支吗?
  • 你至少可以看到你在每一步得到了什么。简单的 printf 可以提供帮助:)
  • @Mitch:算法如何探索两个子节点?
  • @Mitch:假设你想找到D 的路径,你只探索左边的分支,你找不到路径,尽管存在一个。 OP 的函数只记录算法访问过的节点(不是所有候选访问),因此要枚举路径,它必须探索所有可用的分支。
  • @Mitch:当然,二叉搜索树具有该属性,直二叉树没有排序要求。鉴于 OP 的原始功能和描述,我认为没有理由认为它是 BST。 (另外,OP 公开声明使用回溯算法,这需要......回溯)
【解决方案2】:

我必须同意@Mark Elliot,但乍一看,他的回答似乎令人困惑。您确实需要一个停止条件,这样您就不必继续探索其他节点并将它们添加到堆栈中。您正在返回一个布尔值,因此您可以使用它来测试调用是否找到了您正在寻找的节点。

如果您打算在堆栈中包含 C 路径中的最后一个节点,那么您应该在添加到堆栈时删除字符串比较。

例如,您可以这样做。

bool Prefix(Node*root, stack *tas, char *search)
{
    if (!isEmpty(root))
    {
        push(tas, root->data, search, root);

        if (strcmp(search,root->data) == 0)
        {
            return True;
        }

        if (Prefix(root->left, tas, search))
            return True;
        if (Prefix(root->right, tas, search))
            return True;
    }
    return False;
}

【讨论】:

  • 这里的推送过分了;唯一需要存储在堆栈上的是 Node*。一种有效的方法是从程序堆栈中分配 Node 堆栈元素,然后在找到匹配的 Node 时处理 Node 堆栈。例如,typedef struct NodeStack NodeStack; struct NodeStack { Node* node; NodeStack* link; }; ... bool Prefix(Node* node, NodeStack* link, char* search){ if(!isEmpty(node){ NodeStack elem = { node, link }; if(strcmp(search, node->data) == 0){ processStack(&elem); return true;} if(Prefix(node->left, &elem, search)) return true;
  • 另外,尾递归可以转换为迭代,只需将if(!isEmpty... 更改为while 并将第二个if(Prefix... 替换为node = node->right;(我将root 替换为@987654329 @,因为它是一个任意节点,而不是根;坏名称会导致坏代码)。
  • 所有优点,但如您所见,我的代码示例只是对原始代码的轻微修改,以指出 Mark 的答案。
  • 是的,我赞成,但这是一个教育网站。我不是在批评您的代码,而是在批评 OP 的推送调用,并指出了额外的优化。注意:我在此处包含这些 cmets 的原因是因为您的答案是最佳答案,并且 OP 最有可能出现在哪里,因此是指出这些改进的最佳位置。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-21
  • 2014-12-17
  • 1970-01-01
  • 1970-01-01
  • 2021-12-28
  • 2018-07-07
相关资源
最近更新 更多