C++版演算法分析小技巧

Avery_Liu發表於2013-11-22

    在演算法分析過程中,我們往往有這樣的兩個需求:一、我們需要一些隨機生成的測試資料。二、我們需要測試演算法執行的時間。這就需要我們在程式設計上採用一些小技巧來滿足這兩個需求。

    一、隨機生成測試資料

        測試資料的含義非常廣泛,可以是簡單的整型數值,也可以是物件等,由於我所研究的演算法比較簡單,所以能滿足給出[a,b)內的隨機整型資料就可以了。

        隨機資料的生成涉及到兩個函式rand與srand這兩函式都包含在<iostream>標頭檔案中。

        我們先來看看srand函式,它的原型是

void srand(
   unsigned int seed 
);

        可以看出srand函式沒有返回值,引數是一個無符號整型數seed,seed即為隨機數的種子,srand這個函式就是用來設定隨機數種子的,何為隨機數種子?這就要我們理解隨機數的產生機制,程式設計中的隨機數與我們生活中的隨機過程中產生的隨機數不同,為偽隨機數,即程式產生的隨機數是由一些公式計算得來的,這些公式依據變數的值計算出隨機數,這裡被這些公式所依賴的變數就叫做隨機數種子,意為隨機數是根據這些種子數計算出來的一些整型序列,當種子相同的時候,這個序列相同。

        接下來我們來看看rand函式這個函式的原型是

int rand( void );

        根據msdn上remark,rand函式的返回值為0到RAND_MAX(32767),所以呼叫rand函式會返回一個0到32767之間的隨機值,問題來了,如果只需要[a,b)數怎麼辦呢?只需要rand()%(b-a)+a即可,想想為什麼。

        到現在為止,我們已經可以隨意地獲取任意區間內的整型隨機數了,但是如果直接呼叫rand沒有設定任何種子也是可以出結果的,難道沒有種子變數也可以計算麼?

        回過頭來看看srand函式在msdn上的remark:

            The srand function sets the starting point for generating a series of pseudorandom integers in the current thread. To reinitialize the generator to create the same sequence of results, call the srand function and use the same seed argument again. Any other value for seed sets the generator to a different starting point in the pseudorandom sequence. rand retrieves the pseudorandom numbers that are generated. Calling rand before any call to srand generates the same sequence as calling srand with seed passed as 1.

        這段話的大意是rand函式每次呼叫時都會去檢查隨機種子,如果沒有在所有rand函式呼叫前都沒有設定隨機種子,隨機種子預設值為1,相當於預設呼叫了一次srand(1)。

        所以此時生成[a,b)之間的整型隨機數程式碼如下:

/************************************************************
 * (C) Avery Liu HIT,2013
 * NOTE:
 *      This is the script of csdn blog demo codes, written
 * by Avery Liu. you can copy and run these codes freely, but 
 * no commercial activity is allowed. 
 *      If you want to use these code for other usage, please 
 * indicates the source:      http://blog.csdn.net/avery_liu
 ***********************************************************/

/*srand & rand demo*/
#include<iostream>
using namespace std;
#define MAX_TEST_NUM 100
int myRand(int lowBound, int upBound);
int main()
{
	int lowBound, upBound;
	cin >> lowBound >> upBound;
	for (int i = 0; i < MAX_TEST_NUM; i++)
	{
		int returnVal = myRand(lowBound, upBound);
		if (returnVal == -1)
		{
			cout << "Error Input" << endl;
			break;
		}
		cout << returnVal << endl;
	}
	system("Pause");
	return 0;
}

int myRand(int lowBound, int upBound)
{
	/*Check the parameters*/
	if (lowBound >= upBound) return -1;
	else if (upBound < 1) return -1;

	return rand() % (upBound - lowBound) + lowBound;
}

    二、測試演算法執行時間

        測試執行時間的方法有很多種,在這裡我就先舉一例使用函式GetTickCount來測試執行時間的。

        先來看看GetTickCount,它的原型是:

DWORD WINAPI GetTickCount(void);

        DWORD是Windows定義的unsigned long型的變數,GetTickCount返回的是系統開機執行到現在的時間,但最高只能表示到49.7天,如果超過這個數值的話又會歸零。為什麼呢?由於是32位無符號長整型,所以它表示的最大範圍是(1>>32)-1,換算後即為49.7103天。超過這個數值按照我們計算機組成原理中數的表示的知識,11...11(總共32個)就會變成00...00(32個0),也就是說歸回零點重新計數了,怎麼解決這個問題呢?使用函式GetTickCount64,就可以了它的返回值是ULONGLONG,也就是unsigned long long,為64為無符號整型,最大可表示範圍為:(1>>64)-1,也就是2.13504*(10^11)天,這是一個什麼概念?算一算大概是5.84942億年,夠大了吧(*^__^*) 。

        這裡可以看出這個GetTickCount函式在一般情況下是可以應用的,但是有些倒黴的人正好是在49.7的交界處呼叫了這個函式的話返回錯誤的結果,所以如果你剛開機,那就大無畏地用它吧,如果像我一樣經常蓋上筆記本就不管的,那還是用GetTickCount64吧。

        要注意這兩個函式都需要標頭檔案<Windows.h>,並且GetTickCount在Windows 2000以後的系統才能呼叫,GetTickCount64在Windows Server 2008和Windows Vista之後的系統才能呼叫。

        具體用法如下:

/************************************************************
 * (C) Avery Liu HIT,2013
 * NOTE:
 *      This is the script of csdn blog demo codes, written
 * by Avery Liu. you can copy and run these codes freely, but 
 * no commercial activity is allowed. 
 *      If you want to use these code for other usage, please 
 * indicates the source:      http://blog.csdn.net/avery_liu
 ***********************************************************/

/*srand & rand demo*/
#include<iostream>
#include<Windows.h>
using namespace std;
#define MAX_TEST_NUM 10000
int myRand(int lowBound, int upBound);
int main()
{
	int lowBound, upBound;
	cin >> lowBound >> upBound;
	ULONGLONG startTime = GetTickCount64();
	for (int i = 0; i < MAX_TEST_NUM; i++)
	{
		int returnVal = myRand(lowBound, upBound);
		if (returnVal == -1)
		{
			cout << "Error Input" << endl;
			break;
		}
		cout << returnVal << endl;
	}
	ULONGLONG endTime = GetTickCount64();
	//ULONGLONG test = 0 - 1;
	//cout << test/(24*3600000.0) << endl;
	cout << "time: " << (endTime - startTime) / 1000.0<< endl;
	system("Pause");
	return 0;
}

int myRand(int lowBound, int upBound)
{
	/*Check the parameters*/
	if (lowBound >= upBound) return -1;
	else if (upBound < 1) return -1;

	return rand() % (upBound - lowBound) + lowBound;
}
        只要在你想測試時間的程式碼段前定義一個ULONGLONG startTime並呼叫GetTickCount獲得時間,再在程式碼段後呼叫獲得endTime,endTime-startTime再進行轉換後就是我們所需要的演算法執行時間。

        中間被註釋掉的是我用來測試ULONGLONG的最大表示範圍的,大家可以不必理會。


        隨機數生成和測試執行時間這兩部分還有可以改進的地方,比如隨機數種子可以設定成當前系統時間,但要注意要保留下這個種子,不然如果出現問題了就難以找到讓程式出錯的輸入,獲取執行時間還有很多方法,比如CTime類啊,有時間再做比較。

        如有任何問題,歡迎大家指正交流。


相關文章