四、快速排序

序言

快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。
步骤为:
从数列中挑出一个元素,称为”基准”(pivot),
重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。
递归到最底部时,数列的大小是零或一,也就是已经排序好了。这个算法一定会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。

图解排序过程:

实例分析

举例来说,现有数组 arr = [3,7,8,5,2,1,9,5,4],分区可以分解成以下步骤:
1.首先选定一个基准元素,这里我们元素 5 为基准元素(基准元素可以任意选择):

          pivot
            ↓
3   7   8   5   2   1   9   5   4

将基准元素与数组中最后一个元素交换位置,如果选择最后一个元素为基准元素可以省略该步:

                              pivot
                                ↓
3   7   8   4   2   1   9   5   5

从左到右(除了最后的基准元素),循环移动小于基准元素 5 的所有元素到数组开头,留下大于等于基准元素的元素接在后面。在这个过程它也为基准元素找寻最后摆放的位置。循环流程如下:
循环 i == 0 时,storeIndex == 0,找到一个小于基准元素的元素 3,那么将其与 storeIndex 所在位置的元素交换位置,这里是 3 自身,交换后将 storeIndex 自增 1,storeIndex == 1:

                                pivot
                                  ↓
  3   7   8   4   2   1   9   5   5
  ↑
storeIndex

循环 i == 3 时,storeIndex == 1,找到一个小于基准元素的元素 4:

     ┌───────┐                 pivot
     ↓       ↓                   ↓
 3   7   8   4   2   1   9   5   5
     ↑       ↑
storeIndex   i

交换位置后,storeIndex 自增 1,storeIndex == 2:

                              pivot
                                ↓
3   4   8   7   2   1   9   5   5
        ↑           
   storeIndex

循环 i == 4 时,storeIndex == 2,找到一个小于基准元素的元素 2:

        ┌───────┐             pivot
        ↓       ↓               ↓
3   4   8   7   2   1   9   5   5
        ↑       ↑
   storeIndex   i

交换位置后,storeIndex 自增 1,storeIndex == 3:

                              pivot
                                ↓
3   4   2   7   8   1   9   5   5
            ↑           
       storeIndex

循环 i == 5 时,storeIndex == 3,找到一个小于基准元素的元素 1:

           ┌───────┐         pivot
            ↓       ↓           ↓
3   4   2   7   8   1   9   5   5
            ↑       ↑
       storeIndex   i

交换后位置后,storeIndex 自增 1,storeIndex == 4:

                              pivot
                                ↓
3   4   2   1   8   7   9   5   5
                ↑           
           storeIndex

循环 i == 7 时,storeIndex == 4,找到一个小于等于基准元素的元素 5:

                ┌───────────┐ pivot
                ↓           ↓   ↓
3   4   2   1   8   7   9   5   5
                ↑           ↑
           storeIndex       i

交换后位置后,storeIndex 自增 1,storeIndex == 5:

                             pivot
                                ↓
3   4   2   1   5   7   9   8   5
                    ↑           
               storeIndex

循环结束后交换基准元素和 storeIndex 位置的元素的位置:

                  pivot
                    ↓
3   4   2   1   5   5   9   8   7
                    ↑           
               storeIndex

那么 storeIndex 的值就是基准元素的最终位置,这样整个分区过程就完成了。
引用维基百科上的一张图片:

快排code

//找出storeIndex
int partition(Element *arr, int left, int right) {
    int storeIndex = left;
    int pivot = arr[right]; // 直接选最右边的元素为基准元素
    int i = 0;
    for (i = left; i < right; i++) {
        if (arr[i] < pivot) {
            swap(&arr[storeIndex], &arr[i]);
            storeIndex++; // 交换位置后,storeIndex 自增 1,代表下一个可能要交换的位置
        }
    }
    swap(&arr[storeIndex], &arr[right]);// 将基准元素放置到最后的正确位置上
    return storeIndex;
}


void quick_sort(Element *arr, int left, int right) {
    if (left > right) {
        return;
    }
    int storeIndex = partition(arr, left, right);
    quick_sort(arr, left, storeIndex - 1);
    quick_sort(arr, storeIndex + 1, right);
}

第二种思路:

先找pivot

这里假设我们每次从三个数中,找出中位数进行作为主元

Element MEDIAN3(Element *arr, int left, int right) {
    int centerPos = (left + right) / 2;
    if (arr[left] > arr[centerPos]) {
        swap(&arr[left], &arr[centerPos]);
    }
    if (arr[left] > arr[right]) {
        swap(&arr[left], &arr[right]);
    }

    if (arr[centerPos] > arr[right]) {
        swap(&arr[centerPos], &arr[right]);
    }

    return arr[ centerPos];
};
将比pivot小的放在左边,比pivot大的放在右边,递归
        int pivot = MEDIAN3(arr,left,right); //找出主元
        printf("pivot = %d \n",pivot);
        int low = left;
        int high = right ;
        while (1) {

            while (arr[++low] < pivot) {}; //比pivot大,终止
            while (arr[--high] > pivot) {};//比pivot小,终止
            if (low < high) { 
                printf("swap %d   and %d \n",arr[low],arr[high]);
                swap(&arr[low], &arr[high]);
            } else {
                break; //low>hight终止

            }
        }
        quick_sort_inner(arr, left, low-1);    /* 递归解决左边 */
        quick_sort_inner(arr, low, right);   /* 递归解决右边 */

全部代码:

#include "quick_sort.h"
void quick_sort(Element *arr, int len) {
    quick_sort_inner(arr, 0, len -1);
}

Element MEDIAN3(Element *arr, int left, int right) {
    int centerPos = (left + right) / 2;
    if (arr[left] > arr[centerPos]) {
        swap(&arr[left], &arr[centerPos]);
    }
    if (arr[left] > arr[right]) {
        swap(&arr[left], &arr[right]);
    }

    if (arr[centerPos] > arr[right]) {
        swap(&arr[centerPos], &arr[right]);
    }
    printf("left = %d  center = %d  right = %d \n",arr[left],arr[centerPos],arr[right  ]);
    return arr[ centerPos];
};

void quick_sort_inner(Element *arr, int left, int right) {
    //找准pivot
    if (0 < right - left) {

        int pivot = MEDIAN3(arr,left,right);
        int low = left;
        int high = right ;
        while (1) {

            while (arr[++low] < pivot) {};
            while (arr[--high] > pivot) {};
            if (low < high) {
                printf("swap %d   and %d \n",arr[low],arr[high]);
                swap(&arr[low], &arr[high]);
            } else {
                break;
            }
        }
        quick_sort_inner(arr, left, low-1);    /* 递归解决左边 */
        quick_sort_inner(arr, low, right);   /* 递归解决右边 */
    }
};

swap函数:

void swap(Element *a, Element *b) {
    Element temp = *a;
    *a = *b;
    *b = temp;
};

code地址

https://github.com/HumorSmith/Alorthim/tree/master/sort

引用

http://bubkoo.com/2014/01/12/sort-algorithm/quick-sort/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值