氣泡排序,選擇排序,插入排序,希爾排序,堆排序,歸併排序,快速排序實現
阿新 • • 發佈:2019-01-12
前言:排序的定義
排序是計算機內經常進行的一種操作,其目的是將一組“無序”的記錄序列調整為“有序”的記錄序列。分內部排序和外部排序,若整個排序過程不需要訪問外存便能完成,則稱此類排序問題為內部排序。反之,若參加排序的記錄數量很大,整個序列的排序過程不可能在記憶體中完成,則稱此類排序問題為外部排序。內部排序的過程是一個逐步擴大記錄的有序序列長度的過程
各種排序的時間複雜度
排序方法 | 平均情況 | 最好情況 | 最壞情況 | 輔助空間 | 穩定性 |
---|---|---|---|---|---|
氣泡排序 | O(n^2) | O(n) | O(n^2) | O(1) | 穩定 |
簡單選擇排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 穩定 |
直接插入排序 | O(n^2) | O(n) | O(n^2) | O(1) | 穩定 |
希爾排序 | O(nlogn)~O(n^2) | O(n^1.3) | O(n^2) | O(1) | 不穩定 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不穩定 |
歸併排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 穩定 |
快速排序 | O(nlogn) | O(nlogn) | O(n^2) | O(logn)~O(n) | 不穩定 |
各種排序的時間測試
排序方法 | 5萬資料 | 50萬資料 | 100 萬資料 |
---|---|---|---|
優化氣泡排序 | 8.183984s | 太慢 | 太慢 |
簡單選擇排序 | 2.682234s | 太慢 | 太慢 |
直接插入排序 | 1.496184s | 太慢 | 太慢 |
希爾插入排序 | 0.008725s | 0.120478s | 0.256040s |
頂堆選擇排序 | 0.007705s | 0.097579s | 0.204354s |
遞迴歸併排序 | 0.006827s | 棧溢位 | 棧溢位 |
非遞歸併排序 | 0.006666s | 0.080683s | 0.148987s |
普通快速排序 | 0.007767s | 0.086778s | 0.188957s |
優化快速排序 | 0.007215s | 0.087385s | 0.182627s |
氣泡排序
- 氣泡排序,通過兩兩比較相鄰記錄,依次浮出最小或者最大值
- 優化方案:增加標誌位判斷是否已經有序,而不繼續冒泡
實現程式碼
int BubbleSort1(SqList * L)
{
int i, j;
int flag = 1;
for (i = 0; i < MAXSIZE - 1 && flag; i++) {
flag = 0; // 若標誌位flag不為1,說明已經有序,這個時候就可以退出迴圈了
for (j = MAXSIZE - 1; j > i; j--) {
if (L->r[j] < L->r[j - 1]) {
swap(L, j, j - 1);
flag = 1;
}
}
}
return 0;
}
簡單選擇排序
- 簡單選擇排序,通過n-i次關鍵字的比較,從n-i+1個記錄中選出關鍵字最小的記錄,然後與第i的位置替換
- 通俗的講就是,迴圈比較出後面最小的值依次放在前面去,就是從小到大了
實現程式碼
int SelectSort(SqList * L)
{
int i, j, min;
for (i = 0; i < MAXSIZE - 1; i++) {
min = i; // 將最小值的下標min指向當前下標i,起始位置
for (j = i + 1; j < MAXSIZE; j++) {
if (L->r[min] > L->r[j]) {
min = j; // 將最小值的下標min指向下標j,不停的比較,min指向當前迴圈最小值的下標
}
}
if (i != min) {
swap(L, i, min); // 將最小值min交換到有序的i的位置,則每次迴圈以後,最小的值都依次被排序
}
}
return 0;
}
直接插入排序
- 直接插入排序,從第二個數開始迴圈,將當前數拿出來,依次將左邊大於當前值的數向後移動一位,直到不大於當前值停止,然後將當前值插入空檔
實現程式碼
int InsertSort(SqList * L)
{
int i, j, flag;
for (i = 1; i < MAXSIZE; i++) { // 從下標1開始,假設下標0已經有序
if (L->r[i] < L->r[i - 1]) { // 若當前的值小於前一位,則執行下面的操作
flag = L->r[i]; // 將當前值記錄到儲存位
for (j = i - 1; j >= 0 && L->r[j] > flag; j--) { // 將左邊大於當前值的依次向後移動一位,直到不大於為止
L->r[j + 1] = L->r[j];
}
L->r[j + 1] = flag; // 插入儲存位到空檔
}
}
return 0;
}
希爾排序
- 希爾排序,直接插入排序的高效版,先使整個序列基本有序,然後再進行插入排序,大突破,打破O[n^2]
- 使基本有序的方法就是把順序迴圈變成跳躍方式的間隔迴圈,例如a[i]和a[i+1]比較變成a[i]和a[i+4]比較,然後慢慢縮小a[i]和a[i+2]比較,最後為1的時候就是插入排序了,這個過程在逐漸把排序變成基本有序
實現程式碼
int ShellSort(SqList * L)
{
int i, j, flag;
int increment = MAXSIZE;
while (increment > 1) {
increment = increment / 3 + 1; // 增量,目前沒有最好的增量,可以調整
for (i = increment; i < MAXSIZE; i++) { // 跳躍調整序列為基本有序,increment等於1時為普通插入排序
if (L->r[i] < L->r[i - increment]) {
flag = L->r[i]; // 將當前值記錄到儲存位
for (j = i - increment; j >= 0 && L->r[j] > flag; j -= increment) {
L->r[j + increment] = L->r[j]; // 記錄後移,查詢插入位置
}
L->r[j + increment] = flag; //插入儲存位到空檔
}
}
}
return 0;
}
堆排序,歸併排序
- 不做仔細講解,這兩個略麻煩
- 堆排序利用二叉樹構建大頂堆取根節點
- 歸併排序採用拆分比較,再歸併的方式實現排序
實現程式碼
// 見完整程式碼
快速排序
- 王者段位的排序,氣泡排序的升級版
- 中心思想就是遞迴不斷的把小的放左邊,大的放右邊,最後實現排序
實現程式碼
int QuickSort(SqList * L)
{
QSort(L, 0, MAXSIZE - 1);
return 0;
}
int QSort(SqList * L, int low, int high)
{
int pivot;
if (low < high) {
pivot = Partition(L, low, high); // 將low..high一分為二
QSort(L, low, pivot - 1); // 向低子表遞迴排序
QSort(L, pivot + 1, high); // 向高子表遞迴排序
}
return 0;
}
int Partition(SqList * L, int low, int high)
{
int pivotkey;
pivotkey = L->r[low]; //先暫時定第一個位置為中樞。此時選取可優化,因為可能不是中間值,可能偏大偏小
while (low < high) { // 不停的移動下標,當上下位置移動相交以後退出迴圈,此時中樞前後分別小於和大於中樞值
while (low < high && L->r[high] >= pivotkey) { // 從上向下直到當前值小於中樞值
high--;
}
swap(L, low, high); // 把上半區中比中樞記錄小的交換到低端
while (low < high && L->r[low] <= pivotkey) { // 從下向上直到當前值大於中樞值
low++;
}
swap(L, low, high); // 把下半區中比中樞記錄大的交換到頂端
}
return low; // 返回中樞下標位置
}
完整程式碼
/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
> Author: xiaojunyu/LunaW
> Mail : [email protected]
> Gmail : [email protected]
> Blog : http://blog.csdn.net/lunaw
> Web : http://lunaw.cn
*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#define MAXSIZE 50000 // 設定資料量
#define IsPrintf 0 // 設定為1可以檢視輸出
typedef struct {
int r[MAXSIZE + 1];
} SqList;
void Init(SqList * L); // 初始化隨機數
void Print(SqList * L); // 輸出資料
void swap(SqList * L, int i, int j); // 交換值
double GetTime(void); // 獲取當前時間
int BubbleSort0(SqList * L);
/* 最簡單的交換排序,不是真正意義上的氣泡排序 */
int BubbleSort(SqList * L);
/* 真正意義上的氣泡排序,兩兩交換,浮出最小或者最大值 */
int BubbleSort1(SqList * L);
/* 優化的氣泡排序,浮出最小或者最大值,增加標誌位判斷是否已經有序 */
int SelectSort(SqList * L);
/* 簡單選擇排序,通過n-i次關鍵字的比較,從n-i+1個記錄中選出關鍵字最小的記錄,然後與第i的位置替換 */
int InsertSort(SqList * L);
/* 直接插入排序,從第二個數開始迴圈,將當前數拿出來,依次將左邊大於當前值的數向後移動一位,直到不大於當前值停止,然後將當前值插入空檔 */
int ShellSort(SqList * L);
/* 希爾排序,直接插入排序的高效版,先使整個序列基本有序,然後再進行插入排序,大突破,打破O[n的平方] */
int HeapAdjust(SqList * L, int s, int m); // 構建大頂堆
int HeapSort(SqList * L);
/* 堆排序,簡單選擇排序的升級*/
int MSort(int SR[], int TR1[], int s, int t);
int Merge(int SR[], int TR[], int i, int m, int n);
int MergeSort(SqList * L);
/* 遞迴歸併排序 */
int MergePass(int SR[], int TR[], int s, int n);
int MergeSort1(SqList * L);
/* 非遞歸併排序 */
int Partition(SqList * L, int low, int high); // 選取中樞,然後使左邊都比它小,右邊都比它大
int QSort(SqList * L, int low, int high); // 遞迴排序
int QuickSort(SqList * L);
/* 快速排序,氣泡排序的升級,王者段位的排序,中心思想就是遞迴不斷的把小的放左邊,大的放右邊*/
int Partition1(SqList * L, int low, int high); // 三數取中法選取中樞,使左邊都比它小,右邊都比它大
int QSort1(SqList * L, int low, int high); // 遞迴排序
int QuickSort1(SqList * L);
/* 優化快速排序,採用三數取中法選取中樞,同時減少中樞的交換次數 */
int main(void)
{
double start, end;
SqList H;
SqList L;
printf("資料量: %d\n\n", MAXSIZE);
Init(&H);
// printf("原始資料: \n");
// Print(&H);
// L = H;
// start = GetTime();
// BubbleSort0(&L);
// end = GetTime();
// printf("初級氣泡排序: \n");
// Print(&L);
// printf("執行時間: %lfs\n\n", end - start);
// L = H;
// start = GetTime();
// BubbleSort(&L);
// end = GetTime();
// printf("標準氣泡排序: \n");
// Print(&L);
// printf("執行時間: %lfs\n\n", end - start);
if (MAXSIZE <= 50000) { // 資料大了時間太久
L = H;
start = GetTime();
BubbleSort1(&L);
end = GetTime();
printf("優化氣泡排序: \n");
Print(&L);
printf("執行時間: %lfs\n\n", end - start);
L = H;
start = GetTime();
SelectSort(&L);
end = GetTime();
printf("簡單選擇排序: \n");
Print(&L);
printf("執行時間: %lfs\n\n", end - start);
L = H;
start = GetTime();
InsertSort(&L);
end = GetTime();
printf("直接插入排序: \n");
Print(&L);
printf("執行時間: %lfs\n\n", end - start);
}
L = H;
start = GetTime();
ShellSort(&L);
end = GetTime();
printf("希爾插入排序: \n");
Print(&L);
printf("執行時間: %lfs\n\n", end - start);
L = H;
start = GetTime();
HeapSort(&L);
end = GetTime();
printf("頂堆選擇排序: \n");
Print(&L);
printf("執行時間: %lfs\n\n", end - start);
if (MAXSIZE <= 100000) { // 陣列遞迴把棧記憶體佔滿了會報錯,分配記憶體又很慢
L = H;
start = GetTime();
MergeSort(&L);
end = GetTime();
printf("遞迴歸併排序: \n");
Print(&L);
printf("執行時間: %lfs\n\n", end - start);
}
L = H;
start = GetTime();
MergeSort1(&L);
end = GetTime();
printf("非遞歸併排序: \n");
Print(&L);
printf("執行時間: %lfs\n\n", end - start);
L = H;
start = GetTime();
QuickSort(&L);
end = GetTime();
printf("普通快速排序: \n");
Print(&L);
printf("執行時間: %lfs\n\n", end - start);
L = H;
start = GetTime();
QuickSort1(&L);
end = GetTime();
printf("優化快速排序: \n");
Print(&L);
printf("執行時間: %lfs\n\n", end - start);
return 0;
}
void Print(SqList * L)
{
if (IsPrintf) {
int i;
for (i = 0; i < MAXSIZE; i++) {
printf("%d ", L->r[i]);
}
printf("\n");
}
}
void Init(SqList * L)
{
int i;
srand((unsigned)time(NULL));
for (i = 0; i < MAXSIZE; i++) {
L->r[i] = rand() % MAXSIZE;
}
}
void swap(SqList * L, int i, int j)
{
int temp = L->r[i];
L->r[i] = L->r[j];
L->r[j] = temp;
}
int BubbleSort1(SqList * L)
{
int i, j;
int flag = 1;
for (i = 0; i < MAXSIZE - 1 && flag; i++) {
flag = 0; // 若標誌位flag不為1,說明已經有序,這個時候就可以退出迴圈了
for (j = MAXSIZE - 1; j > i; j--) {
if (L->r[j] < L->r[j - 1]) {
swap(L, j, j - 1);
flag = 1;
}
}
}
return 0;
}
int BubbleSort(SqList * L)
{
int i, j;
for (i = 0; i < MAXSIZE