[算法学习笔记]又一个采用分治法的排序算法----快速排序算法

快速排序算法

快速排序算法是当前在实际排序应用中最好的选择,虽然排序算法最坏情况下的时间复杂度为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);
    }
}


  • 合并
    由于快速排序是原址排序的,所以不需要合并操作.

原址排序是指排序的过程中不需要使用临时数组来存储, 直接在原来的数组上排序即可,快速排序,堆排序等都是原址排序, 归并排序并不属于原址排序

快速排序的示意动画

[算法学习笔记]又一个采用分治法的排序算法----快速排序算法_第1张图片

c语言实现

完整代码如下:

#include 
#include 
#include 

#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);
    }
}

每次进行排序前都会随机产生一个范围内的随机数作为下标, 与当前最后一个元素交换位置, 就能避免最坏情况的出现, 但是这样做会牺牲一定性能, 在数据量较小的情况下, 随机化版本的快排算法可能在大部分实验过程中消耗的时间大于普通快拍, 但是当数据量较大时或者出现最坏情况时, 随机化版本则胜出

你可能感兴趣的:(算法)