分析排序算法
執行效率
1. 最好、最壞、平均時間復雜度
數據的有序程度不一樣,對排序算法時間復雜度的影響很大,所以需要對最好、最壞情況復雜度有了解
2. 時間復雜度的系數、常數
當兩個算法的時間復雜度是一個階層的,這時就需要考慮它們的系數或者常數項,以此來判斷快慢
3. 比較次數和交換次數
基于比較的排序算法基本都有比較和交換的操作,計算時間復雜度的時候,需要把這兩種操作計算進去
內存消耗
內存消耗可以用空間復雜度來表示,對于空間復雜度為 O(1)
排序算法,稱其為 原地排序
穩定性
針對排序算法,還有另外一個指標,即算法的穩定性。
算法的穩定性指的是,排序關鍵字相同的兩個項,在排序之前和之后的相對位置是否變化,若相對位置沒有變化,則是穩定排序算法,否則是非穩定排序算法
O(n2) 復雜度排序算法
算法 | 原地排序 | 穩定排序 | 最好時間復雜度 | 最壞時間復雜度 | 平均時間復雜度 |
---|---|---|---|---|---|
冒泡排序 | 是 | 是 | O(n) - 數組有序 | O(n2) - 數組逆序 | O(n2) |
插入排序 | 是 | 是 | O(n) - 數組有序 | O(n2) - 數組逆序 | O(n2) |
選擇排序 | 是 | 否 | O(n2) | O(n2) | O(n2) |
1. 冒泡排序
思想:每次比較相鄰的兩個數,滿足交換條件則交換兩個數,所以每次都能將未排序的最大(小)的數冒泡到末尾,循環 n 次后,數組已經有序
可優化點:當某次冒泡中沒有發生數據交換時,此時數組已經有序,不需要再進行后續的冒泡比較了
function quickSort(arr) {
if(!arr || arr.length < 2) {
return arr;
}
for (let i = 0; i < arr.length; i++) {
// j 和 j + 1 比較,最后一個元素就是 j + 1,所以 - 1
const top = arr.length - i - 1;
let isSorted = true;
for(let j = 0; j < top; j++) {
if (arr[j] > arr[j+1]) {
const temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
isSorted = false;
}
}
if (isSorted) {
break;
}
}
return arr;
}
2. 插入排序
思想: 將數組分為 已排序區間和未排序區間,每次從將未排序區間第一個元素插入到已排序區間的正確位置上
// 使用 js 數組方法
function insertSort(arr) {
for (let i = 1; i < arr.length; i++) {
const ordered = arr.slice(0, i);
const value = arr[i];
const index = ordered.findIndex(val => val > value);
if (index !== -1) {
arr.splice(i, 1);
arr.splice(index, 0, value);
}
}
return arr;
}
// 不使用 js 數組方法
function insertSort(arr) {
if (arr.length < 2) {
return arr;
}
for (let i = 1; i < arr.length; i++) {
const value = arr[i];
let j = i;
while (j--) {
if (arr[j] > value) {
arr[j + 1] = arr[j];
} else {
break;
}
}
arr[j + 1] = value;
}
return arr;
}
3. 選擇排序
思想: 也是將數組分為 已排序區間和未排序區間,每次從未排序區間中找到最小值,和未排序區間第一個值做交換
function selectSort(arr) {
if (arr.length < 1) {
return arr;
}
let orderedTrail = 0;
while (orderedTrail < arr.length){
let j = orderedTrail;
let min = arr[j];
let minIndex = j;
while (j < arr.length) {
if (arr[j] < min) {
min = arr[j];
minIndex = j;
}
j += 1;
}
arr[minIndex] = arr[orderedTrail];
arr[orderedTrail++] = min;
}
return arr;
}
O(nlogn) 復雜度排序算法
算法 | 原地排序 | 穩定排序 | 最好時間復雜度 | 最壞時間復雜度 | 平均時間復雜度 |
---|---|---|---|---|---|
歸并排序 | 否 - O(n) | 是 | O(nlogn) | O(nlogn) | O(nlogn) |
快速排序 | 是 | 否 | O(O(nlogn)) - 分區比較均衡 | O(n2) - 分區及其不均衡 | O(O(nlogn)) |
1. 歸并排序
思想: 把數組分為前后兩個部分,分別對兩個部分進行排序,然后將排好序的兩部分合并,則數組有序( 分而治之的思想 )
function mergeSort(arr) {
if (!arr || !Array.isArray(arr) || !arr.length) {
return [];
}
const merge = function(left, middle, right) {
const tempArr = Array(right - left);
let index = 0;
let leftIndex = left;
let rightIndex = middle;
while (leftIndex < middle && rightIndex < right) {
const leftValue = arr[leftIndex];
const rightValue = arr[rightIndex];
if (leftValue <= rightValue) {
tempArr[index++] = leftValue;
leftIndex += 1;
} else {
tempArr[index++] = rightValue;
rightIndex += 1;
}
}
let start = leftIndex < middle ? leftIndex : rightIndex;
let end = leftIndex < middle ? middle : right;
while (start < end) {
tempArr[index++] = arr[start++];
}
end = right - left;
for (start = 0; start < end; start++) {
arr[start + left] = tempArr[start];
}
}
const sort = function(left, right) {
if (left >= right) {
return;
}
const middle = ((right + left) / 2) >> 0;
sort(left, middle);
sort(middle + 1, right);
merge(left, middle, right);
}
sort(0, arr.length)
return arr;
}
時間復雜度分析: T(n) = T(n/2)(左伴部分的時間復雜度) + T(n/2)(右半部分的時間復雜度) + K(合并時的時間復雜度),可以以此推出 T(n) = 2^k*T(n/2^k) + k*n,當 2^k == 1時,k = logn,帶入即可得 T(n) = nlogn + Cn
2. 快速排序
思想: 取區間任意位置為標準值,遍歷區間,比標準值小的都放在左邊,比標準值大的都放在右邊,以此類推,直到只有一個元素,此時數組有序( 分而治之的思想 )
function quickSort(arr) {
if (!arr || !Array.isArray(arr) || !arr.length) {
return [];
}
const swap = function(i ,j) {
const temp = arr[j];
arr[j] = arr[i];
arr[i] = temp;
}
const partial = function(left, right) {
const reference = arr[right];
let i = left;
for (let j = left; j < right; j++) {
if (arr[j] < reference) {
swap(i, j)
i += 1;
}
}
swap(i, right);
return i;
}
const sort = function(left, right) {
if (left >= right) {
return;
}
border = partial(left, right);
sort(left, border - 1);
sort(border + 1, right);
}
sort(0, arr.length - 1);
return arr;
}
注意: 由于在交換元素的過程中可能會破壞數組項的原本的先后順序,所以快速排序是非穩定排序
O(n) 復雜度排序算法
1. 桶排序
思想: 根據數據范圍,分幾個桶,將對應區間的數據放到對應的桶中進行排序,然后根據桶的順序合并每個桶中的數據
復雜度分析: 當劃分為 m個桶時,如果劃分均勻,則每個桶有 k = n/m 個項,若對每個桶采用快速排序,則復雜度為 O(k*logk),m 個桶的總復雜度為 O(m*k*logk),又有 k = n/m,則為 O(n*log(n/m)),可見,當桶的個數越接近項的個數,時間復雜度越接近 O(n)
2. 基數排序
思想: 根據數據的每一位分別進行排序,需要使用穩定的排序算法