java中Arrays.sort()的源码分析

来源:互联网 发布:centos压缩文件夹 编辑:程序博客网 时间:2024/06/08 20:06

今天刷算法题,遇到一个排序类题目:largest-number,想了一个晚上加一个早上,终于AC了,看到别人的代码很简洁,瞬间觉得自己写的很low,不过有很多用java写的解法都用到了Arrays.sort()方法,之前用的很少,所以一直没怎么注意这个知识点。

于是决定看下这个方法的源码到底是如何进行排序的。

以整形数组为例进行分析,其他原始数据类型基本相似。

在java.util.Arrays类下

找到整形数组为参数的排序函数入口:

public static void sort(int[] a, int fromIndex, int toIndex) {        rangeCheck(a.length, fromIndex, toIndex);sort1(a, fromIndex, toIndex-fromIndex);}

rangeCheck方法:

private static void rangeCheck(int arrayLen, int fromIndex, int toIndex) {        if (fromIndex > toIndex)            throw new IllegalArgumentException("fromIndex(" + fromIndex +                       ") > toIndex(" + toIndex+")");        if (fromIndex < 0)            throw new ArrayIndexOutOfBoundsException(fromIndex);        if (toIndex > arrayLen)            throw new ArrayIndexOutOfBoundsException(toIndex); }
其中rangeCheck方法用于检查fromIndex到toIndex范围在数组a的范围内,否则将抛出异常

sort1代码如下(分割几个部分介绍):

 private static void sort1(int x[], int off, int len) {// Insertion sort on smallest arraysif (len < 7) {    for (int i=off; i<len+off; i++)for (int j=i; j>off && x[j-1]>x[j]; j--)    swap(x, j, j-1);    return;         }          ... ...

开头首先对长度进行判断,即当数组长度len小于7时,直接采用插入排序方法完成,时间复杂度为O(n^2),由于数组比较小,因此这个时间复杂度是可以接受的

// Choose a partition element, vint m = off + (len >> 1);       // Small arrays, middle elementif (len > 7) {    int l = off;    int n = off + len - 1;    if (len > 40) {        // Big arrays, pseudomedian of 9int s = len/8;l = med3(x, l,     l+s, l+2*s);m = med3(x, m-s,   m,   m+s);n = med3(x, n-2*s, n-s, n);    }    m = med3(x, l, m, n); // Mid-size, med of 3}int v = x[m];// Establish Invariant: v* (<v)* (>v)* v*int a = off, b = a, c = off + len - 1, d = c;while(true) {    while (b <= c && x[b] <= v) {if (x[b] == v)    swap(x, a++, b);b++;    }    while (c >= b && x[c] >= v) {if (x[c] == v)    swap(x, c, d--);c--;    }    if (b > c)break;    swap(x, b++, c--);}        ... ...
在数组长度大于7时,进行进一步处理,在数组的第一、中间、最后的元素中取得中间值作为后续排序的枢轴,方法里边多了一个 if (len > 40)判断,当大于40时,即把
整个数组分为三段:前段、中段、后段。每段又取第一、中间和最后一个元素进行筛选中间值,这样就求得了整个数组九个采样点的中间值,之所以除以8是因为要取三段,每段又分三点,所以一共九个点,对于一条线来说,9个点间有8段,所以除以8。

求中间值函数med3()代码:

private static int med3(int x[], int a, int b, int c) {return (x[a] < x[b] ?(x[b] < x[c] ? b : x[a] < x[c] ? c : a) :(x[b] > x[c] ? b : x[a] > x[c] ? c : a));}
取完枢轴数后,就需要进行下一步排序了,可以看到while代码块中就是典型的快速插入排序,一趟快排的结果如下图:


黄色区域表示元素值等于v枢轴值的各元素,蓝色区域表示值小于枢轴v值的各元素,绿色区域表示值大于枢轴v值的各元素。

此时还需对一趟排序的结果进行调整:

        // Swap partition elements back to middleint s, n = off + len;s = Math.min(a-off, b-a  );  vecswap(x, off, b-s, s);s = Math.min(d-c,   n-d-1);  vecswap(x, b,   n-s, s);// Recursively sort non-partition-elementsif ((s = b-a) > 1)    sort1(x, off, s);if ((s = d-c) > 1)    sort1(x, n-s, s);   }   /**     * Swaps x[a] with x[b].     */    private static void swap(int x[], int a, int b) {    int t = x[a];    x[a] = x[b];    x[b] = t;    }    /**     * Swaps x[a .. (a+n-1)] with x[b .. (b+n-1)].     */    private static void vecswap(int x[], int a, int b, int n) {    for (int i=0; i<n; i++, a++, b++)        swap(x, a, b);    }

分别对上图中的四个区域分前后两部分,各挑出最小的长度作为调整参数,将黄色区域集中在中间,元素值小于枢轴v值的蓝色区域移至前边,元素值大于枢轴v值的蓝色区域移至后边,上述代码执行后的结果如下:


调整好后整个数组按块有序,即元素值 蓝色 < 黄色< 绿色,但此时蓝色和绿色区域内部元素还没有排好序,因此要分别对蓝色区域和绿色区域的内部元素进行再一次的快速排序,整个排序过程使用递归方式,最终完成整个数组的排序。整个排序时间复杂度为O(nlogn)。

其他类型数据,如long,double 类型等排序过程类似。


还有一种函数是 sort(T[] a, Comparator<? super T> c)使用了自定义的实现Comparator接口的实例作为判断大小的比较器,可以利用这种自定义的比较器实现自定义类的实例间的比较功能。

函数入口:

public static <T> void sort(T[] a, int fromIndex, int toIndex, Comparator<? super T> c) {        rangeCheck(a.length, fromIndex, toIndex);T[] aux = (T[])copyOfRange(a, fromIndex, toIndex);        if (c==null)            mergeSort(aux, a, fromIndex, toIndex, -fromIndex);        else            mergeSort(aux, a, fromIndex, toIndex, -fromIndex, c); }

与上面类似,先通过起始终止地址来检查是否在实例数组a的有效范围内,然后根据是否传递Comparator比较器来选择排序函数。

没有传递自定义比较器c时将采用自定义类型的默认compareTo()方法,但要求使用的自定义类需要实现java.lang.Comparable接口,才能使用不指定比较器的方法。否则使用没有实现java.lang.Comparable接口的类且调用了不带比较器的sort方法将报错。

当指定了自定义的比较器时:

 public static <T> void sort(T[] a, int fromIndex, int toIndex, Comparator<? super T> c) {        rangeCheck(a.length, fromIndex, toIndex);T[] aux = (T[])copyOfRange(a, fromIndex, toIndex);        if (c==null)            mergeSort(aux, a, fromIndex, toIndex, -fromIndex);        else            mergeSort(aux, a, fromIndex, toIndex, -fromIndex, c);}
aux是复制实例数组a的新数组


private static void mergeSort(Object[] src,  Object[] dest,  int low, int high, int off,  Comparator c) {int length = high - low;// Insertion sort on smallest arraysif (length < INSERTIONSORT_THRESHOLD) {    for (int i=low; i<high; i++)for (int j=i; j>low && c.compare(dest[j-1], dest[j])>0; j--)    swap(dest, j, j-1);    return;}

排序开始时,判断length是否小于INSERTIONSORT_THRESHOLD = 7,若是则采用插入排序,与之前的原始类型的排序方法一样。

        int destLow  = low;        int destHigh = high;        low  += off;        high += off;        int mid = (low + high) >>> 1;        mergeSort(dest, src, low, mid, -off, c);        mergeSort(dest, src, mid, high, -off, c);        // If list is already sorted, just copy from src to dest.  This is an        // optimization that results in faster sorts for nearly ordered lists.        if (c.compare(src[mid-1], src[mid]) <= 0) {           System.arraycopy(src, low, dest, destLow, length);           return;        }        // Merge sorted halves (now in src) into dest        for(int i = destLow, p = low, q = mid; i < destHigh; i++) {            if (q >= high || p < mid && c.compare(src[p], src[q]) <= 0)                dest[i] = src[p++];            else                dest[i] = src[q++];        }    }

从代码中可以看到这里使用的是归并排序,先求出中间值mid,然后分为前后相同大小的两部分对数组再次求更小范围内的排序。中间的判断语句块意思是在前后半部分都排好序的情况下,即low -- mid,mid+1 -- high两部分都排好序下,src[mid]是后半部分最小值,src[mid-1]是前半部分最大值,前后半部分也都已排好序,因此整个low到high的数组是排好序的,当然无需任何操作直接返回即可。

最下面的for循环则是对这两个排好序的数组做合并操作。整个数组排序时间复杂度O(nlogn)。


以上内容是个人对这几个排序函数的一些理解,

不过至于为什么原始类型采用的快速排序,而对象类型则采用归并排序,还不太清楚。希望有人能够帮忙解答这个问题~~







0 0
原创粉丝点击