快速排序,找中位

来源:互联网 发布:免费企业域名邮箱 编辑:程序博客网 时间:2024/06/03 20:34

陆小凤原创


小白:快速排序算法,这么上古的东西,而且每一种语言都有相应的实现,还需要学吗?

陆小凤:如果目标是解决常规的业务开发,那就没有理由去做这样的事情,有时间还不如去理解业务。但是,如果你的工作对性能的要求很高(比如大量的数据运算),或者你需要看懂一些经常与算法打交道的项目,或者你想感受一下“算法如何把问题变小”的设计思想,或者…

小白:好吧,陆大侠,你总能找到理由来开讲!

快速排序是工程中常见的非稳定的排序算法,最快时为O(nlogn),最差时为O(n^2)。

快排的设计,体现了分而治之的思想,一步步地把问题变小,最终解决。

本文介绍快排的实现设计,试图从中感受分而治之的思想。

快排的关键点,一是确定中位值,二是确定中位。

中位值,坐在中位,左边的数比中位值小,右边的数比中位值大。于是,这个中位值就是最终排序后的一个元素,它的位置不需要再调整。然后,就中位两边的两部分,再重复定值定位的行为,即可解决问题。

小白:也就是每次确定一个值,再对左右两部分递归操作,不断减小问题规模…,你之前讲的递归思想又可以上场了。

陆小凤:设计思想是不断地把问题变小,但不一定用递归来实现,迭代也可以。

(一)定中位值

这个很随意。

一般,可以选择数列最左边,或最右边的值作为中位值。

也可随机地选择一个值(包括中间的值),然后跟最左或最右的值互换位置,于是又回到了一般的思路。

对于中位值,需要用变量记录下来,因为数列中的中位值很快就会被其它值覆盖。

中位值,最终要坐到中位。

(二)定中位

定中位,是实现快排的最复杂的流程。

确定出来的中位,用来放置中位值。

最终,要保证“左小右大”,即中位左边的数小于(或等于)中位值,而右边的数大于(或等于)中位值。

这里介绍两个设计思路。

(1)“小左大右”

小左大右
图片来自网络-小左大右

设计两个索引i跟j,分列数列的两端。不断地把小值扔到i的位置,然后把大值扔到j的位置,一直保证[start,i]是小值,[j,end]是大值。

最终,i==j,设置好中位值,再返回i。

在拿到中位后,数列分为[start,i-1]跟[i+1,end]两部分,用次“快排”的算法解决它们。

小白:这就是空手套白狼啊,先假设这个函数已经能排序,再在函数里面调用自己。

这是“小左大右”的一个演示图:
快排找中位例子
可以看到,每一轮的定位,都是不断地把红框区域变小,最终就是中位。

根据这种设计思想,可以这样编码实现:

#include <stdio.h>int position(int* arr, int i, int j) {    int v = arr[i];    while (i < j) {        while (i < j && arr[j] >= v) {            j--;        }        if (i<j) {            arr[i++] = arr[j];        }        while (i<j && arr[i]<v) {            i++;        }        if (i<j) {            arr[j--]=arr[i];        }    }    arr[i]=v;    return i;}void _quicksort(int* arr, int i, int j) {    if (i<j) {        int pos = position(arr, i, j);        _quicksort(arr, i, pos-1);        _quicksort(arr, pos+1, j);    }}void quicksort(int* arr, int size) {    _quicksort(arr, 0, size-1);}int main(int argc, char *argv[]){    int arr[] = {4, 2, 5, 1, 6, 6, 8, 9, 8, 3};    int size=sizeof arr/sizeof *arr;    for (int i = 0; i < size; i ++) {        printf("%d, ", arr[i]);    }    quicksort(arr, size);    printf("\nafter_sort:\n");    for (int i = 0; i < size; i ++) {        printf("%d, ", arr[i]);    }    printf("\n");    return 0;}
(2)“小左”

把所有小值扔到左边,最后,中位就是小值堆的右边的第一个位置。

可以取list[end]即最右的数值为中位值(或最左边也可以)。

设计i、j索引。保证[start,i]为小值,而j用来遍历所有数值(从start到end),如果发现list[j]小于中位值,则扩大[start,i]并把小值扔到里面(list[j]与list[i]互换即可)。

最终,i+1为中位。

示意图是这样的:
快排-小左

按这种设计,可以这样写代码:

#include <iostream>using namespace std;template< typename T >void Exchange(         T &  leftElement,         T & rightElement         ){    T  temp = leftElement;    leftElement = rightElement;    rightElement = temp;}template< typename T >int  Partition(         T List[],         int  nStartPos,         int  nStopPos        ){    if ( nStartPos < 0 || nStopPos < 0 )        return -1;    int  nLessEndPos = nStartPos - 1;    int  nCurrentDealPos = nStartPos;    while ( nCurrentDealPos < nStopPos )    {        if ( List[nCurrentDealPos] <= List[nStopPos] )        {            nLessEndPos ++;            Exchange( List[nLessEndPos], List[nCurrentDealPos] );        }        nCurrentDealPos ++;    }    Exchange( List[nLessEndPos+1], List[nStopPos] );    return nLessEndPos + 1;}template< typename T >void QuickSort(         T List[],         int nStartPos,         int nStopPos        ){    if ( nStartPos < nStopPos )    {        int  nMidPos = Partition<T>( List, nStartPos, nStopPos );        QuickSort( List, nStartPos, nMidPos - 1 );        QuickSort( List, nMidPos + 1, nStopPos );    }}int main(int argc, const char *argv[]){    int arr[] = {4, 2, 5, 1, 6, 6, 8, 9, 8, 3};    int size=sizeof arr/sizeof *arr;    for (int i = 0; i < size; i ++) {        printf("%d, ", arr[i]);    }    QuickSort<int>(arr, 0, size-1);    printf("\nafter_sort:\n");    for (int i = 0; i < size; i ++) {        printf("%d, ", arr[i]);    }    printf("\n");    return 0;}

小白:困死了。