zoukankan      html  css  js  c++  java
  • 排序算法总结之快速排序

    一,快速排序介绍

    快速排序与归并排序一样,也是基于分治的递归算法,体现在:在每一趟快速排序中,需要选出枢轴元素,然后将比枢轴元素大的数组元素放在枢轴元素的右边,比枢轴元素小的数组元素都放在枢轴元素的左边。然后,再对分别对 枢轴元素左边 和 枢轴元素右边的元素进行快速排序。

    二,快速排序算法分析

     ①相比于直接插入排序,快排合适于数据量大(上百万)的情形,而插入排序适合于小数据量的情形。因为,在数据量小的情形下,快排的递归是需要一定的开销的。

    ②相比于归并排序,归并排序的比较次数要比快速排序少,但是它需要一个额外的临时数组,而且移动的元素多。而快速排序不需要显示地声明一个临时数组,它用的是递归栈。在C++中使用它来作为标准的排序程序,而JAVA中则是用归并排序来作为标准的排序(比如java.util.Arrays.java 中的sort(T[]) 方法使用的就是归并排序)。

    ③快速排序主要有两个基本操作:一是选取枢轴元素,另一个则是递归分割数组。枢轴元素的选取对快速排序的效率至关重要,因为它决定了递归的深度。如果枢轴元素刚好处于数组的中间值,那么,数组在分割时就分成了平均的两部分。这样的递归的效率就好。如果每次选取的枢轴元素都是最大/最小 的那个元素,则快排复杂度达到了O(N^2),而且还用了递归栈空间。

    ④枢轴元素的选取可以采用三数取中法。所谓三数取中法,即给定一个数组,选取数组中的第一个元素,最后一个元素,和中间那个元素。哪个元素的值位于中间,则它作为枢轴元素。比如,a[0]=5 , a[4]=1, a[9]=10,  那么  a[4]<a[0]<a[9]  ,故a[0]是枢轴元素。

    采用三数取中法时,会有一个问题,就是当数组不断的递归划分变小之后,枢轴左边的元素个数不足3,这样三数取中法就失去了应有的意义。此外,正如前面提到,对于小数组而言,插入排序反而比快速排序的效率更高。

    正是基于以上两个原因,我们可以将快速排序与插入排序结合。当递归划分的数组变小之后,达到某个值(CUTOFF)时,采用插入排序。(代码83行)

    三,快速排序算法实现

     1 public class QuickSort {
     2 
     3     private static final int CUTOFF = 10;
     4     
     5     
     6     /**
     7      * 
     8      * @param arr
     9      *            待排序的数组
    10      */
    11     public static <T extends Comparable<? super T>> void quickSort(T[] arr) {
    12         quickSort(arr, 0, arr.length - 1);
    13     }
    14 
    15     /**
    16      * 快排的基本操作:通过三数取中法来选取枢轴元素
    17      * 
    18      * @param arr
    19      *            在[left, right]之间选择pivot element
    20      * @param left
    21      *            index of arr to chose pivot element
    22      * @param right
    23      *            index of arr to chose pivot element
    24      * @return pivot element
    25      */
    26     private static <T extends Comparable<? super T>> T media3(T[] arr,
    27             int left, int right) {
    28 
    29         int center = (left + right) / 2;
    30         if (arr[center].compareTo(arr[left]) < 0)
    31             swapReference(arr, center, left);
    32         if (arr[right].compareTo(arr[left]) < 0)
    33             swapReference(arr, right, left);
    34 
    35         // 参考前面两个if比较之后,最小的元素被放置在
    36         // arr[left],然后下面再比较中间与最右的元素,将最大的元素放在arr[right],而arr[center]存放中间元素(pivot)
    37         if (arr[right].compareTo(arr[center]) < 0)
    38             swapReference(arr, right, center);
    39         
    40         swapReference(arr, center, right - 1);//将枢轴元素放在 arr[right-1]上.便于快排交换元素
    41         return arr[right - 1];
    42     }
    43 
    44     private static <T extends Comparable<? super T>> void swapReference(
    45             T[] arr, int from, int to) {
    46         T tmp = arr[from];
    47         arr[from] = arr[to];
    48         arr[to] = tmp;
    49     }
    50 
    51     /**
    52      * 实现了递归的快排主例程, internal quicksort method that makes recrusive calls
    53      * 
    54      * @param arr
    55      *            an array of comparable items
    56      * @param left
    57      *            the left-most index of subarray
    58      * @param right
    59      *            the right-most index of subarray
    60      */
    61     private static <T extends Comparable<? super T>> void quickSort(T[] arr,
    62             int left, int right) {
    63         if(left + CUTOFF <= right)
    64         {
    65             T pivot = media3(arr, left, right);
    66             
    67             //begin partitoning
    68             int i = left, j = right - 1;//在media3中已经将比pivot小的元素放到了a[left]上,把pivot放到了arr[right-1]上,故下面的while中是 ++i 和 --j
    69             for(;;)
    70             {
    71                 while(arr[++i].compareTo(pivot) < 0){}
    72                 while(arr[--j].compareTo(pivot) > 0){}
    73                 if(i < j)
    74                     swapReference(arr, i, j);
    75                 else
    76                     break;
    77             }
    78             swapReference(arr, i, right - 1);//restore pivot
    79             quickSort(arr, left, i - 1);
    80             quickSort(arr, i + 1, right);
    81         }else
    82             //Do an insertion sort on the subarray
    83             insertSort(arr, left, right);
    84     }
    85 }

    ①第3行CUTOFF定义当数组中元素个数为10以下时,采用插入排序。

    ②第11行的quickSort方法是快排对外提供的接口

    ③第26行的media3方法实现了三数取中选取枢轴元素。其实,它不仅仅返回了枢轴元素,它还改变了原数组:

    1) 它将三个数中最大的那个数放在了数组末尾arr[right]---(比枢轴小的放在枢轴元素左边)

    2) 它将三个数中最小的那个数放在了数组的开头arr[left]---(比枢轴大的放在枢轴元素右边)

    3) 它将三个数中的中间那个数(枢轴元素)放在了 arr[right-1]位置处!!!---(在第71-72行选取是否交换元素时可以不受枢轴影响)

    这是快排算法实现的技巧。

    ④第61行的quickSort方法则是实现快速递归分割的主例程。首先在第65行选取枢轴元素,第71行,在数组左边寻找比枢轴元素大的元素;第72行,在数组右边寻找比枢轴元素小的元素,第73-74行将之进行交换。可以看出,这些语句实现得非常精巧:在循环中只有自增和自减操作,以及判断语句,因此执行速度是很快的。

    在media3中已经将比pivot小的元素放到了a[left]上,把pivot放到了arr[right-1]上,故下面的while中是 ++i 和 --j
    ⑤第73行if语句 当 i > j 时 表示一趟快排已经结束,第78行将枢轴元素放到它的最终位置。对于快排而言,每进行一趟,枢轴元素的位置就被唯一确定下来,以后都不再变。

    ⑥第79 和 80行,对枢轴元素左右两个的子数组递归调用。这样,将原问题,划分成了两个子问题。

    可以写出它们的递归表达式:T(N)=T(i)+T(N-i-1)+O(N)

    ⑦当 快速排序划分的子数组足够小时(CUTOFF),不再采用快速排序,而是用插入排序。这样,进一步优化了快速排序的速度。

    用到的插入排序实现如下:

        private static <T extends Comparable<? super T>> void insertSort(T[] a, int left ,int right){
            for(int p = left + 1; p <= right; p++)
            {
                T tmp = a[p];//保存当前位置p的元素,其中[0,p-1]已经有序
                int j;
                for(j = p; j > 0 && tmp.compareTo(a[j-1]) < 0; j--)
                {
                        a[j] = a[j-1];//后移一位
                }
                a[j] = tmp;//插入到合适的位置
            }

    四,快速排序算法复杂度分析

    ①快速排序的时间复杂度与枢轴元素的选取息息相关。平均情况下,时间复杂度为O(NlogN),最坏情况下为O(N^2)

    ②枢轴元素的选取有多种方式:比如上面提到的三数取中,也可以采用随机化算法来选取。采用三数取中时,几乎不会出现最坏的情况。相对基于内存排序的其他算法,快速的效率是很高的,它的时间复杂度的常数因子很小。大约是1.3,而归并大约是1.4 ----不太确定。  1.3NlogN 

    ③快排用到了递归,当数组元素个数很少时,递归的开销就有点大了,故在程序中可将快排与插入排序结合起来。

    五,参考资料

    排序算法总结之插入排序

    排序算法总结之归并排序

    排序算法总结之堆排序

    各种排序算法的总结

  • 相关阅读:
    highcharts 时间少8小时问题
    Spring声明式事务配置管理方法
    jetty简介
    java事务管理
    oracle表中某个字段含有字符回车、空格的手动修改方式
    java环境变量配置
    JAVA解析XML的四种方式
    JSON-lib框架,JAVA对象与JSON、XML之间的相互转换
    Java WebService简单实例
    HTTP协议报文、工作原理及Java中的HTTP通信技术详解
  • 原文地址:https://www.cnblogs.com/hapjin/p/5518922.html
Copyright © 2011-2022 走看看