【问题标题】:Return statement that works but doesn't make much sense有效但没有多大意义的返回语句
【发布时间】:2010-09-14 05:09:52
【问题描述】:

我有以下功能:

int mult(int y, int z)
{
  if (z == 0)
    return 0;
  else if (z % 2 == 1)
    return mult(2 * y, z / 2) + y;
  else 
    return mult(2 * y, z / 2);
}

我需要做的是通过归纳证明它的正确性。现在我遇到的问题是,即使我知道它在我运行它之后有效,但我无法遵循每个单独的步骤。

让我感到困惑的是,y 只显示为一个参数,除了递归部分之外,它在任何地方都没有出现在返回中,但函数实际上返回 y 作为答案。

这是怎么发生的?我需要能够跟踪发生的所有事情,以便为证明进行迭代。

【问题讨论】:

  • 您在哪一步遇到了问题?
  • 是什么阻止你对 z 进行归纳证明?你不知道归纳证明是如何工作的吗?这不是一个真正的 C++ 问题。这是一道数学题。对 z 进行归纳。您实际上不需要了解算法。您只需要能够解释 C++ 代码并熟悉归纳证明技术。

标签: c++ algorithm recursion analysis


【解决方案1】:

由于这显然是一个家庭作业问题,我建议你做这个暗杀可能意味着你要做的事情。跟踪代码。

1) 给出 y 和 z 的起始值。 2) 在纸上或在调试器中,跟踪调用函数时发生的情况。 3) 使用当前的 y/z 值重复第 2 步,直到程序完成。

【讨论】:

  • 问题是我不得不使用没有调试器或具有任何调试功能的 IDE 的学校机器。事实是我理解递归。我不明白的是为什么我们可以得到 y 而没有像 if(z==1) return y; 之类的准确语句。
  • 当你用 z=1 调用 mult 时,z%2 ==1,所以递归将调用 mult(2*y, 1/2) + y。但是请注意,当您使用 z = 1/2 调用 mult 时,它会向下舍入到 z=0,因为它是一个整数,所以 mult(y, 0) 返回 0,所以 mult(2*y, 1/2) + y 减少到 0 + y,相当于有一个 if(z == 1) return y 这样的语句。
  • 这很简单,可以手动追踪。插入一些示例值并按照代码进行操作。
  • 如果您真的没有调试器,请添加“cout
【解决方案2】:
#include <iostream>

using namespace std;

int mult(int y, int z)
{
  if(z==0) {
    cout<<"z is null! - y:"<<y<<" z: "<<z<<endl;
    return 0;
  }
  else if (z%2==1)
  {
    cout<<"z is odd! - y:"<<y<<" z: "<<z<<endl;
    // make z even 
    return mult(2*y,z/2)+y;
  }
  else 
  {
    cout<<"z is even! - y:"<<y<<" z: "<<z<<endl;
    return mult(2*y,z/2);
  }
}


int main()  {

  cout<<"result: "<<mult(3,13)<<endl;

}

输出:

z is odd! - y:3 z: 13
z is even! - y:6 z: 6
z is odd! - y:12 z: 3
z is odd! - y:24 z: 1
z is null! - y:48 z: 0
result: 39

3 和 13 的工作原理:

有一个偶数和奇数的开关(见代码中的注释)。

当 z 为 null 时,递归“开始返回初始调用”。如果数字 z 是奇数,则将 y 添加到递归调用的返回值中,如果是偶数,则仅返回递归调用的值。

odd: return 0 + 24
odd: return 24 + 12
even: return 36
odd: return 36 + 3 

【讨论】:

    【解决方案3】:

    逐步分析

    final result: 100
     mult(10, 10)
     {
         makes 100
         mult(20, 5)
         {
             makes 100
             mult(40, 2) + 20
             {
                  makes 80
                 mult(80, 1)
                 {
                       makes 80
                      mult(160, 0) + 80
                      {
                            return 0;
                      }
                 }
             }
         }
     }
    

    【讨论】:

    • 谢谢,你最了解我的问题。我可以追踪我只是没有注意到最后 1 会在以太情况下返回 y 的代码:P
    • @njwizz 以及为什么你应该追踪它:)。我建议你无论如何都这样做,你自己和不同的价值观。然后检查程序以验证您的结果。在你的作业/考试中,你可能会遇到比这更严重的问题,所以现在就做好准备吧。
    • 无论如何我都需要追踪它,因为我需要通过归纳来证明其正确性。我只是陷入困境并给自己一个心理障碍,因为我没有马上看到它。无论如何感谢您的帮助。
    【解决方案4】:

    注意:如果这是作业,请标记它。

    所以,我们基本上得到了三个递归案例。为了让这一切更清楚,我会将 C 代码重写为一些功能性伪代码。将 mult 替换为直观的运算符符号,并找出低级表达式的描述性解释,如 (z%2==1)

    你会想出类似的东西

    a ** b = 
    | b is 0    -> 0 
    | b is even -> 2a ** (b/2) 
    | b is odd  -> 2a ** (b/2) + a 
    

    你现在明白了吗?

    【讨论】:

    • ** 是什么语言的乘法运算符?
    • 运算符符号应该与标准乘法区分开来,所以为了防止混淆,我不会使用*,而是使用一些特殊的运算符。使用x 或其他任何东西;)
    • 很遗憾您选择了**,标准的 FORTRAN 指数运算符表示乘法。只是x 会更好。
    【解决方案5】:

    一种方法是将每一行翻译成“英语”。我的翻译应该是这样的:

    如果 z 为零,则返回零
    如果 z 是奇数,则返回 mult(y*2, z/2) + y
    如果 z 是偶数,则返回 mult(y*2, z/2)

    一般的模式是递归调用mult,第一个参数加倍,第二个参数减半。

    注意这里你用 z/2 调用 mult,但它的参数是整数,所以如果你的函数继续递归,第二个参数每次都会减半,直到它下降到 1,最后是 1/2向下舍入为 0 - 此时递归将停止,因为 z==0。

    有了这些线索,你应该就能理解这个算法的工作原理了。

    【讨论】:

      【解决方案6】:

      归纳证明基于证明结果对第一个值有效,并且如果该原理对于泛型值N 是正确的,则可证明它适用于N+1

      为了简化,您可以首先证明它适用于{ 0, 1, 2 } 中的z,这对于手动测试应该是微不足道的。然后为了演示归纳步骤,您从一个通用的z=N 开始,并证明如果mult( y, N ) 是一个有效结果,那么就前一个而言,mult( y, N+1 ) 也是一个有效结果。由于偶数和奇数有不同的分支,因此您必须证明偶数和奇数N 的归纳步骤。

      【讨论】:

        【解决方案7】:

        ya = ya

        a = 偶数

        b = 下一个奇数(也就是 a + 1)

        因此,如果在给定奇数 (a 'b') 时,如果您希望上述等式仅使用偶数 (a 'a'),您可以执行以下操作:

        yb = y(a+1) = y*a + y

        现在把 'a' 写成 2*(z/2) 来迷惑大家。

        y*a 变为 (2*y)*(z/2)

        y*b 变为 ((2*y)*(z/2))+y

        由于'z'出现在偶数和奇数的公式中,我们想认为代码告诉我们 (2*y)*(z/2) = (2*y)*(z/ 2) + y 这显然是疯狂!

        原因是我们偷偷地认为 z/2 是整数,所以 z 永远不可能是奇数。当 z 为奇数时,编译器不会让我们将 z/2 分配给整数。如果我们尝试使“z”为奇数,我们将真正使用的整数是 (z-1)/2 而不是 z/2。

        为了解决这个问题,我们必须测试 z/2 是否为奇数,并根据它选择我们的公式(例如,ya 或 yb 以 'a' 表示) .

        在 mult(y,z) 中,'y' 和 'z' 都是整数。使用上面的符号 mult(2*y,b/2) 变成 mult(2*y,a/2) 因为 b/2 将被编译器截断为 a/2。

        由于我们总是会得到一个“a”作为“mult”的参数,即使我们发送一个“b”,我们也必须确保我们只使用需要“a”的公式。因此,如上所述,我们使用 ya+1 代替 yb。

        b/2 = a/2 + 1/2 但 1/2 不能表示为 int 的一部分。

        【讨论】:

          【解决方案8】:

          不是真正的答案,而是更多的建议。
          您可能希望将递归调用从 2 减少到 1:

          int mult(int y, int z)
          {
            int result = 0;  
            if (z == 0)
              return result;
            result = mult(2 * y, z / 2);  // Common between "then" and "else"
            if ((z % 2) == 1)
            {
               result += y;
            }
            return result;
          }
          

          这可以通过遵守“只有一个出口点”的规则来再次简化:

          int mult(int y, int z)
          {
            int result = 0;  
            if (z != 0)
            {
              result = mult(2 * y, z / 2);  // Common between "then" and "else"
              if ((z % 2) == 1)
              {
                 result += y;
              }
            }
            return result;
          }
          

          尽管许多编译器会自动执行这种简化,但简化代码后调试通常会更容易。调试器将在单步执行时匹配代码。

          有时简化会增加清晰度。此外,添加 cmets 将帮助您弄清楚自己在做什么以及下一个阅读代码的人。

          【讨论】:

            猜你喜欢
            • 2019-11-26
            • 2021-08-13
            • 2021-03-14
            • 1970-01-01
            • 1970-01-01
            • 2011-06-06
            相关资源
            最近更新 更多