并不是很懂wikipedia上面说快排的空间复杂度最坏情况是O(NlogN)啊,难道不是空间复杂度平均O(logN),最坏O(N)么……原地快排难道不是只要算递归栈深度就好了么……有谁给我解释一下啊(评论区orz)。

快排的核心思想我感觉就是选一个pivot,然后将待排序数列通过与pivot比较分为两个部分A和B(A部分的数均小于或等于pivot,B部分均大于或等于pivot),这样划分好的数列中的数就处于A <= pivot <= B的状态,接着对划分好的两个部分A和B分别递归调用快排函数,最后一定能出现划分好的部分中只剩下一个元素或没有元素的情况,而对于只有一个元素的数列或空列,我们认为它已经是有序的,这样就把数列排好了。

所以在快排中怎样对数组进行划分是关键,不同版本的快速排序大体结构都差不多,不同之处就在于划分函数的不同,这里就暂时称它为Partition函数吧,下面首先介绍四种不同的实现,再介绍它们的优化。

第一个版本

其实快排一开始不是我们现在看到的这样的,一开始Hoare发明的版本在《算法导论》这本书的思考题7-1上有提及。这个版本相当于是以开头元素为pivot,然后两个index分别从头和尾开始遍历,暂时把这两个index变量称为i和j吧,i从头开始,j从尾开始,其过程如下:

第一步——j往前遍历,遇到小于或等于pivot的数就停止

第二步——i往后遍历,遇到大于或等于pivot的数就停止

第三步——比较i和j大小,j > i则交换i和j位置上的数并在此基础上继续第一步,否则函数返回j

函数返回后,经过Partition函数的调用,数组中下标为j之前(包括j)的元素都小于或等于pivot,j之后的元素都大于或等于pivot,然后对这两部分进行快速排序。

例子:

代码如下:

int Hoare_Partition(int A[ ], int begin, int end){
    int pivot = A[begin];
    ;;
    ;

    ){
        do{
            j--;
        }while(A[j] > pivot);
        do{
            i++;
        }while(A[i] < pivot);
        if(j > i){
            Swap(A, i, j);
        }
        else{
            return j;
        }
    }
}

void Quick_Sort_1(int A[ ], int begin, int end){
    int pivot_position;
    if(begin >= end)
        return;
    pivot_position = Hoare_Partition(A, begin, end);
    Quick_Sort_1(A, begin, pivot_position);
    Quick_Sort_1(A, pivot_position + , end);
}

第二个版本

这个版本是对Hoare原版本的一个改进,Hoare原版本中Partition函数调用结束后pivot会存在于划分好的两个部分中的某一个中,因此第二个版本对此进行了改进,同样选取第一个元素作为pivot,如果数列的下标从begin开始,end结束的话,那么用Hoare的处理思想去处理从begin + 1到end部分的数列(Hoare的版本是处理从begin到end部分的数列的),这样处理完成后下标为begin + 1到j的数都是小于或等于pivot的,而下标为j + 1到end的数都是大于或等于pivot的,最后再将pivot和下标为j的数进行交换并返回j,这样一来下标为j之前(不包括j)的数均小于或等于pivot,下标为j之后的数均大于或等于pivot,pivot也就不在两个划分好的部分中,而独立存在了,因此只要对两个部分继续快排就好了,而不需要再将pivot包括进去。

例子:

代码如下:

int New_Partition_1(int A[ ], int begin, int end){
    int pivot = A[begin];
    int i = begin;
    ;

    ){
        do{
            j--;
        }while(A[j] > pivot);
        do{
            i++;
        }while(A[i] < pivot);
        if(j > i){
            Swap(A, i, j);
        }
        else{
            A[begin] = A[j];
            A[j] = pivot;
            return j;
        }
    }
}

void Quick_Sort_2(int A[ ], int begin, int end){
    int pivot_position;
    if(begin >= end)
        return;
    pivot_position = New_Partition_1(A, begin, end);
    Quick_Sort_2(A, begin, pivot_position - );
    Quick_Sort_2(A, pivot_position + , end);
}

 第三个版本

这个版本是现在比较常见的版本之一,将pivot所在位置挖成一个坑,i从begin + 1开始,j从end开始,然后:

第一步——j往前遍历,找到小于或等于pivot的数

     如果此时j > i,将下标为j的数填入现在的坑中,并在下标为j的位置产生一个新坑,进行第二步

     否则就将pivot填入现在的坑中,并返回j

第二步——i往后遍历,找到大于或等于pivot的数

     如果此时j > i,将下标为i的数填入现在的坑中,并在下标为i的位置产生一个新坑,进行第一步

     否则就将pivot填入现在的坑中,并返回i

例子:

代码如下:

int New_Partition_2(int A[ ], int begin, int end){
    int pivot = A[begin];
    int i = begin;
    ;

    ){
        do{
            j--;
        }while(A[j] > pivot);
        if(j > i){
            A[i] = A[j];
        }
        else{
            A[i] = pivot;
            return i;
        }
        do{
            i++;
        }while(A[i] < pivot&&i < j);
        if(j > i){
            A[j] = A[i];
        }
        else{
            A[j] = pivot;
            return j;
        }
    }
}

void Quick_Sort_3(int A[ ], int begin, int end){
    int pivot_position;
    if(begin >= end)
        return;
    pivot_position = New_Partition_2(A, begin, end);
    Quick_Sort_3(A, begin, pivot_position - );
    Quick_Sort_3(A, pivot_position + , end);
}

第四个版本

这个版本也很常见,就是把最后一个数作为pivot,i一开始等于begin,用j从begin开始往end - 1遍历,找到小于或等于pivot的数,与i所在位置的数交换,并将i++,最后将pivot和下标为i的数交换,返回i。

例子:

代码如下:

int New_Partition_3(int A[ ], int begin, int end){
    int pivot = A[end];
    int i, j = begin;

    for(i = begin; i < end; i++){
        if(A[i] <= pivot){
            Swap(A, i, j);
            j++;
        }
    }
    A[end] = A[j];
    A[j] = pivot;

    return j;
}

void Quick_Sort_4(int A[ ], int begin, int end){
    int pivot_position;
    if(begin >= end)
        return;
    pivot_position = New_Partition_3(A, begin, end);
    Quick_Sort_4(A, begin, pivot_position - );
    Quick_Sort_4(A, pivot_position + , end);
}

通过三数取中来优化快排

这种优化就是每次选择pivot时,选择首元素,尾元素,和中点处元素中第二大的元素来作为pivot,这样就可以尽量避免快排中最坏情况的出现(这样能尽可能让两个划分大小均匀,毕竟是选取大小较为中间的数作为pivot,减少选取到的pivot是极大值或极小值的概率),同时也可以避免某些partition函数的版本(如第三个版本)中出现数组访问越界的情况(因为选取的pivot尽可能是中间值,不会出现一次遍历结束全都大于或全都小于pivot的情况,遍历也就可以停住而不会继续下去导致越界访问),代码如下:

int Choose_Pivot(int A[], int begin, int end){
    ;

    if((A[pivot_position] >= A[end]&&A[pivot_position] >= A[begin])){
        if(A[begin] >= A[end]){
            pivot_position = begin;
        }
        else{
            pivot_position = end;
        }
    }
    else if((A[pivot_position] <= A[end]&&A[pivot_position] <= A[begin])){
        if(A[begin] >= A[end]){
            pivot_position = end;
        }
        else{
            pivot_position = begin;
        }
    }

    return pivot_position;
}

int Optimal_Partition_1(int A[], int begin, int end){
    int pivot_position = Choose_Pivot(A, begin, end), pivot;
    int i = begin, j;

    Swap(A, pivot_position, end);
    pivot = A[end];

    for(j = begin; j < end; j++){
        if(A[j] <= pivot){
            Swap(A, i, j);
            i++;
        }
    }
    A[end] = A[i];
    A[i] = pivot;

    return i;
}

void Quick_Sort_5(int A[], int begin, int end){
    int pivot_position;

    while(begin < end){
        pivot_position = Optimal_Partition_1(A, begin, end);
        if(end - pivot_position >= pivot_position - begin){
            Quick_Sort_5(A, begin, pivot_position - );
            begin = pivot_position + ;
        }
        else{
            Quick_Sort_5(A, pivot_position + , end);
            end = pivot_position - ;
        }
    }
}

通过优先排序较小划分来优化快排

快排是通过递归实现的,而在快排函数中会递归调用两次,第二次调用是尾递归,我们知道尾递归都是可以转换为循环来进行优化的。函数调用是很浪费时间的,并且递归又很占用内存空间,所以可以优先递归调用快排去排序较小的划分子列,这样可以节约空间,然后通过循环来实现原先对较大的划分子列进行快排的尾递归,节约时间和空间,上面通过三数取中法来优化快排的函数中就采用了这一方法。(实际上很多聪明的编译器都会自动优化这一点)

通过随机数来优化快排

因为pivot如果选取的不恰当就很可能出现最坏情况,所以可以生成随机数来决定选取数组中某个位置的数作为pivot,这样就会减小选到极大的或极小的pivot的情况的概率,从而使划分尽可能均匀。想一想,假设一个数列包含以下的数{1, 2, 3, 4, 5, 6, 7, 8},这个数列中{3, 4, 5, 6}这几个数都至少大于或小于数列中25%的数,选择它们为pivot的话就可以让划分变得更加均匀(相比较选择{1, 2, 7, 8}而言),每次都选择{3, 4, 5, 6}的话,在O(Nlog4N)即O(2NlogN)时间就可以完成排序,而选取随机数有50%的概率选中{3, 4, 5, 6}中的数,也就是最多需要O(4NlogN)时间即可完成排序,这个结果是远远比最坏情况O(N^2)要好的,当然注意用rand函数要加括号,代码如下:

int Optimal_Partition_2(int A[], int begin, int end){
    ) + begin);
    int pivot;
    int i = begin, j;

    pivot = A[pivot_position];
    Swap(A, pivot_position, end);

    for(j = begin; j < end; j++){
        if(A[j] <= pivot){
            Swap(A, i, j);
            i++;
        }
    }
    A[end] = A[i];
    A[i] = pivot;

    return i;
}

void Quick_Sort_6(int A[], int begin, int end){
    int pivot_position;

    while(begin < end){
        pivot_position = Optimal_Partition_2(A, begin, end);
        if(end - pivot_position >= pivot_position - begin){
            Quick_Sort_5(A, begin, pivot_position - );
            begin = pivot_position + ;
        }
        else{
            Quick_Sort_5(A, pivot_position + , end);
            end = pivot_position - ;
        }
    }
}

用插入排序来优化快排

快排在小规模数组方面的表现不如插入排序和堆排序好,而且一般递归到规模较小的数组时,数组已经处于一种较为有序,或者说局部有序的状态,这时用插入排序可以节省时间,代码如下:

void Insert_Sort(int A[], int begin, int end){
    int i, j, tmp;

    for(i = begin; i <= end; i++){
        tmp = A[i];
        ; j >= begin; j--){
            if(tmp > A[j]){
                break;
            }
            else{
                A[j + ] = A[j];
            }
        }
        A[j + ] = tmp;
    }
}

int Optimal_Partition_2(int A[], int begin, int end){
    ) + begin);
    int pivot;
    int i = begin, j;

    pivot = A[pivot_position];
    Swop(A, pivot_position, end);

    for(j = begin; j < end; j++){
        if(A[j] <= pivot){
            Swap(A, i, j);
            i++;
        }
    }
    A[end] = A[i];
    A[i] = pivot;

    return i;
}

void Quick_Sort_7(int A[], int begin, int end){
    int pivot_position;

    while(begin < end){
        ){
            Insert_Sort(A, begin, end);
            return;
        }
        pivot_position = Optimal_Partition_2(A, begin, end);
        if(end - pivot_position >= pivot_position - begin){
            Quick_Sort_5(A, begin, pivot_position - );
            begin = pivot_position + ;
        }
        else{
            Quick_Sort_5(A, pivot_position + , end);
            end = pivot_position - ;
        }
    }
}

完整代码:

//
//  main.c
//  Quick Sort
//
//  Created by 余南龙 on 2016/11/29.
//  Copyright © 2016年 余南龙. All rights reserved.
//

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

void Swap(int A[], int i, int j){
    int tmp = A[i];
    A[i] = A[j];
    A[j] = tmp;
}

int Choose_Pivot(int A[], int begin, int end){
    ;

    if((A[pivot_position] >= A[end]&&A[pivot_position] >= A[begin])){
        if(A[begin] >= A[end]){
            pivot_position = begin;
        }
        else{
            pivot_position = end;
        }
    }
    else if((A[pivot_position] <= A[end]&&A[pivot_position] <= A[begin])){
        if(A[begin] >= A[end]){
            pivot_position = end;
        }
        else{
            pivot_position = begin;
        }
    }

    return pivot_position;
}

void Insert_Sort(int A[], int begin, int end){
    int i, j, tmp;

    for(i = begin; i <= end; i++){
        tmp = A[i];
        ; j >= begin; j--){
            if(tmp > A[j]){
                break;
            }
            else{
                A[j + ] = A[j];
            }
        }
        A[j + ] = tmp;
    }
}

int Hoare_Partition(int A[ ], int begin, int end){
    int pivot = A[begin];
    ;;
    ;

    ){
        do{
            j--;
        }while(A[j] > pivot);
        do{
            i++;
        }while(A[i] < pivot);
        if(j > i){
            Swap(A, i, j);
        }
        else{
            return j;
        }
    }
}

int New_Partition_1(int A[ ], int begin, int end){
    int pivot = A[begin];
    int i = begin;
    ;

    ){
        do{
            j--;
        }while(A[j] > pivot);
        do{
            i++;
        }while(A[i] < pivot);
        if(j > i){
            Swap(A, i, j);
        }
        else{
            A[begin] = A[j];
            A[j] = pivot;
            return j;
        }
    }
}

int New_Partition_2(int A[ ], int begin, int end){
    int pivot = A[begin];
    int i = begin;
    ;

    ){
        do{
            j--;
        }while(A[j] > pivot);
        if(j > i){
            A[i] = A[j];
        }
        else{
            A[i] = pivot;
            return i;
        }
        do{
            i++;
        }while(A[i] < pivot&&i < j);
        if(j > i){
            A[j] = A[i];
        }
        else{
            A[j] = pivot;
            return j;
        }
    }
}

int New_Partition_3(int A[ ], int begin, int end){
    int pivot = A[end];
    int i, j = begin;

    for(i = begin; i < end; i++){
        if(A[i] <= pivot){
            Swap(A, i, j);
            j++;
        }
    }
    A[end] = A[j];
    A[j] = pivot;

    return j;
}

int Optimal_Partition_1(int A[], int begin, int end){
    int pivot_position = Choose_Pivot(A, begin, end), pivot;
    int i = begin, j;

    Swap(A, pivot_position, end);
    pivot = A[end];

    for(j = begin; j < end; j++){
        if(A[j] <= pivot){
            Swap(A, i, j);
            i++;
        }
    }
    A[end] = A[i];
    A[i] = pivot;

    return i;
}

int Optimal_Partition_2(int A[], int begin, int end){
    ) + begin);
    int pivot;
    int i = begin, j;

    pivot = A[pivot_position];
    Swap(A, pivot_position, end);

    for(j = begin; j < end; j++){
        if(A[j] <= pivot){
            Swap(A, i, j);
            i++;
        }
    }
    A[end] = A[i];
    A[i] = pivot;

    return i;
}

void Quick_Sort_1(int A[ ], int begin, int end){
    int pivot_position;
    if(begin >= end)
        return;
    pivot_position = Hoare_Partition(A, begin, end);
    Quick_Sort_1(A, begin, pivot_position);
    Quick_Sort_1(A, pivot_position + , end);
}

void Quick_Sort_2(int A[ ], int begin, int end){
    int pivot_position;
    if(begin >= end)
        return;
    pivot_position = New_Partition_1(A, begin, end);
    Quick_Sort_2(A, begin, pivot_position - );
    Quick_Sort_2(A, pivot_position + , end);
}

void Quick_Sort_3(int A[ ], int begin, int end){
    int pivot_position;
    if(begin >= end)
        return;
    pivot_position = New_Partition_2(A, begin, end);
    Quick_Sort_3(A, begin, pivot_position - );
    Quick_Sort_3(A, pivot_position + , end);
}

void Quick_Sort_4(int A[ ], int begin, int end){
    int pivot_position;
    if(begin >= end)
        return;
    pivot_position = New_Partition_3(A, begin, end);
    Quick_Sort_4(A, begin, pivot_position - );
    Quick_Sort_4(A, pivot_position + , end);
}

void Quick_Sort_5(int A[], int begin, int end){
    int pivot_position;

    while(begin < end){
        pivot_position = Optimal_Partition_1(A, begin, end);
        if(end - pivot_position >= pivot_position - begin){
            Quick_Sort_5(A, begin, pivot_position - );
            begin = pivot_position + ;
        }
        else{
            Quick_Sort_5(A, pivot_position + , end);
            end = pivot_position - ;
        }
    }
}

void Quick_Sort_6(int A[], int begin, int end){
    int pivot_position;

    while(begin < end){
        pivot_position = Optimal_Partition_2(A, begin, end);
        if(end - pivot_position >= pivot_position - begin){
            Quick_Sort_5(A, begin, pivot_position - );
            begin = pivot_position + ;
        }
        else{
            Quick_Sort_5(A, pivot_position + , end);
            end = pivot_position - ;
        }
    }
}

void Quick_Sort_7(int A[], int begin, int end){
    int pivot_position;

    while(begin < end){
        ){
            Insert_Sort(A, begin, end);
            return;
        }
        pivot_position = Optimal_Partition_2(A, begin, end);
        if(end - pivot_position >= pivot_position - begin){
            Quick_Sort_5(A, begin, pivot_position - );
            begin = pivot_position + ;
        }
        else{
            Quick_Sort_5(A, pivot_position + , end);
            end = pivot_position - ;
        }
    }
}

int main(int argc, const char * argv[]) {
    ] = {, , , , , , , , , };
    ] = {, , , , , , , , , };
    ] = {, , , , , , , , , };
    ] = {, , , , , , , , , };
    ] = {, , , , , , , , , };
    ] = {, , , , , , , , , };
    ];
    int i;

    srand((unsigned)time());
    ; i < ; i++){
        A_7[i] = (rand() % );
    }
    Quick_Sort_1(A_1, , );
    ; i < ; i++){
        printf("%d ", A_1[i]);
    }
    putchar('\n');
    Quick_Sort_2(A_2, , );
    ; i < ; i++){
        printf("%d ", A_2[i]);
    }
    putchar('\n');
    Quick_Sort_3(A_3, , );
    ; i < ; i++){
        printf("%d ", A_3[i]);
    }
    putchar('\n');
    Quick_Sort_4(A_4, , );
    ; i < ; i++){
        printf("%d ", A_4[i]);
    }
    putchar('\n');
    Quick_Sort_5(A_5, , );
    ; i < ; i++){
        printf("%d ", A_5[i]);
    }
    putchar('\n');
    Quick_Sort_6(A_6, , );
    ; i < ; i++){
        printf("%d ", A_6[i]);
    }
    putchar('\n');
    Quick_Sort_7(A_7, , );
    ; i < ; i++){
        printf("%d ", A_7[i]);
    }
    putchar('\n');
}

QuickSort快速排序的多种实现和优化的更多相关文章

  1. Algorithms - Quicksort - 快速排序算法

    相关概念 快速排序法 Quicksort 也是一个分治思想的算法. 对一个子数组 A[p: r] 进行快速排序的三步分治过程: 1, 分解. 将数组 A[p : r] 被划分为两个子数组(可能为空) ...

  2. quicksort 快速排序 quick sort

    * Java基本版 package cn.mediamix; import java.util.LinkedList; public class QuickSort { public static v ...

  3. QuickSort 快速排序 基于伪代码实现

    本文原创,转载请注明地址 http://www.cnblogs.com/baokang/p/4737492.html 伪代码 quicksort(A, lo, hi) if lo < hi p ...

  4. Java QuickSort 快速排序

    Java QuickSort /** * <html> * <body> * <P> Copyright 1994-2018 JasonInternational ...

  5. 快排 quicksort 快速排序

    首先看这个 http://www.cnblogs.com/morewindows/archive/2011/08/13/2137415.html 看完后就没有什么不理解的了... PS 注意 凡是在一 ...

  6. 【Java】 大话数据结构(15) 排序算法(2) (快速排序及其优化)

    本文根据<大话数据结构>一书,实现了Java版的快速排序. 更多:数据结构与算法合集 基本概念 基本思想:在每轮排序中,选取一个基准元素,其他元素中比基准元素小的排到数列的一边,大的排到数 ...

  7. 排序算法之快速排序(Quicksort)解析

    一.快速排序算法的优点,为什么称之为快排? Quicksort是对归并排序算法的优化,继承了归并排序的优点,同样应用了分治思想. 所谓的分治思想就是对一个问题“分而治之”,用分治思想来解决问题需要两个 ...

  8. 排序算法TWO:快速排序QuickSort

    import java.util.Random ; /** *快速排序思路:用到了分治法 * 一个数组A[0,n-1] 分解为三个部分,A[0,p - 1] , A[p] , A[p + 1, n-1 ...

  9. 快速排序 java实现 (原理-优化) 三路快排

    一.基本的快速排序 在数组中选取一个元素为基点,然后想办法把这个基点元素移动到它在排好序后的最终位置,使得新数组中在这个基点之前的元素都小于这个基点,而之后的元素都大于这个基点,然后再对前后两部分数组 ...

随机推荐

  1. c#防止多次运行代码收集

    1.原文:经过我的测试,还比较好用,但是有个问题,如果不注销,用另一个用户进入,则程序不能判断出已运行.所以只限于用在单用户环境,还是不太完美. class Program { [STAThread] ...

  2. 编译llvm+clang

    第一步,下载llvm代码: git clone git@github.com:llvm-mirror/llvm.git 第二步,进入llvm/tools目录并下载clang代码 cd llvm/too ...

  3. each循环

    var NA_COUNT=0; var NG_OK_COUNT=0; //获取所有检验明细为同一个编号的下拉选项,看有没有不是N/A的下拉选项 $("#@(Perfix)tbData sel ...

  4. [solr] - SolrJ增删查

    使用SolrJ进行对Solr的增.删.查功能. 参考引用: http://wiki.apache.org/solr/Solrj Eclipse中新建一个项目:TestSolr 其中SorlJ的Lib包 ...

  5. Python Queue队列

    queue is especially useful in threaded programming when information must be exchanged safely between ...

  6. SUSE Linux 13服务器版

    SUSE Linux 下面打开图形界面下的终端 桌面右键,选择运行 konsole命令打一桌面终端插件 配置静态ip /etc/sysconfig/network/ 虚拟机下面的网卡 vi /etc/ ...

  7. 学习C++的第二天

    1.输入 十六进制  cin>>hex>>x>>dec>>y    dec 是十进制    oct 是八进制 输出 十六进制 cout<<h ...

  8. bootstrap-巨幕、缩略图、警告框

    巨幕: <div class="jumbotron"> <div class="container"> <h1>W3Scho ...

  9. Linux 服务器的网络配置 - 1. 查看 Linux 服务器的网络连接

    1. 查看 Linux 服务器的网络连接 1)查看主机名: liuqian@ubuntu:~$ hostname ubuntu 2)查看 ip 地址: 用 ifconfig 即可,这里介绍命令组合用法 ...

  10. logstash 1.5.3 配置使用redis做续传

    logstash是ELK中的一员大将, redis插件也是<The Logstash Book>中介绍的一个很好用的玩意儿. 之前,用比较小的集群部署的时候,没有介入redis中间件,所以 ...