C语言学习第三十二天
4.10 递归
C语言中的函数可以递归调用,即函数可以直接或间接调用自身。我们考虑一下将一个数作为字符串打印的情况。前面讲过,数字是以反序生成的:低位数字先于高位数字生成,但它们必须以与此相反的次序打印。
解决该问题有两种方法。一种方法是将生成的各个数字依次存储到一个数组,然后再以相反的次序打印它们,这种方式与3.6节中itoa函数的处理方式相似。另一种方法则是使用递归,函数printed首先调用它 自身打印前面的(高位)数字,然后再打印后面的数字。
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传递给priintd的第二次调用,后者又把1传递给printd的第三次调用。第三次调用printd时首先将打印1,然后再返回到第二次调用。从第三次调用返回后的第二次调用同样也将先打印2,然后再返回到第一次调用。返回到第一次调用时将打印3,随之结束函数的执行。
另外一个能较好说明递归的例子是快速排序。快速排序算法是C.A.R.Hoare于1962年发明的。对于一个给定的数组,从中选择一个元素,以该元素为界将其余元素划分为两个子集,一个子集中的所有元素都小于该元素,另一个子集中的所有元素都大于或等于该元素。对这样两个子集递归执行这一过程,当某个子集中的元素小于2时,这个子集就不需要再次排序,终止递归。
从执行速度来讲,下列版本的快速排序函数可能不是最快的,但它是最简单的算法之一,每次划分子集时,该算法总是选取各个子数组的中间元素。
void qsort1(int v[], int left, int right) {
int i, last;
void swap(int v[], int i, int j);
for (i = left; i < right+1; i++)
printf("%d, ", v[i]);
printf("\n");
if (left >= right) /* 若数组包含的元素数少于两个 */
return; /* 则不执行任何操作 */
swap(v, left, (left + right) / 2); /* 将划分子集的元素 */
last = left; /* 移动到v[0] */
for (i = left + 1; i <= right; i++) /* 划分子集 */
if (v[i] < v[left]){
swap(v, ++last, i);
}
swap(v, left, last); /* 恢复划分子集的元素 */
qsort1(v, left, last - 1);
qsort1(v, last+1, right);
}
void swap(int v[], int i, int j) {
int temp;
temp = v[i];
v[i] = v[j];
v[j] = temp;
}
这里之所以将数组元素交换操作放在一个单独的函数swap中,时因为它在qsort函数中要使用3次。
标准库提供了一个qsort函数,它可用于对任何类型的对象排序。
递归并不节省存储器的开销,因为递归调用过程中必须在某个地方维护一个存储处理值的栈。递归的执行速度并不快,但递归代码比较紧凑,并且比相应的非递归代码更易于编写于理解。在描述树等递归定义的数据结构时使用递归尤为方便。
练习4-12 运用printd函数的设计思想编写一个递归版本的itoa函数,即通过递归调用把整数转换为字符串。
int itoaSelf(int n, char s[]) {
int i = 0;
int ln = n;
if (n < 0) {
ln = -n;
s[i++] = '-';
}
if (ln / 10)
i = itoaSelf(n / 10, s) + 1;
s[i] = ln % 10 + '0';
return i;
}