递归
程序调用自身的编程技巧称为递归( recursion)。一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。
当一个比较规模比较大的问题可以分为规律相同规模比较小的子问题时,可以考虑将其转化为递归问题。
递归最少应包含两部分,递归边界与递归前进段(函数体)。
头递归
函数调用时,刚开始没有进行函数的数据运算,当函数到达递归边界时,函数再逐级反向进行递归运算,直至运算完成(调用后依赖)。头递归在调用时会在系统调用栈上产生隐式额外空间。所以运用递归时应注意递归的栈溢出。
尾递归
尾递归在运行时不会产生隐式计算,故可节省空间与时间。其中递归调用是递归函数体中的最后一条指令。并且在函数中应该只有一次递归调用。在实际编程中,应尽量用为尾递归来节省时间与空间。
头递归实例:波斐那契数列
数列从第二项开始,每一项都等于前两项之和
斐波那契数列: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, …
特别指出:第0项是0,第1项是第一个1。
#include <stdio.h>
int FibonacciSequence(int n)
{
if(n < 2) return n;
else return FibonacciSequence(n - 1) + FibonacciSequence(n - 2); // 调用递归自身
}
int main()
{
int result = FibonacciSequence(6);
printf("%d\n", result);
return 0;
}
尾递归实例:最长连续递增子序列
给定一数组,输出其第一次出现的最长的连续递增子序列
例:a[10] = {1, 3, 5, 2 ,5 ,6, 7, 0, 1, 5};
输出:2, 5, 6, 7
该题尾递归、头递归、循环迭代均可实现。
#include <stdio.h>
struct Recursion
{
int count = 0;
int increasingNum = 0;
int incraPosition = 1;
}recursion;
void IfIncreasing(int arry[], int length)
{
if (length > 0) // 递归边界
{
if (*(arry + length - 1 - 1) < *(arry + length - 1))
{
recursion.count++;
}
else
{
if (recursion.increasingNum <= recursion.count)
{
recursion.increasingNum = recursion.count;
recursion.count = 0;
recursion.incraPosition = length;
}
}
IfIncreasing(arry, length - 1); // 仅在最后一行调用递归
}
if (recursion.increasingNum == 0) // 判断整个数组全为递归,逻辑上不在递归代码之内
{
recursion.increasingNum = recursion.count - 1;
}
}
void print(int arry[])
{
int temp = recursion.incraPosition - 1;
int i;
for (i = 0; i < recursion.increasingNum; i++)
{
printf("%d ", *(arry + temp + i));
}
printf("%d", *(arry + temp + i));
}
int main()
{
int a[10] = { 1, 3, 5, 2 ,5 ,6, 7, 0, 1, 5 };
IfIncreasing(a, 10);
print(a);
return 0;
}
记忆化技术
由波斐那契数列可以看出,递归函数在运行时会产生很多重复的计算,这会造成额外的时间与空间浪费,所以在递归时,应考虑使用记忆化技术。
在计算机科学中,记忆化(英语:memoization而非memorization)是一种提高程序运行速度的优化技术。通过储存大计算量函数的返回值,当这个结果再次被需要时将其从缓存提取,而不用再次计算来节省计算时间。
记忆化是一种典型的时间存储平衡方案。 ——维基百科
通过对波斐那契数列进行记忆化技术加深对记忆化递归的理解。
C++编写如下
#include <iostream>
#include <map>
using namespace std;
class Memoization
{public:
map<int, int> calculated;
int FibonacciSequence(int n)
{
int i;
if (calculated.count(n) > 0) // 如果在map中找到之前计算过的值
{
map<int, int> ::iterator iter = calculated.find(n);
return iter->second; // 直接返回,避免重复计算
}
if (n < 2) // 递归边界
{
i = n;
calculated.insert(map<int, int> ::value_type(n, i)); // 将计算得到的值计入map中
return i;
}
else
{
i = FibonacciSequence(n - 1) + FibonacciSequence(n - 2); // 调用递归自身
calculated.insert(map<int, int> ::value_type(n, i));
return i;
}
}
};
int main()
{
Memoization fibonacciSequence;
int result = fibonacciSequence.FibonacciSequence(6);
cout << result << endl;
return 0;
}
递归碎碎念
1递归中能用尾递归就用尾递归,这样可以降低时间复杂度和空间复杂度
2.记忆化技术很有用,应熟练掌握
3.头递归堆栈溢出时,尾递归可能会有所帮助。
4.可以将递归看成数学归纳法,只需规定元素很少时(递推边界)的递推关系(函数执行主题)并假设其成立,那么当元素很多时该函数就可以完成相应操作。