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 

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

  • 相关阅读:
    C语言-if语句
    C语言-表达式
    C语言-基础
    Java for LeetCode 187 Repeated DNA Sequences
    Java for LeetCode 179 Largest Number
    Java for LeetCode 174 Dungeon Game
    Java for LeetCode 173 Binary Search Tree Iterator
    Java for LeetCode 172 Factorial Trailing Zeroes
    Java for LeetCode 171 Excel Sheet Column Number
    Java for LeetCode 169 Majority Element
  • 原文地址:https://www.cnblogs.com/deolin/p/6832398.html
Copyright © 2011-2022 走看看