【问题标题】:Recursion: Understanding (subset-sum) inclusion/exclusion pattern递归:理解(子集和)包含/排除模式
【发布时间】:2018-09-29 01:52:54
【问题描述】:

我需要了解这种递归是如何工作的,我了解简单的递归示例,但更高级的递归示例很难。即使我认为只有两行代码有问题...... return 语句本身。我只是对它的工作原理持空白,尤其是 and/or 运算符。非常欢迎任何见解。

      bool subsetSumExists(Set<int> & set, int target) {
      if (set.isEmpty()) {
         return target == 0;
      } else {
         int element = set.first();
         Set<int> rest = set - element;
         return subsetSumExists(rest, target)
             || subsetSumExists(rest, target - element);
         } 
      }

【问题讨论】:

  • 那么您在理解递归或布尔运算符and/or时遇到了困难?
  • 两者都有!就像和/或如何选择其中一个返回?第一个还是第二个?

标签: c++ recursion subset-sum


【解决方案1】:

递归代码通常与归约的概念相结合。一般来说,归约是一种通过某种变换将未知问题归结为已知问题的方法。

让我们看一下您的代码。您需要确定是否可以从输入数据集的元素构造给定的目标总和。 如果数据集为空,除了将目标总和与0进行比较之外,别无他法。

否则,让我们应用缩减。如果我们从集合中选择一个数字,实际上可能有 2 种可能性 - 选择的数字参与您正在寻求的总和或不参与。这里没有其他可能性(涵盖所有可能性非常重要!)。事实上,选择哪个数据元素并不重要,只要您能涵盖剩余数据的所有可能性即可。

第一种情况:数字不参与求和。我们可以将问题简化为一个较小的问题,数据集没有被检查的元素和相同的目标总和。

第二种情况:数字参与求和。我们可以将问题简化为一个较小的问题,数据集没有被检查的元素,并且请求的总和减少了数字的值。

请注意,此时您不知道这些情况是否属实。您只需继续减少它们,直到找到可以确定答案的微不足道的空案例。

如果这两种情况中的任何一种情况都是正确的,那么原始问题的答案就是正确的。这正是运算符 || 所做的 - 如果它的任何操作数(两种情况的结果)为真,它将产生真。

【讨论】:

    【解决方案2】:

    || 是逻辑或。它从左到右进行评估并短路。

    这意味着在表达式 A || B 中,A 首先被计算。如果是true,则整个表达式为true,并且不进行进一步评估。如果Afalse,则计算B,表达式得到B 的值。

    在您的示例中,A 是“尝试在不使用集合中的第一个元素的情况下获得相同的总和”。 B 是“使用集合中的第一个元素,这会减少总和,并尝试与其余元素一起获得。”

    【讨论】:

      【解决方案3】:

      让我们先看看算法..

      • 基本情况(即递归终止的情况)是集合为空的情况。

      • 否则程序会将第一个元素从集合中减去。

      • 现在它将调用subsetSumExists(rest, target) 并检查其是否为真, 如果是,它将返回 true 否则它将调用 subsetSumExists(rest, target - element) 并返回任何内容 返回。

      简单来说,只有当第一个 subsetSumExists(rest, target) 返回 false 时,它​​才会调用 subsetSumExists(rest, target - element)

      现在让我们尝试用一个小样本集 {3,5} 和总和 8 来干运行这段代码。从现在开始我将调用函数 sSE

      sSE({3,5}, 8) => "sSE({5}, 8) || sSE({5},(8-3))"
      
      sSE({5}, 8)   => sSE({}, 8) || sSE({}, (8-5)) 
      
      sSE({}, 8)    => false.. now will call sSE({}, (8-5))
      
      sSE({}, 3)    => false.. now will call sSE({5}, (8-3))
      
      sSE({5}, 5)   => sSE({}, 5} || sSE({}, (5-5))
      
      sSE({}, 5)    => false.. now will call sSE({}, (5-5))
      
      sSE({}, 0)    => true.. ends here and return true
      

      【讨论】:

        【解决方案4】:

        要理解递归,您需要了解递归。 为此,您需要反复思考。

        在这种特殊情况下。

        任何人:subsetSum(set, target)

        1. 如果set 为空且target 为0,则subsetSum 存在

        2. 否则,删除set 的第一个元素。检查subdetSum(set, target) 是否存在或subdetSum(set, target - removed_element) 存在(使用步骤0)

        【讨论】:

        • +1 表示“要理解递归,您需要了解递归”
        • 奇怪!当我读到“要理解递归,你需要理解递归”时,我有一种似曾相识的感觉。
        【解决方案5】:

        集合减法看起来是一种奇怪的语法,但我认为它意味着元素上的 pop()。

        它通过找到所有可能的组合来“起作用”,尽管它是指数级的。

        || 语句中,LHS 是包含当前元素的总和,RHS 是不包括它的总和。因此,您将在指数树中得到每个元素的每个组合打开或关闭。

        顺便说一下,指数意味着如果您有 30 个元素,它将产生 2 的 30 次方,即0x40000000 或接近十亿个组合。

        当然你可能会用完内存。

        如果它找到解决方案,它可能不会运行所有 2^N 案例。如果没有解决方案,它将始终访问它们。

        【讨论】:

          【解决方案6】:

          如果我为自己说话,理解问题的困难源于|| 运算符。我们换一种方式看一下相同代码的底部return语句,

          if (subsetSumExists(rest, target - element))
                  return true;
          if (subsetSumExists(rest, target))
                  return true;
          
          return false;
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2013-10-05
            • 1970-01-01
            • 1970-01-01
            • 2021-06-22
            • 2012-04-14
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多