排序之快速排序(下)

快速排序优化

  快排上是可以进行优化的,那么可以进行哪些优化了,是不是和你想的一样了? 我们往下看

  1、优化枢纽值的选取

    如果我们选取的pivotKey是处于整个序列的中间位置,那么我们可以将整个序列分成小数集合和大数集合了。但注意,我刚才说的是“如果……是中间”, 那么假如我们选取的pivotkey不是中间数如何呢?比如我们用到的数组{9,1,5,8,3,7,4,6,2},由代码“pivotkey=arr[low];”知道,我们应该选取9作为第一个枢轴pivotKey。此时,经过一轮 “pivot=partition(arr,1,9);”转换后,它只是更换了9与2的位置,并且返回9给pivot,整个系列并没有实质性的变化。

    就是说,代码“pivotkey=arr[low];”变成了一个潜在的性能瓶颈。排序速度的快慢取决于arr[low]的关键字处在整个序列 的位置,arr[low]太小或者太大,都会影响性能(比如第一例子中的5就是一个中间数,而第二例子的9就是一个相对整个序列过大的数)。因为在现实中, 待排序的系列极有可能是基本有序的,此时,总是固定选取第一个关键字(其实无论是固定选取哪一个位置的关键字)作为首个枢轴就变成了极为不合理的作法。
        改进办法,有人提出,应该随机获得一个low与high之间的数rnd,让它的关键字L.r[rnd]与L.r[low]交换,此时就不容易出现这样的情况。这被称为随机选取枢轴法。应该说,这在某种程度上,解决了对于基本有序的序列快速排序时的性能瓶颈。不过,随机就有些撞大运的感觉,万一没撞成功,随机到了依然是很小或很大的关键字怎么办呢?
        再改进,于是就有了三数取中(median-of-three)法即取三个关键字先进行排序,将中间数作为枢轴,一般是取左端、右端和中间三个数
也可以随机选取。这样至少这个中间数一定不会是最小或者最大的数,从概率来说,取三个数均为最小或最大数的可能性是微乎其微的,因此中间数位于较为中间的
值的可能性就大大提高了。由于整个序列是无序状态,随机选取三个数和从左中右端取三个数其实是一回事,而且随机数生成器本身还会带来时间上的开销,因此随机生成不予考虑。
        我们来看看取左端、右端和中间三个数的实现代码,我们来看看新的partition方法。

    /**
     * 寻找枢纽值的下标,并返回
     *     交换arr[low...high]中的元素,移动枢纽值到正确的位置
     * @param arr
     * @param low
     * @param high
     * @return
     */
    private static int partition(int[] arr, int low, int high) {
        //从第一、中间、最后三个元素中选取第二大的
        int m = low + (high-low)/2;
        if (arr[low] > arr[high]){
            swap(arr,low,high);        /* 交换左端与右端数据,保证左端较小 */
        }
        if (arr[m] > arr[high]){
            swap(arr,high,m);        /* 交换中间与右端数据,保证中间较小 */
        }
        if (arr[m] > arr[low]){
            swap(arr,m,low);        /* 交换中间与左端数据,保证左端较小 */
        }

        int pivotValue = arr[low];                        // 选取arr[low]当作枢纽值
        // 从两端向中间扫描,使枢纽值移动到正确的位置
        while(low < high){
            while(low<high && arr[high]>=pivotValue){
                high --;
            }
            swap(arr,low,high);                         // 将比枢纽值小的记录交换到低端
            while(low<high && arr[low]<=pivotValue){
                low ++;
            }
            swap(arr,low,high);                            // 将比枢纽值大的记录交换到高端
        }
        return low;
    }

    这样就保障枢纽值就有一定的合理性了,当然,如果数列特别大的话,可以进行九数取中(median-of-nine),它是先从数组中分三次取样,每次取三个数,三个样品各取出中数,然后从这三个中数当中再取出一个中数作为枢轴。

  2、优化不必要的交换

    直接上代码

    /**
     * 寻找枢纽值的下标,并返回
     *     交换arr[low...high]中的元素,移动枢纽值到正确的位置
     * @param arr
     * @param low
     * @param high
     * @return
     */
    private static int partition(int[] arr, int low, int high) {
        //从第一、中间、最后三个元素中选取第二大的
        int m = low + (high-low)/2;
        if (arr[low] > arr[high]){
            swap(arr,low,high);        /* 交换左端与右端数据,保证左端较小 */
        }
        if (arr[m] > arr[high]){
            swap(arr,high,m);        /* 交换中间与右端数据,保证中间较小 */
        }
        if (arr[m] > arr[low]){
            swap(arr,m,low);        /* 交换中间与左端数据,保证左端较小 */
        }

        int pivotValue = arr[low];                        // 选取arr[low]当作枢纽值
        // 从两端向中间扫描,使枢纽值移动到正确的位置
        while(low < high){
            while(low<high && arr[high]>=pivotValue){
                high --;
            }
            arr[low] = arr[high];                         // 将比枢纽值小的记录交换到低端
            while(low<high && arr[low]<=pivotValue){
                low ++;
            }
            arr[high]=arr[low];                            // 将比枢纽值大的记录交换到高端
        }
        arr[low] = pivotValue;
        return low;
    }

  3、优化小序列时的排序方案 

    如果数组非常小,其实快速排序反而不如直接插入排序来得更好(直接插入是简单排序中性能最好)。其原因在于快速排序用到了递归操作,在大量数据排序时,这点性能影响相对于它的整体算法优势而言是可以忽略的,但如果数组只有几个记录需要排序时,用快速排序效率反而更低,需要改进下qSort方法。

    /**
     * 对序列arr中的子序列arr[low..high]作快速排序
     * @param arr
     * @param low
     * @param high
     */
    private static void qSort(int[] arr, int low, int high) {
        int pivotKey;
        if(high-low > 7){
            // 找到枢纽值的位置,此时arr[low,pivotKey-1]都小于(大于)arr[pivotKey],arr[pivotKey+1...high]都大于(小于)arr[pivotKey]
            pivotKey = partition(arr,low,high);
            qSort(arr,low,pivotKey-1);                    // 对arr[low...pivotKey-1]进行快速排序
            qSort(arr,pivotKey+1,high);                    // 对arr[pivotKey+1...high]进行快速排序
        } else {
            strainghtInsertSort(arr,low,high);
        }
    }
    /**
     * 对序列arr[low...high]a进行直接插入排序
     * @param arr
     * @param low
     * @param high
     */
    private static void strainghtInsertSort(int[] arr, int low, int high) {
        for(int i=low+1; i<=high; i++){                            // 将arr[i]插入到有序列表
            for(int j=i-1; j>=0&&arr[j]>arr[j+1]; j--){            // arr[low...j]是有序列表
                swap(arr,j,j+1);
            }
        }
    }

  等等,还可以进行其他方面的优化的,更多的优化就交给各位了!

时间: 2025-01-08 02:29:59

排序之快速排序(下)的相关文章

排序之快速排序

package com.xsz.demo; /**     * @author cwqi    * @date 2015-1-6    */ //排序之快速排序,基本思路為分治+挖坑,方法有兩種:一是雙邊掃面:二是單邊掃描. public class QuickSort { public static void main (String[] args){ int a[] ={2,3,30,1,4,56,2,7,3,8}; //int a[] ={4,5,6,2,7,3,8}; //int a[]

希尔排序和快速排序

//希尔排序 在直接插入排序算法中,每次插入一个数,使有序序列只增加1个节点,并且对插入下一个数没有提供任何帮助. 如果比较相隔较远距离(称为增量)的数,使得数移动时能跨过多个元素,则进行一次比较就可能消除多个元素交换. D.L.shell于1959年在以他名字命名的排序算法中实现了这一思想.算法先将要排序的一组数按某个增量d分成若干组, 每组中记录的下标相差d.对每组中全部元素进行排序,然后再用一个较小的增量对它进行,在每组中再进行排序. 当增量减到1时,整个要排序的数被分成一组,排序完成 p

php 实现冒泡算法排序、快速排序、选择排序,插入排序

许多人都说 算法是程序的核心,一个程序的好于差,关键是这个程序算法的优劣.作为一个初级phper,虽然很少接触到算法方面的东西 .但是对于冒泡排序,插入排序,选择排序,快速排序四种基本算法,我想还是要掌握的.下面是我按自己的理解,将四个方法分析一遍. 需求:分别用 冒泡排序法,快速排序法,选择排序法,插入排序法将下面数组中 的值按照从小到的顺序进行排序.  $arr(1,43,54,62,21,66,32,78,36,76,39); 1. 冒泡排序法   *     思路分析:法如其名,就是像冒

单向链表排序:快速排序和归并排序

归并排序改变链接 快速排序改变链接 快速排序改变节点值 所有源码和测试函数 对单向链表的排序有2种形式,只改变节点的值 和 只改变链接 // 节点 struct ListNode { int val; ListNode* next; ListNode(int v, ListNode* n = NULL) { val = v; next = n; } }; 本文链接:单向链表排序:快速排序和归并排序 参考资料链接: 链表排序(冒泡.选择.插入.快排.归并.希尔.堆排序): 1. 归并排序(改变链接

七大内部排序算法总结(插入排序、希尔排序、冒泡排序、简单选择排序、快速排序、归并排序、堆排序)

 写在前面: 排序是计算机程序设计中的一种重要操作,它的功能是将一个数据元素的任意序列,重新排列成一个按关键字有序的序列.因此排序掌握各种排序算法非常重要.对下面介绍的各个排序,我们假定所有排序的关键字都是整数.对传入函数的参数默认是已经检查好了的.只是简单的描述各个算法并给出了具体实现代码,并未做其他深究探讨. 基础知识: 由于待排序的记录数量不同,使得排序过程中设计的存储器不同,可将排序方法分为两大类:一类是内部排序,指的是待排序记录存放在计算机随机存储器中进行的排序过程.另一类是外部排序,

如何优化合并排序和快速排序

和并排序和快速排序在元素的重复率特别高的时候排序的时间变长.我们可以利用三向切分的办法来避免相同的元素进行交换,以减少交换次数. 具体如下图所示: 总共有3个指针,lt,i,和gt,这个三个指针分别指着队首,队首的下一位,队尾.以队首为参考点,设该数组为a.设中间变量temp. temp ← a[lt] //队首设为参考变量 if a[i] < temp:           swap(a,lt++,i++) else if a[i] > temp:           swap(a,i,j-

九种经典排序算法详解(冒泡排序,插入排序,选择排序,快速排序,归并排序,堆排序,计数排序,桶排序,基数排序)

综述 最近复习了各种排序算法,记录了一下学习总结和心得,希望对大家能有所帮助.本文介绍了冒泡排序.插入排序.选择排序.快速排序.归并排序.堆排序.计数排序.桶排序.基数排序9种经典的排序算法.针对每种排序算法分析了算法的主要思路,每个算法都附上了伪代码和C++实现. 算法分类 原地排序(in-place):没有使用辅助数据结构来存储中间结果的排序**算法. 非原地排序(not-in-place / out-of-place):使用了辅助数据结构来存储中间结果的排序算法 稳定排序:数列值(key)

8. 冒泡法排序和快速排序(基于openCV)

一.前言 主要讲述冒泡法排序和快速排序的基本流程,并给出代码实现,亲测可用. 二.冒泡法排序 冒泡法排序主要是将相邻两个值比较,把小的向前冒泡,大的向后沉淀,时间复杂度为O(n2).主要思想如下: 分为内外循环,每次外循环确定一个大的数据的具体位置,如下实例: 从图中可以看出,进过两次外循环就可以得到排序结果,随后的8次循环都浪费了,为了避免这种情况,我们可以设置一个状态参数,用来表示内循环是否发生数据的交换,从而作为外循环是否退出的信号. 三.快速排序 快速排序是最有效的排序方法之一,其主要思

插入排序 | 冒泡排序 | 希尔排序 | 堆排序 | 快速排序 | 选择排序 | 归并排序

以下是最近学习各种算法的代码实现: #include <stdlib.h> #include <stdio.h> #include <time.h> #include <limits.h> typedef int EleType; typedef int (*CompFunc)(void *,void *); int IntComp(void * a,void *b) { if(*(int *)a > *(int *)b) return 1; if(*