計算機程序設計實訓記錄(4)


完成了oj題的練習,現在就進入了下一環節——算法測試分析


算法概述

直接拿PPT的圖了


代碼段時間復雜度分析

代碼段執行時間測定

關於時間的測定,我們就需要用到C的時間函數clock(),該函數返回自程序啟動起,處理器時鍾所使用的時間。如果失敗,則返回 -1 值。
下面是課件給出來的一個關於時間函數的代碼:

double gettime(int restart)			// 參數非零表示重新設置計時起點
{
  const double c=1.0/CLOCKS_PER_SEC;	// 毫秒轉換至秒
  static clock_t t = 0; 				// 靜態局部變量,存儲計時起點
  if(restart||t==0) t = clock();		// 根據實參決定是否重新確定計時起點
  return c*(clock()-t);				// 從上一計時點到現在所經歷的時間差
}

而在實際操作中,也給出了例子

  double t;
  gettime(1);
  // 調用某函數
  t = gettime(0);	   // 或 t = gettime();

等等,真的可以直接t = gettime();嗎?這還得讓我后續測試一下才行。

函數中執行某運算的次數統計

這里我們可以拿冒泡排序來舉例:

void Bubble(double *a, int length, unsigned long long *nComp,
							unsigned long long *nAssign)
{						// 注意第3-4個參數的數據類型,指針型傳遞
	*nComp = *nAssign = 0;		// 非遞歸函數,可在此置0
	for(int i=1; i<length; i++) 	// 不統計輔助操作
	{
		for(int j=0; j<length-i; j++)
		{
			(*nComp)++; 		// 僅統計元素間的操作(元素間比較)
			if(a[j] > a[j+1])
 			{
				(*nAssign) += 3;
				swap(a[j], a[j+1]);		// 元素間3次賦值
			}
		}
	}
}

為什么要傳指針呢?當然是為了返回多個數據。


課程任務

這里就直接照抄課程的文件了

對於所涉及的排序算法,本課程的重點在於實驗分析或實驗驗證,即:不要求進行嚴密地理論分析,需要時直接引用相關的理論結果。
首先研究基本的冒泡排序算法、選擇排序算法和快速排序算法(如參見表1提到的Sorts-C.zip文件)測試三種排序算法的時間。
其次,利用Score結構體數組討論排序算法的穩定性。所謂排序的穩定性是指:假定在待排序的記錄序列中,存在多個具有相同的關鍵字的記錄,若經過排序,這些記錄的相對次序保持不變,即在原序列中,若x[i]與x[j]相等,且x[i]在x[j]之前,而在排序后的序列中,x[i]仍在x[j]之前,則稱這種排序算法是穩定的;否則稱為不穩定的[3]。對於不穩定的排序算法只要舉出一個實例即可。而對於穩定的排序算法,則不展開深入的理論研究(給出結論即可)。
最后,對double型數組的3個排序函數進行修改,在每個函數中增加2個無符號擴展的長整型指針形參(unsigned long long *),分別用於間接“返回”相關函數執行數組元素間的比較次數、數組元素間賦值次數(不統計輔助操作的次數,即不統計下標比較、下標增減等操作次數)。根據統計結果,對不同排序算法進行對比分析。

看...看起來好難!但是課程本身已經給了我們成品的測試程序,第一個和第二個任務都可以直接解決,好耶!

運行程序

既然已經有了成品,我們就運行一下看看吧。
效果大概是這樣

整型數據(正態分布)
冒泡排序 選擇排序 快速排序
1024 0.002000 0.001000 0.000000 冒泡排序: 正確 選擇排序: 正確 快速排序: 正確
2048 0.000000 0.013000 0.000000 冒泡排序: 正確 選擇排序: 正確 快速排序: 正確
4096 0.016000 0.031000 0.000000 冒泡排序: 正確 選擇排序: 正確 快速排序: 正確
8192 0.140000 0.063000 0.000000 冒泡排序: 正確 選擇排序: 正確 快速排序: 正確
16384 0.600000 0.275000 0.016000 冒泡排序: 正確 選擇排序: 正確 快速排序: 正確
32768 2.632000 1.075000 0.047000 冒泡排序: 正確 選擇排序: 正確 快速排序: 正確
65536 11.197000 4.319000 0.180000 冒泡排序: 正確 選擇排序: 正確 快速排序: 正確

次數統計

既然前面已經有例子了,我們只要縫合上去就好了,好耶!
事實證明完全沒有那么簡單

冒泡排序

void D_Bubble(double *a, int size,unsigned long long *nComp,unsigned long long *nAssign)// 冒泡排序
{
	*nComp = *nAssign = 0;		// 非遞歸函數,可在此置0
	double temp;								// 定義一個局部變量,數據類型與形式數據類型相同
	int i, j;
	for(i=1; i<size; i++)							// 共進行 size-1 輪比較和交換
	{
		for(j=0; j<size-i; j++)
		{
			(*nComp)++; 		// 僅統計元素間的操作(元素間比較)
			if(a[j] > a[j+1])					// 相鄰元素之間比較,必要時
			{
				temp = a[j];					// 交換 a[j] 與 a[j+1]
				a[j] = a[j+1];
				a[j+1] = temp;
				(*nAssign) += 3;				// 元素間3次賦值
			}
		}
	}
}

比較排序

void D_Select(double *a, int size,unsigned long long *nComp,unsigned long long *nAssign)// 選擇排序
{
	*nComp = *nAssign = 0;		// 非遞歸函數,可在此置0
	double temp;
	int i, j, k=0;
	for(i=1; i<size; i++)						// 循環size-1次
	{
		for(j=i; j<size; j++)
		{
			(*nComp)++; 		                  // 僅統計元素間的操作(元素間比較)
			if(a[j] < a[k])
				k = j;	
		}							// 找出當前范圍內"最小"元素的下標
		if(k!=i-1)						// 若"最小"元素不是a[i-1],則交換之
		{
			temp = a[k];
			a[k] = a[i-1];
			a[i-1] = temp;
			(*nAssign) += 3;				// 元素間3次賦值
		}
		k = i;
	}
}

快速排序

void D_Qsort(double *a, int size,unsigned long long *nComp,unsigned long long *nAssign)	// 快速排序
{
	double pivot, temp;
	int left=0, right=size-1;						// 下標(整數)

	if(size<=1) return;

	pivot = a[right];								// 選擇最后一個值為分界值
	do
	{
		while(left<right && a[left]<=pivot)
		{ 
			left++;
			(*nComp)++;                                          //一次比較
		}	// 此處 "<=" 是讓與分界值相等的元素暫時留在原地
		while(left<right && a[right]>=pivot)
		{
			right--;
			(*nComp)++;                                          //一次比較
		}// 此處 ">=" 是讓與分界值相等的元素暫時留在原地
		if(left < right)
		{
			temp=a[left]; a[left]=a[right]; a[right]=temp;
			(*nAssign) += 3;					// 元素間3次賦值
		}
	}while(left < right);
	a[size-1] = a[left]; a[left] = pivot;
	(*nAssign) += 2;			// 找到分界點 left並賦值兩次
	D_Qsort(a, left,nComp,nAssign);							// 遞歸調用(左側部分)
	D_Qsort(a+left+1, size-left-1,nComp,nAssign);					// 遞歸調用(右側部分)
}

啊咧,快速排序沒有元素間的比較嗎?真神奇啊。
這么看的話可能快排反而比較次數最多。然鵝就結果而言是比較少的
接下來就是寫測試程序。

測試程序

void D_Test()
{
	double *data0=NULL, *data=NULL;								// 指針初始化為NULL非常重要!
	char InitConf[][20] = {"完全逆序", "完全順序", "均勻分布", "正態分布"};	// C-字符串數組(數據分布方式)
	char algo[][20] = {"冒泡排序", "選擇排序", "快速排序"};		// C-字符串數組(排序算法名稱)
	void (*f[])(double*, int,unsigned long long*,unsigned long long*) = {D_Bubble, D_Select, D_Qsort};	// 函數指針數組(元素為一系列函數的入口地址)
	int i, j, n, m = sizeof(f)/sizeof(*f);				// m為函數指針數組f的元素個數(此處有3個函數指針,分別指向3個排序函數的入口地址)
	int flag[20];							// 這里認為:常量20足夠大於變量 m。記錄不同算法執行的正確性
	//double t[20];
	unsigned long long nc[20],na[20];				// 同上。記錄不同算法的操作次數
	unsigned long long nComp=0,nAssign=0;
	for(j=2; j>=-1; j--)										// 數據分布類型
	{
		printf("\n雙精度浮點型數據(%s)\n", InitConf[j+1]);
		for(i=0; i<m; i++)
			printf("\t%s\t", algo[i]);
		printf("\n");
		for(n=1024; n<=65536; n*=2)
		{
			D_GetMemory(&data, &data0, n);						// 申請分配堆空間
			D_InitData(data0, n, j);							// 設置原始數據
			printf("%d", n);
			for(i=0; i<m; i++)
			{
				if(n==65536 && j<=0 && i==m-1)					// j<=0(完全順序、完全逆序),i==m-1(快速排序,遞歸算法)
					break;
				D_ReSet(data, data0, n);							// 恢復原始數據
				//gettime(1);										// 設置計時起點
				nComp=0;
				nAssign=0;									//每次排序前初始化計數變量
				f[i](data, n,&nComp,&nAssign);									// 第 i 種排序算法
				//t[i] = gettime(0);
				nc[i]=nComp;
				na[i]=nAssign;								// 返回比較次數和賦值次數
				flag[i] = D_Check(data, n);						// 檢驗排序的正確性
			}
			for(i=0; i<m; i++)
			{
				if(n==65536 && j<=0 && i==m-1)					// j<=0(完全順序、完全逆序),i==m-1(快速排序,遞歸算法)
					printf("\t");
				else
				{
					printf("\t%llu", nc[i]);
					printf("\t%llu", na[i]);
				}
			}
			for(i=0; i<m; i++)
			{
				if(n==65536 && j<=0 && i==m-1)					// j<=0(完全順序、完全逆序),i==m-1(快速排序,遞歸算法)
					printf("\t快速排序:由於遞歸層次太深,可能導致棧溢出,故跳過。");
				else
					printf("\t%s: %s", algo[i], (flag[i]?"正確":"錯誤"));
			}
			printf("\n");
			D_FreeMemory(&data, &data0);						// 釋放堆空間資源,並使指針為空
		}
	}
}

新的問題又出現了
undefined reference to '__chkstk_ms'
查了一下,應該是mingw之間打架的問題,所以我換了一台電腦去build。

運行結果

大概是這樣

雙精度浮點型數據(正態分布)
冒泡排序 選擇排序 快速排序
1024 523776 812901 523776 3051 10965 6671 冒泡排序: 正確 選擇排序: 正確 快速排序: 正確
2048 2096128 3118740 2096128 6126 26537 14526 冒泡排序: 正確 選擇排序: 正確 快速排序: 正確
4096 8386560 12431133 8386560 12243 67638 31535 冒泡排序: 正確 選擇排序: 正確 快速排序: 正確
8192 33550336 50310393 33550336 24549 125374 69960 冒泡排序: 正確 選擇排序: 正確 快速排序: 正確
16384 134209536 200332119 134209536 49131 270704 151097 冒泡排序: 正確 選擇排序: 正確 快速排序: 正確
32768 536854528 804468672 536854528 98268 578908 326634 冒泡排序: 正確 選擇排序: 正確 快速排序: 正確
65536 2147450880 3215171196 2147450880 196563 1321824 692429 冒泡排序: 正確 選擇排序: 正確 快速排序: 正確

終於做完了,好耶O(∩_∩)O


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM