理解递归代码的行为

时间:2017-04-29 13:43:20

标签: algorithm recursion

我正在阅读一份测试的旧问题文件,我计划在几周内回答这个问题,以下问题让我受阻:

有限的比特序列表示为具有集合{0,1}的值的列表 - 例如,[0,1,0],[1,0,1,1]。

[]表示空列表,[b]是由一位b组成的列表。对于非空列表l, head(l)返回l的第一个元素, tail(l)返回通过从l中删除第一个元素获得的列表。

a:l 表示通过在列表l的头部添加a而形成的新列表。

例如:

•head([0,1,0])= 0,tail([0,1,0])= [1,0],

•head([1])= 1,tail([1])= []和

•1:[0,1,0] = [1,0,1,0]。

考虑以下功能:

f1 将列表作为输入并返回另一个列表。

f1(s):
 if (s == []) then return([1])
  else if (head(s) == 0) then return(1:tail(s))
  else if (head(s) == 1) then return(0:f1(tail(s)))
 endif

f2 将位和列表作为输入并返回一位。

f2(b,s):
 if (s == []) then return(b)
  else if (head(s) == 0) then return(f2(not(b),tail(s)))
  else if (head(s) == 1) then return(not(b))
 endif

g1 将非负数作为输入并返回一个列表。

g1(n):
 if (n == 0) then return([0])
  else return f1(g1(n-1))
 endif

g2 将非负数作为输入并返回一位。

g2(n):
 if (n == 0) then return(0)
  else return f2(g2(n-1),g1(n))
 endif
  1. g2(7)和g2(8)的值是什么?
  2. g2(256)和g2(257)的值是什么?
  3. 到目前为止,我只能理解 f1 的行为(如果它是0并且返回,则将头部替换为1。否则,将头部替换为0并将其自身调用列表的其余部分。)和 g1(n) f1 相同,适用于[0] n 次(f1(f1) (... f1([0]))) - n次)。

    是否有一些结构化的方法可以解决这个问题和类似的问题?(特别是在时间压力下。)

    编辑: f2 0 位反转为第一次 1 <之前 0&#39; 的次数/ strong>在列表中

    g2()仍然是一个谜。

1 个答案:

答案 0 :(得分:1)

Paper&amp;笔评估

这样做的最好方法就是开始。获取您面前的功能定义,然后开始使用笔和放大器。论文评估。问题要求我们比较g2(7)g2(8),但是看g2我们需要了解f2g1,所以让我们从那里开始。因为首先计算g1的值,所以我们首先要熟悉g1

我要开始将一些价值推入其中并获得结果。我将从0开始,因为在查看g1后,这似乎是最容易计算的

g1(0) => [0]

g1(1) => f1(g1(0)) => ... stop, do not recompute `g1(0)` here, we already know the answer above
      => f1([0])
      => [1]

g1(2) => f1(g1(1)) => we already know `g1(1)` from above
      => f1([1])
      => 0 : f1([])
      => [0,1]

g1(3) => f1(g1(2)) => we already know `g1(2)`
      => f1([0,1])
      => [1,1]

g1(4) => f1(g1(3)) => reuse `g1(3)`
      => f1([1,1])
      => 0 : f([1]) => we already know `f1([1])`
      => 0 : [0,1]
      => [0,0,1]

我已经在这里看到了一种模式。你呢? g1正在为little endian bit order中的n生成1和0的二进制序列

g1(0) => [0]
g1(1) => [1]
g1(2) => [0,1]
g1(3) => [1,1]
g1(4) => [0,0,1]
g1(7) => ?
g1(8) => ?

我的猜测分别为[1,1,1][0,0,0,1]。让我们继续看看我们是否正确......

g1(5) => f1(g1(4))
      => f1([0,0,1])
      => [1,0,1]

g1(6) => f1(g1(5))
      => f1([1,0,1])
      => [0,1,1]

g1(7) => f1(g1(6))
      => f1([0,1,1])
      => [1,1,1]

g1(8) => f1(g1(7))
      => f1([1,1,1])
      => 0 : f([1,1]) => we already know f1([1,1])
      => 0 : [0,0,1]
      => [0,0,0,1]

取得良好进展

嘿,我们的猜测是正确的!评估g(0)到g(8)的时间大约需要30-45秒 - 这大约是5.5分钟,我们非常了解g1f1的工作原理。我们更好地理解g1,因为我们知道它只需要一个数字并吐出二进制位序列来表示输入数字。 f1如何确切地起作用时有点神奇,但关于这一点很酷的部分是无关紧要的 - 问题要求我们比较g2的值,所以只要我们能够计算g2的值,如果我们对其他函数有了很好的理解就无所谓了。

上次评估g1给了我们一些关于f1的宝贵见解,看起来g2f2相关的情况也是如此。现在我们可以参考g1的某些值,让我们尝试计算一些g2的值 - 我将从0开始,就像我们上次那样

g2(0) => 0

g2(1) => f2(g2(0),g1(1)) => we already know `g2(0)` and `g1(1)`
      => f2(0, [1])
      => 1

g2(2) => f2(g2(1), g1(2)) => we already know these!
      => f2(1, [0,1])
      => f2(0, [1])
      => 1

g2(3) => f2(g2(2), g1(3))
      => f2(1, [1,1])
      => 0

g2(4) => f2(g2(3), g1(4))
      => f2(0, [0,0,1])
      => f2(1, [0,1])
      => f2(0, [1])
      => 1

此时,我还没有看到很多的模式。起初我想也许g2会告诉我们给定的整数是偶数还是奇数,但绝对不是这样。我正在考虑的唯一另一件事是,对于2的幂,它可以返回1,对于2的非幂,它可以返回0。这将是一个奇怪的函数。让我们继续找出

g2(5) => f2(g2(4), g1(5))
      => f2(1, [1,0,1])
      => 0

g2(6) => f2(g2(5), g1(6))
      => f2(0, [0,1,1])
      => f2(1, [1,1])
      => 0

g2(7) => f2(g2(6), g1(7))
      => f2(0, [1,1,1])
      => 1

g2(8) => f2(g2(7), g1(8))
      => f2(1, [0,0,0,1])
      => f2(0, [0,0,1])
      => f2(1, [0,1])
      => f2(0, [1])
      => 1

出现意外模式

好的,我们已达到g2(7) == 1g2(8) == 1。我们的2次幂理论肯定没有成功,但是没关系,因为我们可以看到另一种模式已经出现 - 如果位序列包含奇数{{1},g2将返回1如果位序列包含偶数个1 s,它将返回0

我在这里制作了一个小小的真值表来检查我的猜测

1

所以g1(0) => [0] ones(0) => 0 odd?(ones(0)) => 0 g2(0) => 0 g1(1) => [1] ones(1) => 1 odd?(ones(1)) => 1 g2(1) => 1 g1(2) => [0,1] ones(2) => 1 odd?(ones(2)) => 1 g2(2) => 1 g1(3) => [1,1] ones(3) => 2 odd?(ones(3)) => 0 g2(3) => 0 g1(4) => [0,0,1] ones(4) => 1 odd?(ones(4)) => 1 g2(4) => 1 g1(5) => [1,0,1] ones(5) => 2 odd?(ones(5)) => 0 g2(5) => 0 g1(6) => [0,1,1] ones(6) => 2 odd?(ones(6)) => 0 g2(6) => 0 g1(7) => [1,1,1] ones(7) => 3 odd?(ones(7)) => 1 g2(7) => 1 g1(8) => [0,0,0,1] ones(8) => 1 odd?(ones(8)) => 1 g2(8) => 1 相当于g2(x),至少在odd?ones(x)。评估0 <= x <= 8g2(0)大约需要10分钟,分析模式可能需要5-10分钟,总计约25分钟。但是现在我们知道在不进行所有繁琐的逐步递归的情况下评估g2(8)g(256)所需的一切

代码已被破解

将256转换为二进制,我们知道g(257)g1(256),同样[0,0,0,0,0,0,0,0,1]g1(257)因此很容易计算[1,0,0,0,0,0,0,0,1]并将其与{{{}}进行比较1}}现在

g2(256)

这就是全部

g(257)

为此添加几分钟,我们应该在30分钟左右 - 根据熟练程度和识别模式的能力,给予或取50%。

选择一种模式,任何模式

哦,嗯,也许你找到了一个不同的模式 - 这完全没问题!重要的是你尽可能地检查它(记住你的时间限制)。

  

g1(256) => [0,0,0,0,0,0,0,0,1] ones(256) => 1 odd?(ones(256)) => 1 g2(256) => 1 g1(257) => [1,0,0,0,0,0,0,0,1] ones(257) => 2 odd?(ones(257)) => 0 g2(257) => 0 将位g2(7) => 1 g2(8) => 1 g2(256) => 1 g2(257) => 0 反转为列表f2中第一个1之前的0的次数。

我发现您对b有所了解,但这有点问题,因为s最初等于f2。假设我们要计算b,其中g2(n - 1)f2(b, s) ...我们被卡住了,因为我们还不知道那是什么。不幸的是,这是b

的死胡同

这就是为什么我能够(或者幸运地)为已知输入的g2(1234)f2建立关联,这与{{{ 1}}或g1。一旦我能够看到g2f1的工作方式,我甚至无需计算f2g1来评估g2和{{ 1}}。这是一个巨大的收获,因为我有效地削减了我的笔和f1f2。论文评估模型仍然得出了g(256)g(257)的正确答案。如果没有达到这一点,我将一直在寻找另一种相关性或者一直到f1进行手动评估......这对于考试来说会花费太长时间。