排序是將一組對象按照某個邏輯順序重新排列的過程。現在計算機的廣泛使用使得數據無處不在,整理數據就變得非常重要了。而整理數據的第一步往往都是進行排序。
簡單排序
冒泡排序
原理:
將所有臨近的兩個的對象按照某個邏輯一一進行比較,通常是由大到小或小到大,比較之後交換兩個對象的位置。
反覆操作,最終即可排序成功。
如上圖所示,我們發現,進行一次循環,冒泡排序只能確定一個對象的位置。第一次循環只能夠確定一個對象的位置。所以我們需要多少次循環呢?我們就可以的得出,我們總共需要n-1次循環。因爲n-1次循環能確定n-1個對象的位置,這時候自然最後一個對象的位置也確定了。
代碼如下:
public static void sort(Comparable[] a) {
for (int i = 0; i < a.length - 1; i++) {
for (int j = 0; j < a.length - 1; j++) {
if (less(a[j + 1], a[j])) {
swap(a, j, j + 1);
}
}
}
}
實際也就是依靠兩層循環,外層控制循環次數,內層逐個進行比較。
冒泡排序的優點就是簡單,空間複雜度低。缺點就是慢!無用功多。
時間複雜度O(n^2)
選擇排序
原理:
首先找到當前數組中最小的元素,將這個元素和第一個元素進行交換位置。其次,在剩下的數組中找到最小的元素,將它和數組中第二個元素進行位置的交換。依次類推,就可以達到排序的效果。簡單來說就是每次都選最小的那個,放在前面。
代碼:
public static void sort(Comparable[] a) {
for (int i = 0; i < a.length - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < a.length; j++) {
if (less(a[j], a[minIndex])) {
minIndex = j;
}
}
swap(a, minIndex, i);
}
}
選擇排序將數組分成兩個部分,已經排序好的和未排序兩部分。每次都從未排序部分中找到最小的元素,增加到排好序的末尾。
同樣的,外層循環控制次數,內層循環找到最小的元素。外層循環,因爲只要找到n-1個元素的位置,第n個元素的位置就直接確定了。所以外層循環需要n-1次。我個人認爲選擇排序是最簡單理解的排序之一。
時間複雜度O(n^2)
插入排序
插入排序就和平常打撲克牌一樣。整理撲克牌的一般使用的就是插入排序。整理方法是一張一張來,將每一張牌插入其他已經有序的牌中的適當位置。當然,在數組操作中,如果需要給某個元素插入,騰出空間,則需要將其他元素向後移動一位。
代碼“:
public static void sort(Comparable[] a) {
for (int i = 1; i < a.length; i++) {
for (int j = i - 1; j >= 0 && less(a[j+1], a[j]); j--) {
swap(a, j, j + 1);
}
}
}
這個代碼簡潔,但是卻不容易讓人看懂。不個人不推薦這麼做。而是應該將思路表達的清晰。
改變:
public static void sort(Comparable[] a) {
for (int i = 1; i < a.length; i++) {
int position = findPosition(a, i); // 找到應該插入的位置
Comparable tempI = a[i];
for (int j = i - 1; j >= position; j--) { // 爲插入的位置騰出空間,向後挪動
a[j + 1] = a[j];
}
a[position] = tempI; // 插入值
}
}
public static int findPosition(Comparable[] a, int i) {
for (int j = i - 1; j >= 0; j--) {
if (less(a[j], a[i])) return j + 1;
}
return 0;
}
和選擇排序不同,插入排序所需的時間和取決於輸入元素的初始順序。對一個已經接近有序的數組進行排序,會比對順序隨機的數組進行排序快得多。
平均時間複雜度:O(n^2)。最好情況時間複雜度O(n)。
插入排序對於部分有序的數組來說非常高效,這是它最大的優點。
希爾排序
希爾排序是對插入排序的一個優化。對於大規模的亂序數組,插入排序會比較緩慢,因爲他需要慢慢的向前找到位置,所以元素只能一點一點的從數組中移動到另一端。
希爾排序本質就是分組的插入排序,但是它並不需要一個一個比較,而是能將元素一下子移到一個比較遠的地方。
原理:
先將整個待排元素序列分割成若干個子序列(由相隔某個“增量”的元素組成的)分別進行直接插入排序,然後依次縮減增量再進行排序,待整個序列中的元素基本有序(增量足夠小)時,再對全體元素進行一次直接插入排序。因爲直接插入排序在元素基本有序的情況下(接近最好情況),效率是很高的,因此希爾排序在時間效率上比前兩種方法有較大提高。
上圖中,方框指向的是同一組,也就是將同一組數據排序。當分組越來越少,一組中元素越來越多的時候,排序就基本完成了。而最終,當所有元素都在同一組中的時候,就是簡單插入排序。因爲這個時候數組已經接近有序了,所以排序起來非常快。
代碼:
public static void sort(Comparable[] a) {
int gap = 1;
while (gap < a.length /3) gap = 3*gap + 1;
while (gap >= 1) {
for (int i = 0; i < gap; i++) {
for (int j = i + gap; j < a.length; j += gap) {
int position = findPosition(a, i, j, gap);
Comparable tempJ = a[j];
for (int k = j - gap; k >= position; k -= gap) {
a[k + gap] = a[k];
}
a[position] = tempJ;
}
}
gap /= 3;
}
}
public static int findPosition(Comparable[] a, int begin, int current, int gap) {
for (int i = current - gap; i >= begin; i -= gap) {
if (less(a[i], a[current])) return i + gap;
}
return begin;
}
和插入排序非常類似,僅僅是分組的插入排序。比較:
代碼:
public static void main(String[] args) {
final int NUM = 10000;
Integer[] a1 = new Integer[NUM];
Integer[] a2 = new Integer[NUM];
Integer[] a3 = new Integer[NUM];
Integer[] a4 = new Integer[NUM];
for (int i = 0; i < NUM; i++) {
a1[i] = (int)(Math.random() * NUM);
a2[i] = a1[i];
a3[i] = a1[i];
a4[i] = a1[i];
}
long startTime;
long endTime;
/*
* 交換
* */
startTime = System.currentTimeMillis(); //獲取開始時間
switchSort.sort(a2);
assert isSorted(a2);
endTime = System.currentTimeMillis();
System.out.println("交換排序cost: " + (endTime - startTime) + " ms");
/*
* 插入
* */
startTime = System.currentTimeMillis(); //獲取開始時間
InsertSort.sort(a3);
assert isSorted(a3);
endTime = System.currentTimeMillis();
System.out.println("插入排序cost: " + (endTime - startTime) + " ms");
/*
* 冒泡
* */
startTime = System.currentTimeMillis(); //獲取開始時間
bubbleSort.sort(a1);
assert isSorted(a1);
endTime = System.currentTimeMillis();
System.out.println("冒泡排序cost: " + (endTime - startTime) + " ms");
/*
* shell
* */
startTime = System.currentTimeMillis(); //獲取開始時間
ShellSort.sort(a4);
assert isSorted(a4);
endTime = System.currentTimeMillis();
System.out.println("希爾排序cost: " + (endTime - startTime) + " ms");
}
運行多次,我們會發現:
當數據量不大的時候,插入排序比交換排序快。而數據量大起來,交換排序就比插入排序更加快了。
當然,如果數組是接近有序的,插入排序的性能會非常強大。
冒泡排序比較緩慢。
而希爾排序的性能非常不錯。即使數據量非常大,希爾排序的性能也是不錯的。希爾排序的平均時間複雜度是O(n^1.3)左右。相對簡單插入排序來說確實是極大的提升。並且希爾排序的代碼量並不大。