有这么一种感觉,递归算法在思考角度上是“显得很懒惰的”,为什么这么说呢?
因为递归的本质是自己调用自己,机器这么做很干净利落,一步一步来就好,只要把每一次递归的条件改动一下就好。
但是我们人脑在思考递归时就显得很吃力……(因为在硬件的底层实现,递归是很恐怖的,要不断的开新栈,然后再逐个返回收栈,可能是我脑子的缓存不够用吧,多想那么几个复杂的就崩了)
不过,要写出一个递归函数还是很容易的。
递归函数的要义在于,每一次执行的操作是一样的,不同的是每一次的初始条件,所以与其说自己调用自己,不如理解成每次调用一个一摸一样的函数。
然后,有这个想法开始,就可以往下继续像,如果这么一直调用下去,什么时候停止呢?所以就需要有一个终止的条件。
在之后,调用一个函数是希望得到某一个结果的,那么这个return的结果就需要写出来,包括最底层的return和每一层的return。
换一种思维来考虑递归
比如最常见斐波那契数列,我们正常的思维是从零到一,而递归的实现,是“实现1需要的是0”,所以正好是反过来的。所以这样来想,对于像我这种顺序思考的大脑来说,确实是很偷懒的。
一个实现的例子
例子来自于PTA的“最大子列和”问题,其中的一种方法是“分而治之”(和归并排序的感觉很像,都是递归嘛),第一眼看上去有点吓人,不过逻辑理顺,然后一步步实现就好了。
/* 使用函数调用的方法 */
#include <cstdio>
/* 分而治之的方法 */
/* 递归的方法 */
int max3 (int A, int B, int C)
{
return A > B ? A > C ? A : C : B > C ? B : C;
}
int divideLeftRight(int listNum[], int left, int right)
{
int maxLeft, maxRight;
int maxLeftBorder, maxRightBorder;
int tempLeft, tempRight;
//处理递归结束
if (left == right) {
if (listNum[left] > 0) return listNum[left];
else return 0;
}
//处理左右“分”的过程
int mid = (left + right) / 2;
maxLeft = divideLeftRight(listNum, left, mid);
maxRight = divideLeftRight(listNum, mid + 1, right);
//处理跨越分界的情况
//意思是最大的子列不在左半边也不在右半边,所以要考虑把两边加起来看看
tempLeft = 0;
maxLeftBorder = 0;
for (int i = mid; i >= 0; i--) {
tempLeft += listNum[i];
if (tempLeft > maxLeftBorder)
maxLeftBorder = tempLeft;
}
tempRight = 0;
maxRightBorder = 0;
for (int i = mid + 1; i <= right; i++) {
tempRight += listNum[i];
if (tempRight > maxRightBorder)
maxRightBorder = tempRight;
}
return max3(maxLeft, maxRight, maxLeftBorder + maxRightBorder);
}
int maxSubseqSum(int A[], int n)
{
return divideLeftRight(A, 0, n - 1);
}
int main()
{
int N;
int num[100001] = {0};
scanf("%d", &N);
for (int i = 0; i < N; i++) {
scanf("%d", &num[i]);
}
printf("%d", maxSubseqSum(num, N));
return 0;
}