4.10 递归
C 语言中的函数可以递归调用,即函数可以直接或间接调用自身
我们考虑一下将一个数作为字符串打印的情况
前面讲过,数字是以反序生成的:低位数字先于高位数字生成,但它们必须以与此相反的次序打印
解决该问题有两种方法
一种方法是将生成的各个数字依次存储到一个数组中,然后再以相反的次序打印它们,这种方式与 3.6 节中 itoa
函数的处理方式相似
另一种方法则是使用递归,函数 printd
首先调用它自身打印前面的(高位)数字,然后再打印后面的数字
这里编写的函数不能处理最大的负数
#include <stdio.h>
/* printd: print n in decimal */
void printd(int n)
{
if (n < 0) {
putchar('-');
n = -n;
}
if (n / 10)
printd(n / 10);
putchar(n % 10 + '0');
}
函数递归调用自身时,每次调用都会得到一个与以前的自动变量集合不同的新的自动变量集合
因此,调用 printd(123)
时,第一次调用 printd
的参数 n = 123
它把 12
传递给 printd
的第二次调用,后者又把 1
传递结 printd
的第三次调用
第三次调用 printd
时首先将打印 1
,然后再返回到第二次调用
从第三次调用返回后的第二次调用同样也将先打印 2
,然后再返回到第一次调用
返回到第一次调用时将打 3
,随之结束函数的执行
另外一个能较好说明递归的例子是快速排序
快速排序算法是 C. A. R. Hoare 于 1962 年发明的
对于一个给定的数组,从中选择一个元素,以该元素为界将其余元素划分为两个子集
一个子集中的所有元素都小于该元索,另一个子集中的所有元素都大于或等于该元素
对这样两个子集递归执行这一过程,当某个子集中的元素数小于 2 时,这个子集就不需要再次排序,终止递归
从执行速度来讲,下列版本的快速排序函数可能不是最快的,但它是最简单的算法之一
在每次划分子集时,该算法总是选取各个子数组的中间元素
/* qsort: sort v[left]...v[right] into increasing order */
void qsort(int v[], int left, int right)
{
int i, last;
void swap(int v[], int i, int j);
if (left >= right) /* do nothing if array contains fewer than two elements */
return;
swap(v, left, (left + right) / 2); /* move partition elem */
last = left; /* to v[0] */
for (i = left + 1; i <= right; i++) /* partition */
if (v[i] < v[left])
swap(v, ++last, i);
swap(v, left, last); /* restore partition elem */
qsort(v, left, last - 1);
qsort(v, last + 1, right);
}
这里之所以将数组元素交换操作放在一个单独的函数 swap
中,是因为它在 qsort
函数中要使用了 3 次
/* swap: interchange v[i] and v[j] */
void swap(int v[], int i, int j)
{
int temp;
temp = v[i];
v[i] = v[j];
v[j] = temp;
}
标准库中提供了一个 qsort
函数,它可用于对任何类型的对象排序
递归并不节省存储器的开销,因为递归调用过程中必须在某个地方维护一个存储处理值的栈
递归的执行速度并不快,但递归代码比较紧凑,并且比相应的非递归代码更易于编写与理解
在描述树等递归定义的数据结构时使用递归尤其方便