1. 程式人生 > >快速排序演算法(QSort,快排)及C語言實現

快速排序演算法(QSort,快排)及C語言實現

上節介紹瞭如何使用起泡排序的思想對無序表中的記錄按照一定的規則進行排序,本節再介紹一種排序演算法——快速排序演算法(Quick Sort)

C語言中自帶函式庫中就有快速排序——qsort函式 ,包含在 <stdlib.h> 標頭檔案中。

快速排序演算法是在起泡排序的基礎上進行改進的一種演算法,其實現的基本思想是:通過一次排序將整個無序表分成相互獨立的兩部分,其中一部分中的資料都比另一部分中包含的資料的值小,然後繼續沿用此方法分別對兩部分進行同樣的操作,直到每一個小部分不可再分,所得到的整個序列就成為了有序序列。

例如,對無序表{49,38,65,97,76,13,27,49}進行快速排序,大致過程為:
  1. 首先從表中選取一個記錄的關鍵字作為分割點(稱為“樞軸”或者支點,一般選擇第一個關鍵字),例如選取 49;
  2. 將表格中大於 49 個放置於 49 的右側,小於 49 的放置於 49 的左側,假設完成後的無序表為:{27,38,13,49,65,97,76,49}
  3. 以 49 為支點,將整個無序表分割成了兩個部分,分別為{27,38,13}{65,97,76,49},繼續採用此種方法分別對兩個子表進行排序;
  4. 前部分子表以 27 為支點,排序後的子表為{13,27,38},此部分已經有序;後部分子表以 65 為支點,排序後的子表為{49,65,97,76}
  5. 此時前半部分子表中的資料已完成排序;後部分子表繼續以 65為支點,將其分割為{49}{97,76},前者不需排序,後者排序後的結果為{76,97}
  6. 通過以上幾步的排序,最後由子表{13,27,38}{49}{49}{65}{76,97}構成有序表:{13,27,38,49,49,65,76,97}

整個過程中最重要的是實現第 2 步的分割操作,具體實現過程為:

  • 設定兩個指標 low 和 high,分別指向無序表的表頭和表尾,如下圖所示:
  • 先由 high 指標從右往左依次遍歷,直到找到一個比 49 小的關鍵字,所以 high 指標走到 27 的地方停止。找到之後將該關鍵字同 low 指向的關鍵字進行互換:
  • 然後指標 low 從左往右依次遍歷,直到找到一個比 49 大的關鍵字為止,所以 low 指標走到 65 的地方停止。同樣找到後同 high 指向的關鍵字進行互換:
  • 指標 high 繼續左移,到 13 所在的位置停止(13<49),然後同 low 指向的關鍵字進行互換:
  • 指標 low 繼續右移,到 97 所在的位置停止(97>49),然後同 high 指向的關鍵字互換位置:
  • 指標 high 繼續左移,此時兩指標相遇,整個過程結束;

該操作過程的具體實現程式碼為:
#define MAX 8
typedef struct {
    int key;
}SqNote;

typedef struct {
    SqNote r[MAX];
    int length;
}SqList;
//交換兩個記錄的位置
void swap(SqNote *a,SqNote *b){
    int key=a->key;
    a->key=b->key;
    b->key=key;
}
//快速排序,分割的過程
int Partition(SqList *L,int low,int high){
    int pivotkey=L->r[low].key;
    //直到兩指標相遇,程式結束
    while (low<high) {
        //high指標左移,直至遇到比pivotkey值小的記錄,指標停止移動
        while (low<high && L->r[high].key>=pivotkey) {
            high--;
        }
        //交換兩指標指向的記錄
        swap(&(L->r[low]), &(L->r[high]));
        //low 指標右移,直至遇到比pivotkey值大的記錄,指標停止移動
        while (low<high && L->r[low].key<=pivotkey) {
            low++;
        }
        //交換兩指標指向的記錄
        swap(&(L->r[low]), &(L->r[high]));
    }
    return low;
}
該方法其實還有可以改進的地方:在上邊實現分割的過程中,每次交換都將支點記錄的值進行移動,而實際上只需在整個過程結束後(low==high),兩指標指向的位置就是支點記錄的準確位置,所以無需每次都移動支點的位置,最後移動至正確的位置即可。

所以上邊的演算法還可以改寫為:
//此方法中,儲存記錄的陣列中,下標為 0 的位置時空著的,不放任何記錄,記錄從下標為 1 處開始依次存放
int Partition(SqList *L,int low,int high){
    L->r[0]=L->r[low];
    int pivotkey=L->r[low].key;
    //直到兩指標相遇,程式結束
    while (low<high) {
        //high指標左移,直至遇到比pivotkey值小的記錄,指標停止移動
        while (low<high && L->r[high].key>=pivotkey) {
            high--;
        }
        //直接將high指向的小於支點的記錄移動到low指標的位置。
        L->r[low]=L->r[high];
        //low 指標右移,直至遇到比pivotkey值大的記錄,指標停止移動
        while (low<high && L->r[low].key<=pivotkey) {
            low++;
        }
        //直接將low指向的大於支點的記錄移動到high指標的位置
        L->r[high]=L->r[low];
    }
    //將支點新增到準確的位置
    L->r[low]=L->r[0];
    return low;
}

快速排序的完整實現程式碼(C語言)

#include <stdio.h>
#include <stdlib.h>
#define MAX 9
//單個記錄的結構體
typedef struct {
    int key;
}SqNote;
//記錄表的結構體
typedef struct {
    SqNote r[MAX];
    int length;
}SqList;
//此方法中,儲存記錄的陣列中,下標為 0 的位置時空著的,不放任何記錄,記錄從下標為 1 處開始依次存放
int Partition(SqList *L,int low,int high){
    L->r[0]=L->r[low];
    int pivotkey=L->r[low].key;
    //直到兩指標相遇,程式結束
    while (low<high) {
        //high指標左移,直至遇到比pivotkey值小的記錄,指標停止移動
        while (low<high && L->r[high].key>=pivotkey) {
            high--;
        }
        //直接將high指向的小於支點的記錄移動到low指標的位置。
        L->r[low]=L->r[high];
        //low 指標右移,直至遇到比pivotkey值大的記錄,指標停止移動
        while (low<high && L->r[low].key<=pivotkey) {
            low++;
        }
        //直接將low指向的大於支點的記錄移動到high指標的位置
        L->r[high]=L->r[low];
    }
    //將支點新增到準確的位置
    L->r[low]=L->r[0];
    return low;
}
void QSort(SqList *L,int low,int high){
    if (low<high) {
        //找到支點的位置
        int pivotloc=Partition(L, low, high);
        //對支點左側的子表進行排序
        QSort(L, low, pivotloc-1);
        //對支點右側的子表進行排序
        QSort(L, pivotloc+1, high);
    }
}
void QuickSort(SqList *L){
    QSort(L, 1,L->length);
}
int main() {
    SqList * L=(SqList*)malloc(sizeof(SqList));
    L->length=8;
    L->r[1].key=49;
    L->r[2].key=38;
    L->r[3].key=65;
    L->r[4].key=97;
    L->r[5].key=76;
    L->r[6].key=13;
    L->r[7].key=27;
    L->r[8].key=49;
    QuickSort(L);
    for (int i=1; i<=L->length; i++) {
        printf("%d ",L->r[i].key);
    }
    return 0;
}
執行結果: 13 27 38 49 49 65 76 97

總結

快速排序演算法的時間複雜度為O(nlogn),是所有時間複雜度相同的排序方法中效能最好的排序演算法。