1. 程式人生 > >fibonacci遞迴演算法的“備忘錄/Memo”優化法

fibonacci遞迴演算法的“備忘錄/Memo”優化法

        我們先看一個簡單的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, 本文先閒聊到這裡。