确保多次返回的递归函数"保留"期望的结果

时间:2015-01-14 04:52:55

标签: c recursion

考虑这段代码(引自geeksforgeeks.org,作者Tushar Roy),如果从根到 leaf 的路径具有总和为指定值的键,则计算真或假:< / p>

bool hasPathSum(struct node* node, int sum)
{
  /* return true if we run out of tree and sum==0 */
  if (node == NULL)
  {
     return (sum == 0);
  }

  else
  {
    bool ans = 0; 

    /* otherwise check both subtrees */
    int subSum = sum - node->data;

    /* If we reach a leaf node and sum becomes 0 then return true*/
    if ( subSum == 0 && node->left == NULL && node->right == NULL )
      return 1;

    if(node->left)
      ans = ans || hasPathSum(node->left, subSum);
    if(node->right)
      ans = ans || hasPathSum(node->right, subSum);

    return ans;
  }
}

在这段代码中,作者在他的赋值中使用逻辑OR运算符来返回变量ans,以避免在返回false时覆盖返回true。我已经将代码重构为:

int hasPathSum(Treelink tree, int sum){
    if ( !tree ) { //succesful path found
        return (sum == 0);
    } else {
        sum -= tree->item;
        if ( (sum == 0) && (!tree->left && !tree->right)) 
            return 1;
        return hasPathSum(tree->left, sum) || hasPathSum(tree->right, sum);
    } 
}

虽然在这种情况下很明显,使用临时变量和/或逻辑OR运算符可以有效防止递归返回的覆盖,用于递送递归调用值的最佳方法是什么?

编辑

相反,让我们考虑一个有点人为的例子;给定一个不正确排序的二叉树(例如,作为root的密钥100,其中左子密钥为5,右子密钥为2),以递归方式从树中找到最小密钥。我会天真地接近这个:

免责声明:已编写但尚未编译。

int findMin(Tree tree, int min) {
    if (!tree) return 0; //defensive check

    if ( tree->item < min && (tree->left || tree->right) ) { 
        //Can go deeper and reestablish the min in both directions
        if ( tree->left ) 
            min = findMin(tree->left, tree->item);
        if ( tree->right ) 
            min = findMin(tree->right, tree->item);
    } else if ( tree->item >= min && (tree->left || tree->right ) ) {
        //can go deeper but should not reestablish the min
        if ( tree->left ) 
            min = findMin(tree->left, min);
        if ( tree->right ) 
            min = findMin(tree->right, min);
    } else {
        return min; //return min's final value
    }
}

可能会通过findMin(testTree, testTree->item)调用它。从根本上说;我们被迫从根分支下去探索并找到一个可能在树中任何地方的密钥(甚至是根本身!)我们知道我们必须做出分配以“拉起”#39;正确的密钥,但这很可能会覆盖真正的&#39;除非我们对每个分配进行某种有限的检查,例如。

tmpmin = findMin(tree->left);
min = (min < tmpmin) ? min : tmpmin;  

我们也许还有两个独立的变量(毕竟这是一个二叉树),它指定了左边的min和右边的min,只返回了两个的min。在这些情况下,我仍然使用某种临时变量来捎带回原来的调用者。最终,我很好奇关于在设计递归算法时避免这些类型的错误的通用启发式方法。

2 个答案:

答案 0 :(得分:3)

这是没有优化的原始示例。您现在可以看到算法更加清晰。

bool hasPathSum(struct node* node, int sum)
{
  /* return true if we run out of tree and sum==0 */
  if (node == NULL)
     return (sum == 0);

  /* otherwise check both subtrees */
  int subSum = sum - node->data;
  return hasPathSum(node->left, subSum) ||  hasPathSum(node->right, subSum);
}

关于实际问题 - 如何在这种递归中返回值,其中对数据结构中的数据没有约束。返回子树的当前最小值(或者您实际查看的任何数据结构)都可以正常工作。

只考虑问题而不考虑实际的数据结构。

current = useful_start_value
for value in data:
    if binaryOp(current, value):
       current = value   

因此,您拥有的每个值都会根据current数据对其进行测试。对于findMin函数,这会产生以下代码。我已经适应了原始数据结构。

int findMin(struct node* node, int min) {
    if (!node) return min;
    if (node->data < min)
       min = node->data;

    int leftMin = findMin(node->left, min);
    if(leftMin < min)
       min = leftMin;

    int rightMin = findMin(node->right, min);
    if(rightMin < min)
       min = rightMin;

    return min;
}

作为一个侧面 - 这样写的你也可以看到干净地访问节点的实际顺序。

我的一般建议是:

  • 不要及早优化
  • 在源代码中的一个位置执行递归调用(如果可以的话)。
  • 尝试将问题视为本地问题 - 就像在findMin中我们只需要找到最少三个值 - 所以我们就这样做了。

我已经上传了a complete working example其他功能,用于测试和生成测试树。

答案 1 :(得分:2)

编辑:我误解了原来的问题,所以phoku的答案更加相关。


C(和大多数其他语言)中的逻辑或运算符执行所谓的短路:它会在找到操作数时立即停止评估操作数。因此,如果hasPathSum(tree->left, sum)返回1,则永远不会调用hasPathSum(tree->right, sum)

This question有更详细的解释。它是为Java编写的,但C中的运算符的工作方式相同。