複雜度爲O(n²)的經典排序算法
冒泡排序、插入排序、選擇排序、歸併排序、快速排序、計數排序、基數排序、桶排序。
排序算法 | 時間複雜度 | 是否基於比較 |
---|---|---|
冒泡, 插入, 選擇 | O(n²) | YES |
快排, 歸併 | O(nlogn) | YES |
桶, 計數, 基數 | O(n) | NO |
排序算法的執行效率
- 最好情況、最壞情況、平均情況時間複雜度
- 在對同一階時間複雜度的排序算法性能對比的時候,我們就要把
係數
、常數
、低階
也考慮進來 - 基於比較的排序算法的執行過程,會涉及兩種操作,一種是元素比較大小,另一種是元素交換或移動, 分析此種排序, 應該把比較次數和交換(或移動)次數也考慮進去
內存消耗
原地排序
(Sorted in place)。原地排序算法,就是特指空間複雜度是 O(1) 的排序算法
穩定性
這個概念是說,如果待排序的序列中存在值相等的元素,經過排序之後,相等元素之間原有的先後順序不變
冒泡排序(Bubble Sort)
核心思想:
1. 從序列的一端開始, 對相鄰的兩個元素進行比較, 不符合預期的順序則交換位置, 然後比較下一對相鄰的元素.
2. 可以將待排序的序列分爲兩段(①待排序段: 位於冒泡的起始端, 開始長度爲序列的長度n; ②已排序端: 位於冒泡的結束端, 開始長度爲0)每一次對待排序段從頭到尾的一次冒泡, 會讓待排序段減少末尾的一個元素, 而這個元素會成爲已排序段頭部的新元素. 至多經過n次(嚴格來說是n-1次, 因爲最後一次, 待排序段僅有一個元素)冒泡, 整個序列達到預期.
3. 當一次冒泡沒有任何數據交換, 說明已經達到預期.
複雜度分析
- 原地排序
- 穩定
- 時間複雜度
- 最好(已經有序): O(n)
- 最壞(倒序): O(n²)
- 平均(加權平均期望時間複雜度): O(n²)
show the code
/*
冒泡排序,a表示數組,n表示數組大小
*/
public void bubbleSort(int[] a, int n) {
if (n <= 1) return;
for (int i = 0; i < n; ++i) {
// 標識是否完成了排序
boolean finished = false;
for (int j = 0, i < n-1-i, j++) {
if (a[j] > a[j + 1]) {
// 交換元素
int temp = a[i+1];
a[j+1] = a[j];
a[j] = temp;
} else {
finished = true;
}
}
if (finished) break;
}
}
插入排序(Insertion Sort)
核心思想
- 將待排序的序列分爲兩個區間
- 已排序段(起始長度爲1)
- 待排序段起始長度爲n-1)
- 每次從待排序段的頭部取一個元素插入到已排序段的合適位置, 知道待排序段的元素個數爲0
複雜度分析
- 原地排序, 空間複雜度O(1);
- 最好時間複雜度: O(n), 對有序序列排序;
- 最壞時間複雜度: O(n²), 對倒序序列排序;
- 平均時間複雜度: O(n²)
- 穩定排序算法
show the code
/*
冒泡排序,a表示數組,n表示數組大小
*/
public void bubbleSort(int[] a, int n) {
if (n <= 1) return;
for (int i = 1; i < n; ++i) {
int intValue = a[i];
int j = i - 1;
for (; j >= 0; --j) {
if (intValue < a[j]) {
// move
a[j+1] = a[j]
} else {
break;
}
}
// insert
a[j+1] = intValue;
}
}
選擇排序(Selection Sort)
核心思想
- 將待排序的序列分爲兩段
- 已排序段(初始爲空)
- 未排序段(初始爲n)
- 每次從未排序段找出最小值, 與最前面的元素交換, 已排序段加1, 未排序段減一
複雜度分析
- 原地排序
- 最好最壞平均時間複雜度: O(n²)
- 非穩定排序
Others
雖然平均時間複雜度相同, 但是插入排序比冒泡排序更受歡迎, 因爲它擁有更簡潔的數據交換, 所需要的時間更短.