zoukankan      html  css  js  c++  java
  • jdk1.6与jdk1.7list集合排序区别与算法

    源码分析:

    Collections.sort中:

     
     public static <T extends Comparable<? super T>> void sort(List<T> list) {
            Object[] a = list.toArray();
            Arrays.sort(a);
            ListIterator<T> i = list.listIterator();
            for (int j=0; j<a.length; j++) {
                i.next();
                i.set((T)a[j]);
            }
        }

    可以发现,最终还是使用了Arrays.sort(a);的,不同的是:一个针对数组,一个针对集合

    扩展:不同版本的内部实现问题

    JDK1.6以下的时候:调用sort方法时,默认是使用mergeSort的算法
    JDK1.7后,使用TimSort的算法。源码如下:
    JDK7的sort方法:

     
    public static void sort(Object[] a) {  
            if (LegacyMergeSort.userRequested)  
                legacyMergeSort(a);  
            else  
                ComparableTimSort.sort(a);  
        }  
        /** To be removed in a future release. */  
        private static void legacyMergeSort(Object[] a) {  
            Object[] aux = a.clone();  
            mergeSort(aux, a, 0, a.length, 0);  
        }

    JDK6以下的sort方法:

     
    public static void sort(Object[] a) {  
            Object[] aux = (Object[])a.clone();  
            mergeSort(aux, a, 0, a.length, 0);  
        }

    当然可以使用下列的方式,在JDK7依旧使用mergeSort算法:

     
    System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");

    但是,从注释中可以发现,以后将让TimSort代替mergeSort

    根据这篇JDK7中的排序算法详解—Collections.sort和Arrays.sort找到了解决方法:

    而在Java 7中,内部实现换成了TimSort,其对对象间比较的实现要求更加严格:
    Comparator的实现必须保证以下几点(出自这儿)):

    1. sgn(compare(x, y)) == -sgn(compare(y, x))
    2. (compare(x, y)>0) && (compare(y, z)>0) 意味着 compare(x, z)>0
    3. compare(x, y)==0 意味着对于任意的z:sgn(compare(x, z))==sgn(compare(y, z)) 均成立

    所以,compare要相应的改成:

     
    public int compare(ComparatorTest o1, ComparatorTest o2) {
        return o1.getValue() == o2.getValue() ? 0 : 
                    (o1.getValue() > o2.getValue() ? 1 : -1);
    }

    先对相等的情况判断,再对大小的判断。

    算法分析

    既然这个算法比之前快排要快,那么肯定有它的巧妙之处,我们来仔细看看吧。

    算法步骤

    1.对于很小的数组(长度小于27),会使用插入排序。
    2.选择两个点P1,P2作为轴心,比如我们可以使用第一个元素和最后一个元素。
    3.P1必须比P2要小,否则将这两个元素交换,现在将整个数组分为四部分:
    (1)第一部分:比P1小的元素。
    (2)第二部分:比P1大但是比P2小的元素。
    (3)第三部分:比P2大的元素。
    (4)第四部分:尚未比较的部分。
    在开始比较前,除了轴点,其余元素几乎都在第四部分,直到比较完之后第四部分没有元素。
    4.从第四部分选出一个元素a[K],与两个轴心比较,然后放到第一二三部分中的一个。
    5.移动L,K,G指向。
    6.重复 4 5 步,直到第四部分没有元素。
    7.将P1与第一部分的最后一个元素交换。将P2与第三部分的第一个元素交换。
    8.递归的将第一二三部分排序。

    图表演示

    注:图片来自Vladimir Yaroslavskiy的论文。

    算法源码 

    1. //对外公开的两个sort方法  
    2. public static void sort(int[] a) {  
    3.     sort(a, 0, a.length);  
    4. }  
    5. public static void sort(int[] a, int fromIndex, int toIndex) {  
    6.     rangeCheck(a.length, fromIndex, toIndex);  
    7.     dualPivotQuicksort(a, fromIndex, toIndex - 1, 3);  
    8. }  
    9. //对数组的边界检测  
    10. private static void rangeCheck(int length, int fromIndex, int toIndex) {  
    11.     if (fromIndex > toIndex) {  
    12.         throw new IllegalArgumentException("fromIndex > toIndex");  
    13.     }  
    14.     if (fromIndex < 0) {  
    15.         throw new ArrayIndexOutOfBoundsException(fromIndex);  
    16.     }  
    17.     if (toIndex > length) {  
    18.         throw new ArrayIndexOutOfBoundsException(toIndex);  
    19.     }  
    20. }  
    21. //交换数组中两个元素  
    22. private static void swap(int[] a, int i, int j) {  
    23.     int temp = a[i];  
    24.     a[i] = a[j];  
    25.     a[j] = temp;  
    26. }  
    27. /** 
    28.  * 双轴快排的具体实现 
    29.  * @param a     待排序数组 
    30.  * @param left  数组需排序上界 
    31.  * @param right 数组需排序下界 
    32.  * @param div   理解为从何位置取轴 
    33.  */  
    34. private static void dualPivotQuicksort(int[] a, int left,int right, int div) {  
    35.     int len = right - left;  
    36.     //数组长度如果很小(27),则直接用插入排序对其排序  
    37.     if (len < 27) {  
    38.         for (int i = left + 1; i <= right; i++) {  
    39.             for (int j = i; j > left && a[j] < a[j - 1]; j--) {  
    40.                 swap(a, j, j - 1);  
    41.             }  
    42.         }  
    43.         return;  
    44.     }  
    45.     //取到位于1/div和div-1/div位置的点,并用他们来做轴  
    46.     int third = len / div;  
    47.     int m1 = left + third;  
    48.     int m2 = right - third;  
    49.     if (m1 <= left) {  
    50.         m1 = left + 1;  
    51.     }  
    52.     if (m2 >= right) {  
    53.         m2 = right - 1;  
    54.     }  
    55.     //确保left是小的,right是大的  
    56.     if (a[m1] < a[m2]) {  
    57.         swap(a, m1, left);  
    58.         swap(a, m2, right);  
    59.     }  
    60.     else {  
    61.         swap(a, m1, right);  
    62.         swap(a, m2, left);  
    63.     }  
    64.     // 两个轴  
    65.     int pivot1 = a[left];  
    66.     int pivot2 = a[right];  
    67.     // 代表比p1小和比p2大的两个指针  
    68.     int less = left + 1;  
    69.     int great = right - 1;  
    70.     // 开始取出less到great之间的未知大小数据,与两个轴比较  
    71.     // 并且将数据放入正确的区域后调整各个指针  
    72.     for (int k = less; k <= great; k++) {  
    73.         //如果取出的数比p1小,那么直接到less左侧,并且less右移  
    74.         if (a[k] < pivot1) {  
    75.             swap(a, k, less++);  
    76.         }   
    77.         //如果取出的数比p2大,那么首先确定great左侧没有比p2大的数  
    78.         //然后与great位置的数字交换,great左移  
    79.         //此时,great交换的数字肯定是比p2小或者相等的(首先确定过)  
    80.         //那么此时再与p1相比,处理这个数的区间  
    81.         else if (a[k] > pivot2) {  
    82.             while (k < great && a[great] > pivot2) {  
    83.                 great--;  
    84.             }  
    85.             swap(a, k, great--);  
    86.             if (a[k] < pivot1) {  
    87.                 swap(a, k, less++);  
    88.             }  
    89.         }  
    90.         //如果这个数比p1大但是比p2小,则不需要交换,只需将k指针右移  
    91.     }  
    92.     //将p1与less左侧的第一个数交换  
    93.     swap(a, less - 1, left);  
    94.     //将p2与great右侧的第一个数交换  
    95.     swap(a, great + 1, right);  
    96.     // 计算出在两轴大小之间的个数  
    97.     int dist = great - less;  
    98.     //如果这个数很小(13),那么取轴的点向两边偏  
    99.     if (dist < 13) {  
    100.         div++;  
    101.     }  
    102.     // 对三个子区间分别排序,因为less-1和great+1是轴,已经排好了序  
    103.     // 所以不需要比较  
    104.     dualPivotQuicksort(a, left, less - 2, div);  
    105.     dualPivotQuicksort(a, great + 2, right, div);  
    106.     // 如果在中间区间的数字很多,那么排除掉一些相等的元素再进行排序  
    107.     if (dist > len - 13 && pivot1 != pivot2) {  
    108.         for (int k = less; k <= great; k++) {  
    109.             if (a[k] == pivot1) {  
    110.                 swap(a, k, less++);  
    111.             }  
    112.             else if (a[k] == pivot2) {  
    113.                 swap(a, k, great--);  
    114.                 if (a[k] == pivot1) {  
    115.                     swap(a, k, less++);  
    116.                 }  
    117.             }  
    118.         }  
    119.     }  
    120.     // 对中间的区间排序  
    121.     if (pivot1 < pivot2) {  
    122.         dualPivotQuicksort(a, less, great, div);  
    123.     }  
    124. }  


    总结

    双轴排序利用了区间相邻的特性,对原本的快排进行了效率上的提高,很大程度上是利用了数学的一些特性,果然,算法跟数学还是息息相关的吖。
  • 相关阅读:
    BluetoothLE-Multi-Library
    android BLE Peripheral 手机模拟设备发出BLE广播 BluetoothLeAdvertiser
    android布局中使用include及需注意点
    保留最新N份备份目录脚本
    MySQL 针对Swap分区的运维注意点
    Samba共享目录的多用户权限设置案例
    Redis常见问题和解决办法梳理
    Redis日常操作命令小结
    Redis数据"丢失"讨论及规避和解决的几点总结
    confluence与jira账号对接、查看到期时间及问题总结
  • 原文地址:https://www.cnblogs.com/wangbenqing/p/7536220.html
Copyright © 2011-2022 走看看