1. 程式人生 > >[演算法學習筆記]又一個採用分治法的排序演算法----快速排序演算法

[演算法學習筆記]又一個採用分治法的排序演算法----快速排序演算法

快速排序演算法

快速排序演算法是當前在實際排序應用中最好的選擇,雖然排序演算法最壞情況下的時間複雜度為O(n^2), 但是可以通過隨機化的方式避免最壞情況的發生, 而快速演算法的平均複雜度為O(n lgn), 而且隱含的常數因子非常小, 因此效率十分高.

演算法詳細

快排和歸併排序一樣也是採用分治策略的排序演算法, 分為三個步驟: 分解, 解決, 合併

  • 分解
    將陣列A[p…r]分解成兩個子陣列A[p…q-1]和A[q+1…r], 注意存在某個子陣列為空的可能性. 分解的具體做法是選取陣列的最後一個元素, 然後前面的每一個元素都和最後一個元素比較, 如果小於或者等於最後一個元素則將它放入前面的陣列中, 否則放入後一個數組, 全部比較完成後將A[q]和A[r]交換位置,具體做法如下:

    int partition(int nums[], int p, int r){
    int x = nums[r];
    int i = p - 1, j;
    for(j = p; j < r; j++){
        if(nums[j] <= x){
            i++;
            swap(nums+i, nums+j);
        }
    }
    swap(nums+i+1, nums+r);
    
    return i+1;
    }
  • 解決
    通過遞迴呼叫來實現排序的過程, 實現如下:
void quickSort(int nums[], int p, int r){
    if(p < r){
        int
q = partition(nums, p, r); quickSort(nums, p, q - 1); quickSort(nums, q + 1, r); } }

  • 合併
    由於快速排序是原址排序的,所以不需要合併操作.
原址排序是指排序的過程中不需要使用臨時陣列來儲存, 直接在原來的陣列上排序即可,快速排序,堆排序等都是原址排序, 歸併排序並不屬於原址排序

快速排序的示意動畫

這裡寫圖片描述

c語言實現

完整程式碼如下:

#include <stdio.h>
#include <stdlib.h>
#include <time.h> #define MAX_N 1000000 void swap(int *, int *); void quickSort(int [], int, int); int partition(int [], int, int); int main(){ int nums[MAX_N]; int i; srand(time(0)); printf("Original array: "); for(i = 0; i < MAX_N; i++){ nums[i] = rand() % (MAX_N * 2) + 1; printf("%d\t", nums[i]); } printf("\n"); quickSort(nums, 0, MAX_N - 1); printf("After quick sort:"); for(i = 0; i < MAX_N; i++){ printf("%d\t", nums[i]); } printf("\n"); return 0; } void swap(int *a, int *b){ int temp = *a; *a = *b; *b = temp; } void quickSort(int nums[], int p, int r){ if(p < r){ int q = partition(nums, p, r); quickSort(nums, p, q - 1); quickSort(nums, q + 1, r); } } int partition(int nums[], int p, int r){ int x = nums[r]; int i = p - 1, j; for(j = p; j < r; j++){ if(nums[j] <= x){ i++; swap(nums+i, nums+j); } } swap(nums+i+1, nums+r); return i+1; }

維基百科上的快排實現

void swap(int *x, int *y) {
    int t = *x;
    *x = *y;
    *y = t;
}
void quick_sort_recursive(int arr[], int start, int end) {
    if (start >= end)
        return;//這是為了防止宣告堆疊陣列時當機
    int mid = arr[end];
    int left = start, right = end - 1;
    while (left < right) {
        while (arr[left] < mid && left < right)
            left++;
        while (arr[right] >= mid && left < right)
            right--;
        swap(&arr[left], &arr[right]);
    }
    if (arr[left] >= arr[end])
        swap(&arr[left], &arr[end]);
    else
        left++;
    quick_sort_recursive(arr, start, left - 1);
    quick_sort_recursive(arr, left + 1, end);
}
void quick_sort(int arr[], int len) {
    quick_sort_recursive(arr, 0, len - 1);
}

快排的隨機化版本

快排的最壞情況發生在當待排序的陣列已經是有序的情況下, 解決這種的辦法也十分簡單, 而且對於程式碼的改動也十分小.

int Random(int p, int r){
    srand(time(0));
    int q = (int)(rand() %(r - p + 1) + p);
    return q;
}

int randomizedPartition(int A[], int p, int r){
    int i = Random(p, r);
    swap(A+i, A+r);
    return partition(A, p, r);
}

void randomizedQuickSort(int A[], int p, int r){
    if(p < r){
        int q = randomizedPartition(A, p, r);
        randomizedQuickSort(A, p, q-1);
        randomizedQuickSort(A, q+1, r);
    }
}

每次進行排序前都會隨機產生一個範圍內的隨機數作為下標, 與當前最後一個元素交換位置, 就能避免最壞情況的出現, 但是這樣做會犧牲一定效能, 在資料量較小的情況下, 隨機化版本的快排演算法可能在大部分實驗過程中消耗的時間大於普通快拍, 但是當資料量較大時或者出現最壞情況時, 隨機化版本則勝出