【JDK優化】java.util.Arrays的排序研究

【java.uti.Arrays】 包含用來操做數組(好比排序和搜索)的各類方法。這篇文章咱們就來研究一些大師們寫的排序算法。java


(1) 基本數據類型數組的排序,如Arrays.sort(int[])等。採用了一種經 調優的快速排序  該算法改編自 Jon L. Bentley 和 M. Douglas McIlroy 合著的 Engineering a Sort Function", Software-Practice and Experience Vol. 23(11) P. 1249-1265 (November 1993)。此算法在許多數據集上提供 n*log(n) 性能,這致使其餘快速排序會下降二次型性能。算法


下面是JDK中調優快速排序算法的源代碼數組

   /**
     * 將指定範圍的整形數組升序排序。
     * x[] 待排數組
     * off 從數組的第off個元素開始排序
     * len 數組長度
     */
    private static void sort1(int x[], int off, int len) {
	//優化1:在小規模(size<7)數組中,直接插入排序的效率要比快速排序高。
	if (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;
	}

	//優化2:精心選擇劃分元素,即樞軸
	//若是是小規模數組(size<=7),直接取中間元素做爲樞軸
	//若是是中等規模數組(7=<size<=40),則在數組首、中、尾三個位置上的數中取中間大小的數做爲樞軸
	//若是是大規模數組(size>40),則在9個指定的數中取一個僞中數(中間大小的數s)
	int m = off + (len >> 1);
	if (len > 7) { 
	    int l = off;
	    int n = off + len - 1;
	    if (len > 40) {        
			int 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);
	}
	int v = x[m];

         //優化3:每一次樞軸v的劃分,都會造成造成一個形如  (<v)* v* (>v)*
        //階段一,造成 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--);
	}

	//階段二,將樞軸和與樞軸相等的元素交換到數組中間
	int 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);

	//階段三,遞歸排序與樞軸不相等都元素區間
	if ((s = b-a) > 1)
	    sort1(x, off, s);
	if ((s = d-c) > 1)
	    sort1(x, n-s, s);
    }


★ 優化1:在小規模(size<7)數組中,直接插入排序的效率要比快速排序高。性能

      沒有一種排序在任何狀況下都是最優的《基於比較的內部排序總結 》。 O(N^2)級別的排序看起來彷佛比全部先進排序要差的多。但實際上也並不是如此,Arrays中的sort()算法就給了咱們一個很好的例子。當待排數組規模很是小的時候(JDK中規模的閾值爲INSERTIONSORT_THRESHOLD=7),直接插入排序反而要比快排,歸併排序要好。優化

           這個道理很簡單。數組規模小,簡單算法的比較次數不會比先進算法多多少。相反,諸如快排,歸併排序等先進算法使用遞歸操做,所付出的運行代價更高。spa

 

★ 優化2:精心選擇劃分元素,即樞軸。code

      快排有一種最差的狀況,即蛻化成效率最差的起跑排序(見《 交換排序 》)。 致使這種狀況產生的主要緣由就是樞軸的選擇並不能把整個數組劃分紅兩個大體相等的部分。好比對於基本有序的數組,選擇第一個元素做爲樞軸就會產生這種蛻化。對象

      既然如此,咱們能夠看看Arryas.sort()是如何爲咱們選擇樞軸的。blog

      ● 若是是小規模數組(size<=7),直接取中間元素做爲樞軸。      排序

      ● 若是是中等規模數組(7=<size<=40),則在數組首、中、尾三個位置上的數中取中間大小的數做爲樞軸
      ● 若是是大規模數組(size>40),則在9個指定的數中取一個僞中數(中間大小的數s)

      中小規模時,這種取法儘可能能夠避免數組的較小數或者較大數成爲樞軸。值得一提的是大規模的時候,首先在數組中尋找9個數據(能夠經過源代碼發現這9個數據的位置較爲平均的分佈在整個數組上);而後每3個數據找中位數;最後在3箇中位數上再找出一箇中位數做爲樞軸。

      仔細想一想,這種精心選擇的樞軸,使得快排的最差狀況成爲了極小機率事件了。

 

★ 優化3:根據樞軸v劃分,造成一個形如  (<v)*   v* (>v)* 的數組

      普通快排算法,都是使得樞軸元素移動到數組的較中間位置。樞軸以前的元素所有小於或等於樞軸,以後的元素所有大於樞軸。但與樞軸相等的元素並不能移動到樞軸附近位置。這一點在Arrays.sort()算法中有很大的優化。

      咱們舉個例子來講明Arrays的優化細節       1五、9三、1五、4一、六、1五、2二、七、1五、20

      第一次樞軸:v=15

      階段一,造成 v* (<v)* (>v)* v* 的數組:

                                          1五、1五、 七、六、 4一、20、2二、9三、 1五、15

                  咱們發現,與樞軸相等的元素都移動到了數組的兩邊。而比樞軸小的元素和比樞軸大的元素也都區分開來了。

      階段二,將樞軸和與樞軸相等的元素交換到數組中間的位置上

                                          七、六、 1五、1五、 1五、1五、 4一、20、2二、93

      階段三,遞歸排序與樞軸不相等都元素區間{七、6}和{4一、20、2二、93}

      仔細想一想,對於重複元素較多的數組,這種優化無疑能到達更好的效率。

 

 

(1) 對象數組的排序,如Arrays.sort(Object[])等。採用了一種經 修改的歸併排序  其也有幾個優化的閃光點。

     下面是JDK中改進歸併排序算法的源代碼:

    /**
     * 將指定範圍的對象數組按天然順序升序排序。
     * src[] 原待排數組
     * dest[] 目的待排數組
     * low 待排數組的下界位置
     * high 待排數組的上界位置
     * off 從數組的第off個元素開始排序
     */    
    private static void mergeSort(Object[] src,
				  Object[] dest,
				  int low,
				  int high,
				  int off) {
	int length = high - low;

	//優化1:規模很小的數組的排序,直接插入排序的效率反而比歸併要高。
	//規模定在INSERTIONSORT_THRESHOLD=7以內
        if (length < INSERTIONSORT_THRESHOLD) {
            for (int i=low; i<high; i++)
                for (int j=i; j>low &&
			 ((Comparable) dest[j-1]).compareTo(dest[j])>0; j--)
                    swap(dest, j, j-1);
            return;
        }

        // 遞歸排序dest的一半元素並賦值給src
        int destLow  = low;
        int destHigh = high;
        low  += off;
        high += off;
        int mid = (low + high) >> 1;
        mergeSort(dest, src, low, mid, -off);
        mergeSort(dest, src, mid, high, -off);

        //優化2:若是低子列表中的最高元素小於高子列表中的最低元素,則忽略合併        
        //若是須要歸併的兩端low~(middle-1),middle~high已經有序,即src[mid-1]==src[mid]。
        //那麼只須要將src的low~high賦值對應的dest便可,無需再歸併。
        if (((Comparable)src[mid-1]).compareTo(src[mid]) <= 0) {
            System.arraycopy(src, low, dest, destLow, length);
            return;
        }

        //將src的兩個部分合並,並賦值給dest
        for(int i = destLow, p = low, q = mid; i < destHigh; i++) {
            if (q >= high || p < mid && ((Comparable)src[p]).compareTo(src[q])<=0)
                dest[i] = src[p++];
            else
                dest[i] = src[q++];
        }
    }

★ 優化1: 同上面的快速排序


★ 優化2: 若是低子列表中的最高元素小於高子列表中的最低元素,則忽略合併。 這個優化措施無疑對基本有序序列是極大的效率改進。

相關文章
相關標籤/搜索