內部排序演算法比較(超詳解)
一、題目描述
通過隨機資料比較各排序演算法的關鍵字比較次數和關鍵字移動次數,以 及執行時間,取得直觀感受。
二、設計要求
一、需求分析
實現各排序演算法,分別進行以下各組比較,並進行總結。
一、各演算法在不同規模下的比較。
1)比較範圍:直接插入排序、冒泡法排序、簡單選擇排序、快速排序1(自己實現)、快速排序2(呼叫STL)、歸併排序。
2)比較指標:a)關鍵字操作次數(比較次數和移動次數之和),b)排序時間。每個指標採用多次重複取平均數記錄,重複次數不小於100。注:1次關鍵字對換按3次移動計算。
3)資料規模:分別為50,100,500,1000,5000,10000;
4)資料型別:隨機資料
二、演算法在不同資料型別下的比較
1)比較範圍:直接插入排序、冒泡法排序、簡單選擇排序、快速排序1(自己實現)、快速排序2(呼叫STL)、歸併排序。
2)比較指標:a)關鍵字操作次數(比較次數和移動次數之和),b)排序時間。每個指標採用多次重複取平均數記錄,重複次數不小於100。注:1次關鍵字對換按3次移動計算。
3)資料規模:10000;
4)資料型別:隨機資料、正序資料、逆序資料;
三、程式碼要求
1、必須要有異常處理,比如刪除空連結串列時需要丟擲異常;
2、保持良好的程式設計的風格:
程式碼段與段之間要有空行和縮近
識別符號名稱應該與其代表的意義一致
函式名之前應該添加註釋說明該函式的功能 、關鍵程式碼應說明其功能
3、遞迴程式注意呼叫的過程,防止棧溢位
四、演算法分析
#include<iostream>
#include<time.h>
#include <algorithm>
#include<stdlib.h>
#include <windows.h>
#include <string>
using namespace std;
//關鍵次數初始化
static long long move1=0;
static long long move2=0;
static long long move3=0;
static long long move4=0;
static long long move5=0;
static long long move6=0;
//輸出結果
void print(int a[], int n){
for(int j= 0; j<n; j++){
cout<<a[j] <<" ";
}
cout<<endl<<endl;
}
/***********歸併排序*******************/
void Merge(int r[],int rf[], int i, int m, int n) //歸併操作
{
int j,k;
for(j=m+1,k=i; i<=m && j <=n ; ++k){
move1+=1;
if(r[i] < r[j]) {rf[k] = r[i++]; move1+=3; move1+=1;}
else {rf[k] = r[j++]; move1+=3; move1+=1;}
}
while(i <= m) {rf[k++] = r[i++]; move1+=3; move1+=2;}
move1+=1;
while(j <= n) {rf[k++] = r[j++]; move1+=3; move1+=2;}
move1+=1;
}
void MSort(int r[], int rf[], int s, int t) //將r[]歸併排序為rf[]
{
move1+=1;
//int *rf2=new int[t];
int rf2[10000+1];
if(s==t) {rf[s] = r[s]; move1+=3;}
else
{
move1+=1;
int m=(s+t)/2; /*平分*p 表*/
MSort(r, rf2, s, m); /*遞迴地將p[s…m]歸併為有序的p2[s…m]*/
MSort(r, rf2, m+1, t); /*遞迴地將p[m+1…t]歸併為有序的p2[m+1…t]*/
Merge(rf2, rf, s, m,t); /*將p2[s…m]和p2[m+1…t]歸併到p1[s…t]*/
}
}
void MergeSort(int r[], int rf[], int n)
{ /*對順序表*p 作歸併排序*/
MSort(r, rf, 0, n-1);
}
/*************歸併排序結束*******************/
/***********快速排序1(遞迴)*******************/
/*
void swap(int* a,int*b)
{
int temp;
temp=*a;
*a=*b;
*b=temp;
move2+=3;
}
int Partition(int a[],int low,int high)
{
int pivotkey= a[low];
while(low<high)
{
while (low<high&&a[high]>=pivotkey) { --high;move2+=1;}
swap(&a[low],&a[high]);
while (low<high&&a[low<=pivotkey]){ ++low;move2+=1;}
swap(&a[low],&a[high]);
}
move2+=1
return low;
}
void quickSort(int a[], int low, int high)
{
int pivotloc;
if (low<high)
{
pivotloc = Partition(a,low,high); //將表一分為二
quickSort(a,low,pivotloc-1); //遞迴對低子表遞迴排序
quickSort(a,pivotloc+1,high); //遞迴對高子表遞迴排序
}
move2+=1;
}*/
/*************快速排序1(遞迴)結束*******************/
/***********快速排序1(非遞迴)*******************/
void quicksort(int a[],int n)
{
struct node
{
int low,high;
}st[10000];
int i,j,low,high,temp,top=0;
st[top].low=0;
st[top].high=n-1;
while(top>-1)
{ low=st[top].low;
high=st[top].high;
top--;
i=low;
j=high;
if(low<high)
{
move2+=1;
temp=a[low];
while(i!=j)
{
move2+=1;
while(i<j&&a[j]>temp){ j--; move2+=1;}
if(i<j){a[i]=a[j];i++;move2+=3;}
while(i<j&&a[i]<temp){ i++; move2+=1;}
if(i<j){a[j]=a[i];j--;move2+=3;}
}
a[i]=temp;
top++;
st[top].low=low;
st[top].high=i-1;
top++;
st[top].low=i+1;
st[top].high=high;
}
move2+=1;
}
move2+=1;
}
/*************快速排序1(非遞迴)結束*******************/
/*************氣泡排序開始*******************/
void bubbleSort(int a[], int n)
{
int temp;
for(int i=0;i<n-1;i++)
for(int j=0;j<n-i-1;j++)
{
if(a[j]>a[j+1])
{
temp=a[j];
a[j]=a[j+1];
a[j+1]=temp;
move3+=3;
}
move3+=1;
}
}
/*************氣泡排序結束*******************/
/*************簡單選擇排序開始*******************/
void selectSort(int a[], int n)
{
int key,temp;
for(int i=1;i<n;i++)
{
key=i-1;
for(int j=i;j<n;j++)
{
if(a[j]<a[key])
{
key=j;
move4+=3;
}
move4+=1;
}
if(key!=i-1)
{
temp=a[key];
a[key]=a[i-1];
a[i-1]=temp;
move4+=3;
}
move4+=1;
// print(a,n,i); //列印每趟排序的結果
}
}
/*************簡單選擇排序結束*******************/
/*************直接插入排序結束*******************/
void InsertSort(int a[], int n)
{
for(int i= 1; i<n; i++){
if(a[i] < a[i-1]){ //若第i個元素大於i-1元素,直接插入。小於的話,移動有序表後插入
move5+=1;
int j= i-2;
int x = a[i]; //複製為哨兵,即儲存待排序元素
move5+=3;
a[i] = a[i-1]; //先後移一個元素
move5+=1;
while(x < a[j]&&j>=0){ //查詢在有序表的插入位置
a[j+1] = a[j];
j--; //元素後移
move5+=1;
}
move5+=1;
a[j+1] = x; //插入到正確位置
}
move5+=1;
// print(a,n,i); //列印每趟排序的結果
}
}
/*************直接插入排序結束*******************/
/*************STL快速排序開始************************/
int comp(const void*a,const void*b)
{
move6+=3;
return *(int*)a-*(int*)b;
}
/*************STL快速排序結束************************/
//彙總所有的排序演算法
void sort123(int z,int type)
{
/********初始化變數*********************/
//clock_t *start=new clock_t[100];
//clock_t *end=new clock_t[100];
long long *start=new long long[100];
long long *end=new long long[100];
double *dfMinus=new double[100];
double *dfTim=new double[100];
LARGE_INTEGER litmp;
double dfFreq;
QueryPerformanceFrequency(& litmp); //獲取CPU時鐘頻率
dfFreq = (double)litmp.QuadPart;
double times1=0;
double times2=0;
double times3=0;
double times4=0;
double times5=0;
double times6=0;
srand(time(NULL));
//給原陣列賦值
for(int i=0;i<=99;i++)
{
//初始化各個陣列
int *a=new int[z];
int *c=new int[z];
int *d=new int[z];
int *e=new int[z];
int *f=new int[z];
int *g=new int[z];
if(type==0)
{
for(int j=0;j<z;j++) //下標法
{
a[j]=rand();
c[j]=a[j];
d[j]=a[j];
e[j]=a[j];
f[j]=a[j];
g[j]=a[j];
}
}
else if(type==1)
{
for( int j=0;j<z;j++)
{
a[j]=j+i;
c[j]=a[j];
d[j]=a[j];
e[j]=a[j];
f[j]=a[j];
g[j]=a[j];
}
}
else
{
for(int j=0;j<z;j++)
{
a[j]=z-j+i;
c[j]=a[j];
d[j]=a[j];
e[j]=a[j];
f[j]=a[j];
g[j]=a[j];
}
}
/***歸併排序開始*******/
QueryPerformanceCounter(&litmp); //獲取開始計數值
start[i] = litmp.QuadPart;
int *b=new int[z]; //b陣列用於歸併排序
MergeSort(c,b, z);
QueryPerformanceCounter(&litmp); //獲取結束計數值
end[i] = litmp.QuadPart;
dfMinus[i] = (double)(end[i]- start[i]);
dfTim[i] = dfMinus[i]/dfFreq; //計算持續時間,單位為秒誤差不超過1微妙算持續時間,單位為秒誤差不超過1微妙
times1+=dfTim[i];
delete []b;
delete []c;
/*****歸併排序結束********/
/***快速排序1開始*******/
QueryPerformanceCounter(&litmp); //獲取開始計數值
start[i] = litmp.QuadPart;
quicksort(d,z); //此處用的是非遞迴快排,非遞迴需要堆疊不是很大
//quickSort(d,0,z-1); //此處是遞迴快排演算法,演算法在上面已經註釋掉,因為快排遞迴需要堆疊太大,一般電腦需要調整堆疊,我改動的為5032768位元組(隨便,夠大就好)
QueryPerformanceCounter(&litmp); //獲取結束計數值
end[i] = litmp.QuadPart;
dfMinus[i] = (double)(end[i]- start[i]);
dfTim[i] = dfMinus[i]/dfFreq; //計算持續時間,單位為秒誤差不超過1微妙算持續時間,單位為秒誤差不超過1微妙
times2+=dfTim[i];
//if(i<=0)
// print(d,z);
delete []d;
/*****快速排序1結束********/
/***冒泡開始*******/
QueryPerformanceCounter(&litmp); //獲取開始計數值
start[i] = litmp.QuadPart;
bubbleSort(e,z); //氣泡排序
QueryPerformanceCounter(&litmp); //獲取結束計數值
end[i] = litmp.QuadPart;
dfMinus[i] = (double)(end[i]- start[i]);
dfTim[i] = dfMinus[i]/dfFreq; //計算持續時間,單位為秒誤差不超過1微妙算持續時間,單位為秒誤差不超過1微妙
times3+=dfTim[i];
delete []e;
/*****冒泡結束********/
/***選擇法開始*******/
QueryPerformanceCounter(&litmp); //獲取開始計數值
start[i] = litmp.QuadPart;
selectSort(f,z); //簡單選擇排序
QueryPerformanceCounter(&litmp); //獲取結束計數值
end[i] = litmp.QuadPart;
dfMinus[i] = (double)(end[i]- start[i]);
dfTim[i] = dfMinus[i]/dfFreq; //計算持續時間,單位為秒誤差不超過1微妙算持續時間,單位為秒誤差不超過1微妙
times4+=dfTim[i];
delete []f;
/*****選擇法結束********/
/***插入法開始*******/
QueryPerformanceCounter(&litmp); //獲取開始計數值
start[i] = litmp.QuadPart;
InsertSort(g,z); //直接插入排序
QueryPerformanceCounter(&litmp); //獲取結束計數值
end[i] = litmp.QuadPart;
dfMinus[i] = (double)(end[i]- start[i]);
dfTim[i] = dfMinus[i]/dfFreq; //計算持續時間,單位為秒誤差不超過1微妙算持續時間,單位為秒誤差不超過1微妙
times5+=dfTim[i];
delete []g;
/*****插入法結束********/
/***STL快排法開始*******/
QueryPerformanceCounter(&litmp); //獲取開始計數值
start[i] = litmp.QuadPart;
//sort(a, a+z); //STL一種改進型分段演算法
qsort(a,z,sizeof(int),comp);
QueryPerformanceCounter(&litmp); //獲取結束計數值
end[i] = litmp.QuadPart;
dfMinus[i] = (double)(end[i]- start[i]);
dfTim[i] = dfMinus[i]/dfFreq; //計算持續時間,單位為秒誤差不超過1微妙算持續時間,單位為秒誤差不超過1微妙
times6+=dfTim[i];
/*****STL快排法結束********/
delete []a;
}
delete []start;
delete []end;
delete []dfMinus;
delete []dfTim;
/***********列印用時**********************/
string s;
if(type==0) { s="隨機資料";cout<<s<<endl;}
else if(type==1) { s="正序資料";cout<<s<<endl;}
else { s="逆序資料";cout<<s<<endl;}
cout<<"歸併排序: "<<z<<"個"<<s<<"重複實驗100次;"<<" 關鍵字操作次數平均為"<<move1/100<<"次"<<endl<<"平均耗時"<<times1/100*1000<<" 毫秒"<<endl<<endl;
cout<<"快速排序(非遞迴): "<<z<<"個"<<s<<"重複實驗100次;"<<" 關鍵字操作次數平均為"<<move2/100<<"次"<<endl<<"平均耗時"<<times2/100*1000<<" 毫秒"<<endl<<endl;
cout<<"氣泡排序: "<<z<<"個"<<s<<"重複實驗100次;"<<" 關鍵字操作次數平均為"<<move3/100<<"次"<<endl<<"平均耗時"<<times3/100*1000<<" 毫秒"<<endl<<endl;
cout<<"簡單選擇排序: "<<z<<"個"<<s<<"重複實驗100次;"<<" 關鍵字操作次數平均為"<<move4/100<<"次"<<endl<<"平均耗時"<<times4/100*1000<<" 毫秒"<<endl<<endl;
cout<<"直接插入排序: "<<z<<"個"<<s<<"重複實驗100次;"<<" 關鍵字操作次數平均為"<<move5/100<<"次"<<endl<<"平均耗時"<<times5/100*1000<<" 毫秒"<<endl<<endl;
cout<<"STL快速排序: "<<z<<"個"<<s<<"重複實驗100次;"<<" 關鍵字操作次數平均為"<<move6/100<<"次"<<endl<<"平均耗時"<<times6/100*1000<<" 毫秒"<<endl<<endl;
}
//主函式
int main()
{
sort123(50,0);
sort123(100,0);
sort123(500,0);
sort123(1000,0);
sort123(5000,0);
sort123(10000,0);
sort123(10000,1);
sort123(10000,2);
system("pause");
return 0;
}
六、結果截圖
七、結果分析:
1、隨機資料比較:資料規模分別為50,100,500,1000,5000,10000,我們經過驗證可以得出初步結論:
在資料基本無序的狀態下且資料較多:排序效率比較如下:
快速排序法>STL快速排序法>歸併排序法>直接插入排序法>簡單選擇法>
氣泡排序法
在資料基本無序的狀態下且資料較少:排序效率比較如下:
快速排序法>STL快速排序法>直接插入排序法簡單選擇法>氣泡排序法>
歸併排序法
在資料更少的情況下,直接插入法也有奇效。
2、正序資料比較:資料規模為10000,我們經過驗證可以得出初步結論:
在資料基本有序且為正序的狀態下:排序效率如下:
直接插入排序>STL快速排序法>歸併排序>快速排序>簡單選擇排序>
氣泡排序法
3、逆序資料比較:資料規模為10000,我們經過驗證可以得出初步結論:
在資料基本有序且為正序的狀態下:排序效率如下:
STL快速排序法>歸併排序>快速排序>直接插入排序>簡單選擇排序>
氣泡排序法
八、總結
1、涉及遞迴的有歸併排序以及快速排序,這兩個都是我編寫程式的時候感覺最艱難的。
2、歸併排序使用的陣列除了中間要合併用的那個rf2用的是靜態陣列,其他都是動態陣列new建立的,堆建立陣列本來是挺好的,可是這裡卻因為資料規模較大的時候,遞迴太深rf2又無法delete[],只能用陣列,建立了一個rf2[10001].
3、快速排序我寫了兩種方法,一種是遞迴的,一種是非遞迴的,遞迴真的理解是最簡單的,可是對堆疊真的要求太高了,遞迴本來是崩潰的,不過忽然找到了方法:因為快排遞迴需要堆疊太大,一般電腦需要調整堆疊,我改動的為5032768位元組(隨便,夠大就好)
4、時間精度的問題也好解決,用的是QueryPerformance,這個精度非常好,學到的好東西。