【經典排序演算法】八大排序對比總結
針對前面討論的八大經典排序演算法:氣泡排序、插入排序、選擇排序、堆排序、歸併排序、快速排序、希爾排序、桶排序。
關於各種什麼時間複雜度,空間複雜度的對比總結,網上一大堆,別人也總結的很好,這裡就不贅述了,這裡主要通過對大量資料進行排序測試,測試各排序演算法的執行時間,來比較各排序之間優劣(時間上)。
先貼出測試程式:最後會給出各資料量的情況下,各演算法的執行時間。
上面的 LENGTH 和 MAXNUM 巨集定義在另外一個頭檔案中(因為桶排序中需要用到 MAXNUM),LENGTH 為待排資料的量,MAXNUM 為待排資料數值的最大值。typedef void(*SortFun)(int Arr[], int length); //函式指標 double SortTest(SortFun fp) { int Arr[LENGTH]; for (int i = 0; i < LENGTH; ++i) Arr[i] = rand() % MAXNUM; clock_t beginTime = clock(); fp(Arr, LENGTH); double diffTime = double(clock() - beginTime) / CLOCKS_PER_SEC; return diffTime; }
為了方便呼叫函式指標,這裡在原排序程式的基礎上添加了一個通用的函式介面。為方便理解,這裡重新貼出各排序程式
1、選擇排序
思想:每次找一個最小值
特點:不穩定排序,in-place sort
最壞時間情況:O(N^2)
最好時間情況:O(N^2)
空間複雜度:O(1)
void SelectSort(int Array[], int length) { int i, j; int Minindex; for (i = 0; i < length - 1; ++i) //最後留下的元素自然是最大的 { Minindex = i; for (j = i + 1; j < length; ++j) { if (Array[j] < Array[Minindex]) Minindex = j; //更新最小值的索引 } swap(Array[i], Array[Minindex]); //將最小值與無序區的第一個元素交換 } }
2、桶排序
思想:類似於雜湊表的分離連結串列法,定義一個對映函式,將關鍵字劃入對應的桶中
特點:穩定排序
最壞時間情況:全部分到一個桶中O(N^2),一般情況為O(NlogN)
最好時間情況:每個桶中只有一個數據時最優O(N)
空間複雜度:典型地以空間換時間,和雜湊表類似,看要多少桶
typedef struct Node { int data; struct Node *next; }node; void AddtoBucket(node *&pNode, int value) { node *pHead = NULL; int tempValue; node *pNew = new node; pNew->data = value; pNew->next = NULL; if (NULL == pNode) { pNode = pNew; } else { pHead = pNode; while ((pNode->data <= value) && (pNode->next != NULL)) pNode = pNode->next; if (pNode->data <= value) { pNode->next = pNew; } else { //插入連結串列,這裡採用的是修改值的方法,即插入指定節點的前面,無需獲得該節點的前一節點 //只需修改該節點的值使其等於待插入節點值,然後該節點指向新建節點,新建節點的鍵值則設為前面節點的鍵值 tempValue = pNode->data; pNode->data = pNew->data; pNew->data = tempValue; //修改指標 pNew->next = pNode->next; pNode->next = pNew; } pNode = pHead; //修正頭節點指標 } } void BucketSort(int Arr[], int length, int MaxNum) { node **list = new node*[length]; //分配指標陣列空間 for (int i = 0; i < length; ++i) list[i] = NULL; int index; for (int i = 0; i < length; ++i) { index = (Arr[i] * length) / (MaxNum + 1); //對映函式,可自定義 AddtoBucket(list[index], Arr[i]); } //將桶中的資料放入原來的序列中 for (int i = 0, j = 0; i < length; ++i) { while (list[i] != NULL) { Arr[j++] = list[i]->data; list[i] = list[i]->next; } } //銷燬分配的空間,code 略 } //統一介面 void BucketSortTest(int Arr[], int length) { BucketSort(Arr, length, MAXNUM); }
3、希爾排序
思想:分割成子序列,然後用直接插入排序
特點:不穩定排序演算法
平均時間複雜度:O()
空間複雜度:O(1)
void ShellSort(int Array[], int length)
{
int i, j, gap;
int temp;
for (gap = length / 2; gap > 0; gap /= 2)
for (i = 0; i < gap; ++i)
{
//下面為直接插入排序,排序的是數組裡間隔gap的元素
for (j = i + gap; j < length; j += gap)
{
int k = j - gap;
temp = Array[j];
while (k >= 0 && Array[k] > temp)
{
Array[k + gap] = Array[k];
k -= gap;
}
Array[k + gap] = temp;
}
}
}
4、插入排序
思想:將無序區的元素比較插入到有序區適當位置,是移動不是交換,所以是穩定排序演算法
特點:穩定排序演算法,in-place sort
時間複雜度:最壞O(N^2),最優O(N)
void InsertSort(int Array[], int length)
{
int i, j;
int temp;
for (i = 1; i < length; ++i)
{
j = i - 1;
temp = Array[i]; //待插入資料,即無序區的第一個元素
while ((j >= 0) && (temp < Array[j])) //跳出迴圈的兩個條件
{
Array[j + 1] = Array[j]; //資料後移
--j;
}
Array[j + 1] = temp;
}
}
5、歸併排序
思想:分治法
特點:穩定排序演算法
時間複雜度:O(NlogN)
空間複雜度:O(N+logN)
void Merge(int unsorted[], int first, int mid, int last, int sorted[])
{
int i = first, j = mid;
int k = 0;
while (i < mid && j < last)
{
if (unsorted[i] < unsorted[j])
sorted[k++] = unsorted[i++];
else
sorted[k++] = unsorted[j++];
}
while (i < mid)
sorted[k++] = unsorted[i++];
while (j < last)
sorted[k++] = unsorted[j++];
/*2-路合併的結果,與後面一路是無序的(這兩路不在同一序列中),
需要將前面合併的結果匯入輸入序列中,再次進行2-路合併,first表徵相對位置*/
for (int v = 0; v < k; ++v)
unsorted[v + first] = sorted[v];
}
/*分割及歸併排序*/
void MergeSort(int unsorted[], int first, int last, int sorted[])
{
if (first + 1 < last)
{
int mid = first + ((last - first) >> 1); //mid=(left+right)>>1;當left和right比較大時,有溢位的危險
MergeSort(unsorted, first, mid, sorted);
MergeSort(unsorted, mid, last, sorted);
Merge(unsorted, first, mid, last, sorted);
}
}
//統一介面
void MergeSortTest(int Arr[], int length)
{
int *sorted = new int[length];
MergeSort(Arr, 0, length, sorted);
}
6、堆排序
思想:最大堆(最小堆)
特點:不穩定排序演算法
時間複雜度:O(NlogN)
空間複雜度:O(1)
#define PARENT(x) ((x-1) >> 1)
#define LEFT(x) ((x << 1) + 1)
#define RIGHT(x) ((x << 1) + 2)
//調整最大堆
void MaxHeapify(int Array[], int Index, int HeapSize)
{
int L = LEFT(Index); //Index為父節點索引
int R = RIGHT(Index);
int Largest;
//選擇三(兩個)個元素(“血緣關係”)的最大值
if (L <= HeapSize && Array[L] > Array[Index])
Largest = L;
else
Largest = Index;
if (R <= HeapSize && Array[R] > Array[Largest])
Largest = R;
if (Largest != Index)
{
swap(Array[Largest], Array[Index]); //最大值與父節點位置交換
MaxHeapify(Array, Largest, HeapSize);
/*這裡的MaxHeapify()遞迴是用於最大堆的調整,從上至下調整,且只需調整一棵子樹
構建最大堆是從陣列尾端往前,並不需要遞迴,實際上構建時也不會出現遞迴呼叫*/
}
}
//構建最大堆
void BuildHeapify(int Array[], int HeapSize)
{
for (int i = PARENT(HeapSize); i >= 0; --i)
MaxHeapify(Array, i, HeapSize); //必須單步向前
}
void HeapSort(int Array[], int length)
{
int HeapSize = length - 1;
BuildHeapify(Array, HeapSize);
for (int i = HeapSize; i >= 0; --i)
{
swap(Array[0], Array[i]);
--HeapSize;
MaxHeapify(Array, 0, HeapSize);
//刪除根節點後,,有一棵子樹滿足最大堆,有一棵不滿足,只需調整一棵子樹
}
}
7、快速排序
思想:分治,化整為零,與基準比較
特點:不穩定排序演算法
時間複雜度:O(NlogN),如果資料本來是排序好的,最壞O(N^2)
空間複雜度:O(logN)
int Partition(int Array[], int left, int right)
{
int L = left, R = right;
int pivot = Array[L];
while (L < R)
{
while ((L < R) && (Array[R] > pivot))
--R;
if (L < R)
{
Array[L] = Array[R]; //補數的時候,要知道空位在哪
++L;
}
while ((L < R) && (Array[L] < pivot))
++L;
if (L < R)
{
Array[R] = Array[L];
--R;
}
}
Array[L] = pivot; //補上最後的空位
return L;
}
void QuickSort(int Array[], int left, int right)
{
if (left < right)
{
int pos = Partition(Array, left, right);
QuickSort(Array, left, pos - 1); //注意邊界
QuickSort(Array, pos + 1, right);
}
}
//統一介面
void QuickSortTest(int Arr[], int length)
{
QuickSort(Arr, 0, length - 1);
}
8、氣泡排序
思想:冒泡
特點:穩定排序演算法
時間複雜度:O(N^2)
void BubbleSort(int Array[], int length)
{
for (int i = 0; i < length - 1; ++i)
for (int j = 1; j < length - i; ++j) //單次之後最大資料在最後面
{
if (Array[j - 1] > Array[j])
swap(Array[j - 1], Array[j]);
}
}
主程式:
避免長久的等待,將氣泡排序置最後。
int main()
{
cout << "BucketSort: " << SortTest(BucketSortTest) << endl;
cout << "HeapSort : " << SortTest(HeapSort) << endl;
cout << "InsertSort: " << SortTest(InsertSort) << endl;
cout << "MergeSort : " << SortTest(MergeSortTest) << endl;
cout << "QuickSort : " << SortTest(QuickSortTest) << endl;
cout << "SelectSort: " << SortTest(SelectSort) << endl;
cout << "ShellSort : " << SortTest(ShellSort) << endl;
cout << "BubbleSort: " << SortTest(BubbleSort) << endl;
return 0;
}
結果:
#define LENGTH 20000 //資料量
#define MAXNUM 10000 //資料的最大值
#define LENGTH 10000 //資料量
#define MAXNUM 10000 //資料的最大值
#define LENGTH 5000 //資料量
#define MAXNUM 10000 //資料的最大值
#define LENGTH 50000 //資料量
#define MAXNUM 100000 //資料的最大值
這裡的氣泡排序需要等很久
上面執行時間的計算,受很多因素的影響,這裡只是對比各大排序的排序時間。毫無疑問快速排序是最快的。