zoukankan      html  css  js  c++  java
  • 优化版快速排序

    在原始的快速排序上追加了两个点。

    • 引入了“分界点”,但递归进行到后期,数组足够小时,用插入排序(InsertionSort)效率更好
    • 准基元素并不是单纯的选择数组的第0位,而是使用三数取中法求出基准元素
      1 /**
      2  * 快速排序(三数取中法 + 插入排序优化)
      3  */
      4 public class QuickSort<T extends Comparable<? super T>> {
      5 
      6     /**
      7      * 分界点
      8      * 数字5~20皆可,这里选择了数字10
      9      */
     10     private static final int CUTOFF = 10; 
     11 
     12     /** 
     13      * 入口方法
     14      * 参数个数仅一个,对外友好
     15      */
     16     public void sort(T[] arr) {
     17         sort(arr, 0, arr.length - 1);
     18     }
     19 
     20     /**
     21      * 快速排序
     22      * 此方法会不断递归自身,一次递归代表一趟排序
     23      */
     24     private void sort(T[] arr, int left, int right) {
     25         // 数组长度在10以上时,使用快速排序更合适,否则使用插入排序
     26         if (left + CUTOFF < right) {
     27             // 算出基准元素
     28             T pivot = median3(arr, left, right);
     29             // 定义游标i, j,由于最左和最右的元素经过三数取中法之后
     30             // 已经确保了前者小于基准元素,后者大于基准元素
     31             // 所以游标应该越过他们
     32             int i = left;
     33             int j = right - 1;
     34             // 游标开始移动,直到i不在j的左侧
     35             for(;;) {
     36                 // 游标i不断向右侧移动,直到所指向的元素不再小于基准元素,才停止
     37                 while (arr[++i].compareTo(pivot) < 0) {}
     38                 // 游标j不断向左侧移动,直到所指向的元素不再大于基准元素,才停止
     39                 while (arr[--j].compareTo(pivot) > 0) {}
     40                 // i还在j的左侧,而且游标各自指向一个小于基准和大于基准的元素,
     41                 if (i < j) {
     42                     // 大于基准的元素换到右侧,小于基准的元素换到左侧
     43                     swapRef(arr, i, j);
     44                 // 这种情况指的是i, j擦身而过才停止,那么i已经进入了j曾走过的路径,j也如此,
     45                 // 说明小于基准元素的元素和大于基准元素的元素已经分离,分离点正是i
     46                 } else {
     47                     // 既然已经分离了,那么游标的工作就结束了
     48                     break;
     49                 }
     50             }
     51             // 将i指向的元素,和基准元素互换,达到以下示意的效果
     52             // [  *小于基准的元素们*  ], [基准元素], [  *大于基准的元素们*  ]
     53             swapRef(arr, i, right - 1);
     54             // 以基准元素为分割,前后分治
     55             sort(arr, left, i - 1);
     56             sort(arr, i + 1, right);
     57         } else {
     58             insertionSort(arr, left, right);
     59         }
     60     }
     61 
     62     /** 插入排序 */
     63     private void insertionSort(T[] arr, int left, int right) {
     64         // 声明一下索引j,应该循环外要用到。
     65         int j;
     66         // 这里一次循环代表选择排序一趟
     67         // 选择排序的核心表示第i趟确保从头至第i号元素顺序是正确的
     68         // 所以应该从从头开始的left+1排序到right
     69         // 而i = left那趟是没有必要的,因为只有一个元素
     70         for (int i = left + 1; i <= right; i++) {    // 这里犯了一次错,i=right的情况也要考虑到,否则最后那个元素的那趟就漏过去了。
     71             // 每一趟都会比上一趟多一个元素,它希望去“插队”,插入合适的位置
     72             T selected = arr[i];
     73             // 找位置中……(并准备插队)
     74             for (j = i; j > 0 && arr[j - 1].compareTo(selected) > 0; j--) {
     75                 // 准备插队的过程中,比插队元素大的元素应该往后走一位,效果就是一个“空位”不断向左移动。
     76                 arr[j] = arr[j - 1];
     77             }
     78             // “空位”停止了移动,因为“空位”前方的元素比插队元素小,
     79             // 而“空位”后方也是比插队元素大的最后一个元素,
     80             // 插队元素完成了插队,该趟插入排序结束
     81             arr[j] = selected;
     82         }
     83     }
     84 
     85     /** 三分法取基准元素 */
     86     private T median3(T[] arr, int left, int right) {
     87         int center = (left + right) / 2;
     88         // 1、确保最左元素小于中间的元素
     89         if (arr[left].compareTo(arr[center]) > 0) {
     90             swapRef(arr, left, center);
     91         }
     92         // 2、确保最左元素小于最右元素(结合1、2,最左元素是最小的了)
     93         if (arr[left].compareTo(arr[right]) > 0) {
     94             swapRef(arr, left, right);
     95         }
     96         // 3:确保中间元素大于最右元素(结合2、3,最右元素是最大的了)(结合2、3括号里的内容,中间元素是中间值)
     97         if (arr[center].compareTo(arr[right]) > 0) {
     98             swapRef(arr, center, right);
     99         }
    100         // 基准元素移动到倒数第二位,为了不阻挡后续游标i, j的移动。 
    101         swapRef(arr, center, right - 1);
    102         return arr[right - 1];
    103     }
    104 
    105     /** 元素引用交换 */
    106     private void swapRef(T[] arr, int index, int indexEx) {
    107         T temp = arr[index];
    108         arr[index] = arr[indexEx];
    109         arr[indexEx] = temp;
    110     }
    111 
    112 }

    测试一下

        public static void main(String[] args) {
            // 生成一个长度为100,元素在0~50随机分布的列表
            Random random = new Random();
            List<Integer> list = new ArrayList<Integer>();
            for (int i = 0; i < 100; i++) {
                list.add(random.nextInt(50));
            }
            System.out.println(list);
    
            Integer[] arr = list.toArray(new Integer[0]);
            QuickSort<Integer> sorter = new QuickSort<Integer>();
            sorter.sort(arr);
            for (Integer ele : arr) {
                System.out.print(ele + " ");
            }
        }

    结果

    [23, 11, 40, 30, 17, 27, 23, 11, 7, 44, 48, 34, 13, 8, 39, 44, 12, 7, 8, 18, 0, 34, 1, 2, 4, 9, 30, 4, 21, 22, 33, 0, 6, 3, 33, 20, 5, 13, 30, 8, 38, 45, 39, 10, 28, 39, 17, 24, 45, 37, 48, 4, 20, 20, 24, 47, 32, 23, 7, 18, 13, 22, 47, 28, 41, 5, 26, 10, 3, 32, 30, 14, 12, 31, 3, 18, 26, 6, 17, 40, 6, 1, 47, 36, 14, 46, 8, 5, 34, 8, 4, 2, 18, 40, 28, 1, 0, 47, 34, 21]
    0 0 0 1 1 1 2 2 3 3 3 4 4 4 4 5 5 5 6 6 6 7 7 7 8 8 8 8 8 9 10 10 11 11 12 12 13 13 13 14 14 17 17 17 18 18 18 18 20 20 20 21 21 22 22 23 23 23 24 24 26 26 27 28 28 28 30 30 30 30 31 32 32 33 33 34 34 34 34 36 37 38 39 39 39 40 40 40 41 44 44 45 45 46 47 47 47 47 48 48 

    即便有重复元素,也没有出错

  • 相关阅读:
    $dp$模板
    字符串基础
    基础算法

    图论
    山中无甲子,寒尽不知年
    模板集合(持续更新)
    数学基础——同余
    9.19 考试总结
    1-5-17:菲波那契数列
  • 原文地址:https://www.cnblogs.com/deolin/p/6832398.html
Copyright © 2011-2022 走看看