快速排序及其优化

来源:互联网 发布:热阻流软件 编辑:程序博客网 时间:2024/05/21 12:49

  快速排序的实现方式很多,看了一篇博客之后觉得他的实现方式最简洁。算法思路主要为:
1、先重排,选取一个基准值(可以每次选左边的,也可以随机选一个),使左边的元素都小于这个基准值,右边的元素都大于这个基准值。
2、递归求解。
详细一点的说来第一步的方法就是:
以6,10,22,15,3,4,8,12这一组数A为例。
第一步:选取最左端的6作为基准值comp=6,m=0,然后依次与A[1],A[2]。。。比较(i++),遇到A[4]比comp小,交换A[1](基准值的右边一个数)与A[4]的位置(++m),然后继续比较遇到A[5]比comp小,交换A[2]与A[5]的位置继续(++m),知道循环完数组A,得到6,3,4,10,22,15,8,12,退出循环后,最后将6与4做个交换(A[2]显然是分隔点),swap(A,l,m)即完成了一次重排:3,4,6,10,22,15,8,12比6小的均在左边,比6大的均在右边。
第二步:对左右部分继续递归求解即可。
总结一下:选取好基准点之后先不要动基准点的位置,就当个柱子在那,最后排好了其他数之后再拆了柱子将分界点处的数与其进行交换,否则基准值进行交换的话被换到哪里了都不知道,又要有个变量随时记录位置,我将这个方法称为立柱排序
代码如下:

void swap(int* a, int i, int j) {    int temp = a[i];    a[i] = a[j];    a[j] = temp;}void fast_sort(int *A,int l,int r){    if(l>=r) return;//当划分到元素个数为0或者1个时直接退出递归    int m=l;    int comp=A[m];    for(int i=l+1;i<=r;i++)    {        if(A[i]<=comp)//这里的等号要加,当数组中重复的元素比较多时,等号可以提高算法的速度,虽然多了一次交换            swap(A,++m,i);    }    swap(A,m,l);//将基准值(左端点与分隔处交换)    fast_sort(A,l,m-1);    fast_sort(A,m+1,r);}

还有一种就是从一篇博客看来的挖坑排序,原理都是一样的,只是交换的方式不同,比前面的立柱排序执行的交换次数要少。还是以6,10,22,15,3,4,8,12为例来讲。
第一步:和前面一样,先选取一个基准值(挖坑),comp=A[0],然后从后向前找到比基准值小的数4(挖的新坑A[5]),填到A[0]的坑中,既A[m++]=A[n],然后从前向后找比基准值大的数,找到了10,填到刚刚坑A[5]中,现在又有个新坑A[1]。
第二步:重复第一步的操作,先从后向前找,在从前向后找,依次挖坑补坑,最后在m==n的时候停止,将comp(基准值)补到最后一个坑中
第三步:递归左右部分即可。
这里说明一下为什么要先从后向前呢?如果不这样的话就要有个中间变量来暂存进行交换(就和立柱排序一毛一样了),而先从后先前就可以直接将A[m]的坑填上,减少了赋值的操作。
代码如下:

void fast_sort(int *A,int l,int r){    if(l>=r)return;    int m=l,n=r;    int comp=A[m];    while(m<n)    {    //注意m<n的条件要时刻满足,否则在后面m和n继续递归时会越界        while(m<n&&comp<A[n])n--;        if(m<n)A[m++]=A[n];//先从后向前找比基准值小的数放到左边去,即跳过比基准值大的数        while(m<n&&comp>=A[m])m++;        if(m<n)A[n--]=A[m];//再从前向后找比基准值大的数放到右边去    }    A[m]=comp;    fast_sort(A,l,m-1);    fast_sort(A,m+1,r);}

下面讲讲快速排序的优化,首先是基准点的选择上面,因为在不同情况下快排的性能可能会波动到O(n^2),所以可以随机选择基准点使其更接近平均值O(n*log2n),只需要每次随机选择的基准点与最左端的点做一次交换即可,完整代码如下

#include <string>#include <iostream>#include <ctime>#include <cstdlib>using namespace std;#define Maxn 100int T[Maxn] = { 3, 2, 1, 4, 10, 2, 3, 55, 22, 99, 45, 24, 35 };void swap(int* a, int i, int j) {    int temp = a[i];    a[i] = a[j];    a[j] = temp;}double random(double start, double end){    return start + (end - start)*rand() / (RAND_MAX + 1.0);}void fast_sort(int *A,int l,int r){    if (l >= r)return;    int m = l, n = r;    int comp_pos = int(random(l, r + 1));    swap(A, m, comp_pos);    int comp = A[m];    while (m<n)    {        while (comp<A[n])            n--;        if (m<n)            A[m++] = A[n];//先从后向前找比基准值小的数放到左边去        while (m<n&&comp >= A[m])            m++;        if (m<n)            A[n--] = A[m];//再从前向后找比基准值大的数放到右边去    }    A[m] = comp;    fast_sort(A, l, m - 1);    fast_sort(A, m + 1, r);}int main(){    srand(unsigned(time(0)));    fast_sort(T, 0, 10);    return 0;}

进一步优化:
插入排序的时间复杂度是O(N^2),但是在已经排序好的数组上面,插入排序的最佳情况是O(n),插入排序在小数组的排序上是非常高效的,这给我们一个提示,在快速排序递归的子序列,如果序列规模足够小,可以使用插入排序替代快速排序,因此可以在快排之前判断数组大小,如果小于一个阀值就使用插入排序。所以可以在快排中引入插入排序
代码如下

void fast_sort(int *A,int l,int r){    if (l >= r)return;    // 在数组大小小于7的情况下使用快速排序    if (r - l + 1 < 7) {        for (int i = l; i <= r; i++) {            for (int j = i+1; j > l && A[j - 1] > A[j]; j--) {                swap(A, j, j - 1);            }        }        return;    }    int m = l, n = r;    int comp_pos = int(random(l, r + 1));    swap(A, m, comp_pos);    int comp = A[m];    while (m<n)    {        while (comp<A[n])            n--;        if (m<n)            A[m++] = A[n];//先从后向前找比基准值小的数放到左边去        while (m<n&&comp >= A[m])            m++;        if (m<n)            A[n--] = A[m];//再从前向后找比基准值大的数放到右边去    }    A[m] = comp;    fast_sort(A, l, m - 1);    fast_sort(A, m + 1, r);}

第一篇:
http://blog.csdn.net/morewindows/article/details/6684558.
第二篇:
http://www.blogjava.net/killme2008/archive/2010/09/08/quicksort_optimized.html。

到此说完了快速排序的内容,基本也能做到默写出算法的过程,我比较喜欢第一种立柱排序,因为第二种挖坑排序的条件m < n容易掉,且记忆起来没第一种容易。这里感谢两篇博客的作者,我只是理解后在这里总结了下我消化的内容。

原创粉丝点击