完成了oj題的練習,現在就進入了下一環節——算法測試分析
算法概述
代碼段時間復雜度分析
代碼段執行時間測定
關於時間的測定,我們就需要用到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