1. 程式人生 > >氣泡排序,選擇排序,插入排序,希爾排序,堆排序,歸併排序,快速排序實現

氣泡排序,選擇排序,插入排序,希爾排序,堆排序,歸併排序,快速排序實現

前言:排序的定義

排序是計算機內經常進行的一種操作,其目的是將一組“無序”的記錄序列調整為“有序”的記錄序列。分內部排序和外部排序,若整個排序過程不需要訪問外存便能完成,則稱此類排序問題為內部排序。反之,若參加排序的記錄數量很大,整個序列的排序過程不可能在記憶體中完成,則稱此類排序問題為外部排序。內部排序的過程是一個逐步擴大記錄的有序序列長度的過程

在這裡插入圖片描述

各種排序的時間複雜度

排序方法 平均情況 最好情況 最壞情況 輔助空間 穩定性
氣泡排序 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