第 1 章 递归和回调函数

目录

1.1. 十进制转换成二进制
1.2. 阶乘
1.2.1. 私有变量的重要性
1.3. 汉诺塔
1.4. 层次数据
1.5. 应用程序和目录遍历的各种形式
1.6. 函数式编程 vs. 面向对象编程
1.7. HTML
1.7.1. 更灵活的选择方式
1.8. 当出现递归时
1.8.1. 斐波那契数
1.8.2. 剖分问题

我们要讨论的第一个“高级”技巧就是递归。 递归是通过把问题转化成同类型的更简单的问题的方式来解决问题的方法。

1.1. 十进制转换成二进制

Perl 5.6.0发布以前,想在Perl中生成二进制数并没什么好办法。从5.6.0起可以使用sprintf("%b", $num)来转换,但在此以前却是个经常困扰人的问题。

任意整数都可以表示成2k+b的形式,其中k是个较小的整数,b为0或1。b就是二进制的最后一位。要判断最后一位是0还是1很容易,只需检查输入数字是偶数还是奇数即可。剩下的数字是2k,它的二进制与k相同,只是向左移动了一位而已。例如,37 = 2·18 + 1,这里k为18而b为1,因此37的二进制形式(100101)就等于18的二进制形式(10010)末尾加了一个1。

那么怎样计算37的二进制?37是个奇数,所以最后一位必然为1,而其余部分与18的二进制相同。那么如何计算18的二进制?18为偶数,所以最后一位是0,其余部分与9的二进制相同。如何计算9的二进制?9是奇数,因此最后一位是1,其余部分与4的二进制相同。这样反复进行,最后会计算1的二进制,结果显然是1。

这个过程适用于任意整数。计算整数n的过程如下:

  1. 如果n为1,则二进制为1,可以忽略其余步骤。类似地,如果n为0,二进制也就是0。否则:

  2. 计算kb,使得n = 2k + b并且b = 0或1。只需将n除以2,那么k为商,b为余数,当n为偶数时等于0,n为奇数时等于1。

  3. 使用同样的方法计算k的二进制,结果表示为E

  4. n的二进制即为Eb

接下来建立一个名为binary()的函数来计算二进制。下面是声明部分和第一步:

sub binary {
  my ($n) = @_;
  return $n if $n == 0 || $n == 1;
		

下面是第二步:

  my $k = int($n/2);
  my $b = $n % 2;
		

第三步需要计算k的二进制。怎么办?很简单,有个很好用的函数可以计算二进制,那就是binary()——或者说,等它写完之后就有了。用k作为参数调用binary()

  my $E = binary($k);
		

最后一步为字符串连接:

  return $E . $b;
}
		

这个函数能很好地运行。比如,调用binary(37),就会得到字符串100101

这里的重点是要将问题转变成简单的情况。假设要得到数字n的二进制。我们发现它的二进制相当于一个小一点的数字k的二进制与一个二进制位b的结合。而求解k的二进制属于同一问题,但更为简单,所以只需在binary(),的定义中调用自己即可。当调用binary()并传递数字作为参数时,它会调用binary()计算另一个较小数字的二进制,相应地继续调用binary()计算更小的数字的二进制。最终,参数会变成1,binary()只需直接求出它的二进制即可。

最后一步十分重要,称为递归的基线操作base-case)。如果没有基线操作,函数就永远不会结束。如果在binary()的定义中省略了这一行:

  return $n if $n == 0 || $n == 1;
		

那么binary()就会一直计算下去,不会返回任何结果。