快速排序及其优化

22 篇文章 1 订阅
14 篇文章 0 订阅

  快速排序的实现方式很多,看了一篇博客之后觉得他的实现方式最简洁。算法思路主要为:
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 100
int 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容易掉,且记忆起来没第一种容易。这里感谢两篇博客的作者,我只是理解后在这里总结了下我消化的内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值