我们先看一个简单的fibonacci递归程序:
#include <iostream>
#include <windows.h>
using namespace std;
int fun(int n)
{
if(1 == n || 2 == n)
{
return n;
}
return fun(n - 1) + fun(n - 2);
}
int main()
{
int i = 0;
int n = 1000; // 计算1000次
int t1 = GetTickCount();
for(i = 0; i < n; i++)
{
fun(30);
}
int t2 = GetTickCount();
cout << t2 - t1 << endl;
return 0;
}
耗费时间(单位毫秒):47890
仔细画图分析就可知, 上述递归存在重复计算问题, 下面, 我们用备忘录法来试试:
#include <iostream>
#include <windows.h>
using namespace std;
int a[50] = {0};
int funWithMemo(int n)
{
if(1 == n || 2 == n)
{
a[n] = n;
return a[n];
}
if(0 != a[n])
{
return a[n];
}
int x = funWithMemo(n - 1);
int y = funWithMemo(n - 2);
a[n] = x + y;
return a[n];
}
int main()
{
int i = 0;
int n = 1000; // 计算1000次
int t1 = GetTickCount();
for(i = 0; i < n; i++)
{
funWithMemo(30);
}
int t2 = GetTickCount();
cout << t2 - t1 << endl;
return 0;
}
结果为:0 . 程序快得离谱啊, 我们看看, 哪里不公平了, 原来, a中已经存放值了, 所以后面每次都是return a[30];了, 所以, 循环测试的时候, 失去了一点公平性, 好吧, 我们来改改:
#include <iostream>
#include <windows.h>
using namespace std;
int a[50] = {0};
int funWithMemo(int n)
{
if(1 == n || 2 == n)
{
a[n] = n;
return a[n];
}
if(0 != a[n])
{
return a[n];
}
int x = funWithMemo(n - 1);
int y = funWithMemo(n - 2);
a[n] = x + y;
return a[n];
}
int main()
{
int i = 0;
int n = 1000; // 计算1000次
int t1 = GetTickCount();
for(i = 0; i < n; i++)
{
memset(a, 0, 50 * sizeof(int)); // 为了公平起见, 在循环测试中, 数组每次清零
funWithMemo(30);
}
int t2 = GetTickCount();
cout << t2 - t1 << endl;
return 0;
}
结果, 程序还是0, 可见, 确实快。
我们加大测试次数, 程序如下:
#include <iostream>
#include <windows.h>
using namespace std;
int a[50] = {0};
int funWithMemo(int n)
{
if(1 == n || 2 == n)
{
a[n] = n;
return a[n];
}
if(0 != a[n])
{
return a[n];
}
int x = funWithMemo(n - 1);
int y = funWithMemo(n - 2);
a[n] = x + y;
return a[n];
}
int main()
{
int i = 0;
int n = 10000; // 加大到1万次
int t1 = GetTickCount();
for(i = 0; i < n; i++)
{
memset(a, 0, 50 * sizeof(int)); // 为了公平起见, 在循环测试中, 数组每次清零
funWithMemo(30);
}
int t2 = GetTickCount();
cout << t2 - t1 << endl;
return 0;
}
结果为:16. 可见, 程序确实快。 funWithMemo之所以这么快, 是因为它采用了备忘录, 避免了重复的不必要的计算。(实际上, 16毫秒还包括了数组清零的时间)
采用递归, 对于编程人员来说, 非常方便, 但效率低下, 此时, 再引入备忘录, 有时效率会有所提升。 其实, 无论是递归带不带备忘录, 速度都不如自底向上的动态规划算法。
OK, 本文先闲聊到这里。