java源碼中的數據結構 - 插入排序,快速排序 (附可供調試的源碼)

java源碼是學習數據結構的好材料,研究這些代碼,能夠更好的理解算法。

[size=large][b]準備工作[/b][/size]
java.util.Arrays是一個典型的工具類(構造函數修飾符爲private),該類提供了一組sort1()方法,分別用來可以比較的基本類型進行排序。
private static void sort1(int x[], int off, int len)
private static void sort1(long x[], int off, int len)
private static void sort1(byte x[], int off, int len)
private static void sort1(float x[], int off, int len)
...

仔細比較發現,這組方法除了傳入數組的參數類型不同,方法內的代碼幾乎完全相同,產生這個問題的主要原因是jdk沒有爲基本類型提供泛型支持,考慮到效率問題,裝箱在這裏顯然是不合適的。

在Arrays類中包含大量重複代碼的方法不只sort1,還有swap,binarySearch等,不過這裏確實也沒有別的好方法,只能希望以後的版本編譯器能夠提供支持。

另外,這裏還有一個需要說明的地方是double和float的比較,爲了處理類似NaN, 0.0這樣的情況,正確的比較方法應該是:

Double.compare(double1, doble2);
Float.compare(float1, float2);


而在Arrays類中,爲了保證sort1的一致性,對double和float類型數組,先使用sort2方法把NaN, 0.0這樣的數移到數組最後,再使用sort1方法對剩餘部分排序,這部分功能可以參考sort2方法的源碼。
private static void sort2(double a[], int fromIndex, int toIndex)
private static void sort2(float a[], int fromIndex, int toIndex)


[size=large][b]算法分析[/b][/size]
準備工作做完了,現在開始對算法本身進行分析,簡便起見,只分析下面這個方法:
private static void sort1(int x[], int off, int len)


[b]1 數組長度小於7時,使用插入排序[/b]

// The part of code in sort1 method
// Insertion sort on smallest arrays
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;
}
說明:
印象中有本書說len<20時插入排序比較快,jdk選擇的是len<7。
swap()方法用來交換數組中的兩個變量。

[b]2 數組長度大於等於7時,使用快速排序[/b]
快速排序的原理是首先選取一個元素v,然後把比v大和比v小的元素分別移到v的兩邊,最後再對v兩邊的數組分別進行排序。
這裏使用的是平衡快排,即選擇合適的v,使v儘量位於數組中間值,看看jdk怎麼做的:

// The part of code in sort1 method
// Choose a partition element, v
int m = off + (len >> 1); // Small arrays, middle element
if (len > 7) {
int l = off;
int n = off + len - 1;
if (len > 40) { // Big arrays, pseudomedian of 9
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); // Mid-size, med of 3
}
int v = x[m];

從代碼可以看出,jdk分了三種情況:
[b]2.1 len = 7[/b]
v取的是數組中間元素(x[3])

[b]2.2 len > 7 && len <= 40[/b]
看看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));
}

也就是取數組中三個位置值中處於中間的值。
所以這種情況v的取值是: 首,尾,中間元素中處於中間的值。

[b]2.3 len > 40[/b]
仔細分析代碼,可以發現,jdk取數組0, n/8, n/4, 3n/8, n/2, 5n/8, 3n/4, 7n/8, n位置的元素值進行運算,這個算法設計的非常巧妙 :idea: ,首先步長s=len/8,向右移三位就行了,速度快;其次,通過步長從數組中得到9個點,每3個爲一組進行med3元素,產生的3個點再進行一次med3運算,就得到了v值,這樣保證v的值儘可能的處於中間的值。

[size=large][b]總結:[/b][/size]
jdk中sort1排序使用了兩種排序算法,並且根據數組的長度做了相應的優化,這是一個工程中比較實用的算法(看註釋可以找到算法來源)。另外,附件是從jdk源碼中拷貝出來的相關源碼,有興趣的可以自己研究。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章