冒泡排序
public static int[] sortArray(int[] arr) {
int temp;
for (int i = 0; i < arr.length - 1; i++) {
boolean flag = true;
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
flag = false;
}
}
if (flag) {
break;
}
}
return arr;
}
- 思路
- 對長度爲n的數組遍歷n次,每次遍歷依次比較相鄰的兩個元素,如果大小關係不符合要求,就交換它倆的位置。這樣每次遍歷都能保證至少一個元素移動到了它應該在的位置。
- 注意:以從小到大排序爲例,第一次循環後數組末尾位置的元素就是最大值,第二次倒數第二位的元素也是第二大的,依次類推…所以代碼中內層循環的界限是在一直縮小的。
- 優化:用一個flag標記當次循環中是否有元素進行了交換。如果沒有任何元素髮生了交換,則證明數組已經是有序的了,可以直接跳出循環,結束排序。
- 分析
- 穩定性:相鄰元素大小相等時不做交換,所以不會改變大小相等的元素的順序,是穩定的排序算法。
- 空間複雜度:O(1),只在交換數據時需要一塊常數級的臨時空間,是原地排序算法。
- 時間複雜度:最好的情況,數組本來就是有序的,時間複雜度爲O(n)。最壞的情況,數組是倒序的,需要n次冒泡,時間複雜度爲O(n²)。經過不嚴格的簡單推導,平均時間複雜度還是O(n²)。
插入排序
public static int[] sortArray(int[] arr) {
for (int i = 1; i < arr.length; i++) {
int value = arr[i];
int j = i - 1;
for (; j >= 0; j--) {
if (arr[j] > value) {
arr[j + 1] = arr[j];
} else {
break;
}
}
arr[j + 1] = value;
}
return arr;
}
- 思路
- 將數組分爲兩個區間,已排序和未排序。已排序區間最開始就有一個元素,也就是數組的第一個元素。然後取未排序區間的元素,在已排序區間中找到自己的位置,插進去。直到未排序區間爲空。
- 代碼中,以從小到大排序爲例,下標i前的元素爲已排序元素,i及i之後的元素爲未排序元素。每次取一個未排序的元素,倒着去比較已排序元素,如果發現比某個元素小,就插在這個元素之前。
- 分析
- 穩定性:比較插入時,如果元素相等,可以將未排序的元素插在已排序的元素後邊,可以做到保持原有順序不變,是穩定的排序算法。
- 空間複雜度:O(1),不需要額外的存儲空間,是原地排序算法。
- 時間複雜度:最好的情況,數組本來就是有序的,如果從尾到頭遍歷有序部分,查找插入位置,每次循環只需要比較一次就能確定,時間複雜度爲O(n)。最壞的情況,數組是倒序的,每次查找都要把元素插在有序部分的開頭,需要多次移動元素,時間複雜度爲O(n²)。往數組中插入一個數據的平均時間複雜度是O(n),我們相當於循環n次執行插入操作,所以平均時間複雜度爲O(n²)。
冒泡排序 vs 插入排序
- 它們的穩定性和時間、空間複雜度都相同,但如果要追求極致,可以發現冒泡排序在交換元素時需要3個賦值操作,而插入排序只需要一個。當數據量很大的時候這點微小的差距累積起來就可以讓插入排序勝過冒泡排序。
- 插入排序的優化:參考 五分鐘學會一個高難度算法:希爾排序
選擇排序
- 思路
- 類似插入排序,以從小到大排序爲例,每次遍歷找到未排序部分的最小值放在已排序部分的末尾。
- 分析
- 穩定性:具體實現時會將最小值元素與要放在已排序部分末尾那個位置的元素交換位置。經過多次交換後會破壞穩定性。
- 空間複雜度時間複雜度與冒泡排序和插入排序是一樣的。