之前用 JavaScript 寫過 快速排序 和 歸並排序,本文聊聊四個基礎排序算法。(本文默認排序結果都是從小到大)
冒泡排序
冒泡排序每次循環結束會將最大的元素 "冒泡" 到最后一位。
以 [1, 5, 2, 4, 3] 為例,O(n^2) 的復雜度,總共外層循環 5 次,第一次循環結束后的結果是 [1, 2, 4, 3, 5]。 首先是 1 和 5 比較,1 <=5,不交換位置,然后 5 和 2 比較,5 > 2,交換位置,數組變為 [1, 2, 5, 4, 3],然后 5 和 4 比較,交換位置,數組變為 [1, 2, 4, 5, 3],最后 5 和 3 比較,交換位置,數組為 [1, 2, 4, 3, 5],這個時候最大的元素 5 已經到了最后,整個交換過程中大的元素就好像 "冒泡" 一樣冒出來。然后 [1, 2, 4, 3] 再進行同樣操作,以此類推。
冒泡排序看起來無論最好情況還是最壞情況,復雜度一樣,都是 O(n^2)。
function swap(array, a, b) {
var tmp = array[a];
array[a] = array[b];
array[b] = tmp;
}
function bubbleSort(array) {
var _array = array.concat();
for (var i = 0, len = _array.length; i < len; i++)
for (var j = 0; j < len - 1 - i; j++)
if (_array[j] > _array[j + 1])
swap(_array, j, j + 1);
return _array;
}
var a = [1, 5, 2, 4, 3];
var ans = bubbleSort(a);
console.log(ans); // [1, 2, 3, 4, 5]
選擇排序
選擇排序每次循環會找到最值元素的下標,然后將該元素交換到最前面。所以選擇元素每次循環交換一次,不會像冒泡一樣多次交換。
還是以 [1, 5, 2, 4, 3] 為例,第一次循環比較,默認最值下標為 0,最值為 1,接着分別和 5,2,4,3 比較,ok 比完,最值的下標還是 0,那么就不交換(也可以看做 array[0] 和 array[0] 交換)。接着進行第二輪,是為 [5, 2, 4, 3] 進行循環,以此類推。
和冒泡相比,選擇排序也是無論好壞情況,復雜度都是 O(n^2),而效率應該比冒泡稍微好點,畢竟交換次數少了。
function swap(array, a, b) {
var tmp = array[a];
array[a] = array[b];
array[b] = tmp;
}
function selectionSort(array) {
var _array = array.concat();
for (var i = 0, len = _array.length; i < len; i++) {
// 最值元素下標
var index = i;
for (var j = i + 1; j < len; j++)
if (_array[j] < _array[index])
index = j;
swap(_array, i, index);
}
return _array;
}
var a = [1, 5, 2, 4, 3];
var ans = selectionSort(a);
console.log(ans); // [1, 2, 3, 4, 5]
插入排序
插入排序會比前面兩種排序算法高效。它將數組分成 "已排序" 和 "未排序" 兩部分,一開始的時候,"已排序" 的部分只有一個元素,然后將它后面一個元素從 "未排序" 部分插入 "已排序" 部分,從而 "已排序" 部分增加一個元素,"未排序" 部分減少一個元素。以此類推,完成全部排序。(摘自阮老師的博文 http://javascript.ruanyifeng.com/library/sorting.html)
還是以 [1, 5, 2, 4, 3] 為例,外層還是需要循環 5 次,假設循環到第三次,到 2 這個元素,前面已經有序,是為 [1, 5],我們要將 2 插入,首先比較 5 和 2,交換,此時數組前三項為 [1, 2, 5],再比較 1 和 2,ok,不用交換了,有序了,比較結束。再看 4,第一次比較后,交換,數組為 [1, 2, 4, 5],然后 4 和 2 比較,ok,有序了,不用繼續比了,那么 2 就不用和 1 比較了,這樣就大大節省了相鄰元素兩兩比較的次數。
和前兩者相比,插入排序能減少比較次數,當然最壞情況下還是 O(n^2),但是和選擇排序相比,可能會多交換次數。
function insertionSort(array) {
var _array = array.concat();
for (var i = 0, len = _array.length; i < len; i++) {
// 儲存當前位置的值
var item = _array[i];
// 和前面已經有序的部分,比較,交換
for (j = i - 1; j > -1 && _array[j] > item; j--)
_array[j + 1] = _array[j];
_array[j+1] = item;
}
return _array;
}
var a = [1, 5, 2, 4, 3];
var ans = insertionSort(a);
console.log(ans); // [1, 2, 3, 4, 5]
當然,真實生產環境中不可能用這三種排序方法,畢竟效率太低!不過一定要比較效率的話,我覺得是 插入排序 > 選擇排序 > 冒泡排序!
希爾排序
希爾排序是選擇排序的升級版,可以說是分組插入排序,據說復雜度達到 O(n^1.2)。
希爾排序是基於插入排序的以下兩點性質而提出改進方法的:
- 插入排序在對幾乎已經排好序的數據操作時, 效率高, 即可以達到線性排序的效率
- 但插入排序一般來說是低效的, 因為插入排序每次只能將數據移動一位
它是怎么排序的呢?我們設定一個變量叫做 gap,gap 小於數組長度,對於數組,我們將距離 gap 的元素划分為一組,每組進行插入排序,gap 不斷變小,最后減為 1,即完成希爾排序。
gap 如何取值?有個簡單一點的方法,第一次取值數組長度一半,然后再一半,再一半,最后為 1。當然如果要求更高效率,可以深入研究下 gap 的取值。至於希爾排序為什么效率會比普通的插入排序高,這點不在本文探討范圍之內(其實是我不知道),有興趣的可以查閱相關資料。
// 希爾排序:先將整個待排序記錄序列分割成若干個子序列
// 在序列內分別進行直接插入排序,待整個序列基本有序時,
// 再對全體記錄進行一次直接插入排序
function shellSort(array){
var len = array.length
, gap = ~~(len >> 1);
// 克隆數組
var result = array.concat();
while (gap > 0) {
for(var i = gap; i < len; i++) {
var tmp = result[i];
var j = i - gap;
while (j >= 0 && tmp < result[j]) {
result[j + gap] = result[j];
j -= gap; 1
}
result[j + gap] = tmp;
}
gap = ~~(gap >> 1);
}
return result;
}
var a = [1, 5, 2, 4, 3];
var ans = shellSort(a);
console.log(ans); // [1, 2, 3, 4, 5]
Read More: