在計算機科學所使用的排序算法通常被分類為:
- 計算的 時間復雜度(最差、平均、和最好性能),依據(jù)列表(
list
)的大小(n
)。一般而言,好的性能是O(n log n)
,且壞的性能是O(n^2)
。對于一個排序理想的性能是O(n)
。僅使用一個抽象關(guān)鍵比較運算的排序算法總平均上總是至少需要O(n log n)
。 - 存儲器使用量(以及其他電腦資源的使用)
穩(wěn)定性:穩(wěn)定排序算法會讓原本有相等鍵值的紀錄維持相對次序。也就是如果一個排序算法是穩(wěn)定的,當有兩個相等鍵值的紀錄R和S,且在原本的列表中R出現(xiàn)在S之前,在排序過的列表中R也將會是在S之前。 - 依據(jù)排序的方法:插入、交換、選擇、合并等等。
依據(jù)排序的方法分類的三種排序算法:
冒泡排序
冒泡排序?qū)σ粋€需要進行排序的數(shù)組進行以下操作:
- 比較第一項和第二項;
- 如果第一項應該排在第二項之后, 那么兩者交換順序;
- 比較第二項和第三項;
- 如果第二項應該排在第三項之后, 那么兩者交換順序;
- 以此類推直到完成排序;
實例說明:
將數(shù)組[3, 2, 4, 5, 1]
以從小到大的順序進行排序:
- 3應該在2之后, 因此交換, 得到
[2, 3, 4, 5, 1]
; - 3, 4順序不變, 4, 5也不變, 交換5, 1得到
[2, 3, 4, 1, 5]
; - 第一次遍歷結(jié)束, 數(shù)組中最后一項處于正確位置不會再有變化, 因此下一次遍歷可以排除最后一項;
- 開始第二次遍歷, 最后結(jié)果為
[2, 3, 1, 4, 5]
, 排除后兩項進行下一次遍歷; - 第三次遍歷結(jié)果為
[2, 1, 3, 4, 5]
; - 最后得到
[1, 2, 3, 4, 5]
, 排序結(jié)束;
代碼實現(xiàn):
function swap(items, firstIndex, secondIndex){
var temp = items[firstIndex];
items[firstIndex] = items[secondIndex];
items[secondIndex] = temp;
};
function bubbleSort(items){
var len = items.length, i, j, stop;
for (i = 0; i < len; i++){
for (j = 0, stop = len-i; j < stop; j++){
if (items[j] > items[j+1]){
swap(items, j, j+1);
}
}
}
return items;
}
外層的循環(huán)決定需要進行多少次遍歷, 內(nèi)層的循環(huán)負責數(shù)組內(nèi)各項的比較, 還通過外層循環(huán)的次數(shù)和數(shù)組長度決定何時停止比較.
冒泡排序極其低效, 因為處理數(shù)據(jù)的步驟太多, 對于數(shù)組中的每n
項, 都需要n^2
次操作來實現(xiàn)該算法(實際比n^2
略小, 但可以忽略, 具體原因見??), 即時間復雜度為O(n^2)
.
對于含有n個元素的數(shù)組, 需要進行
(n-1)+(n-2)+...+1
次操作, 而(n-1)+(n-2)+...+1 = n(n-1)/2 = n^2/2 - n/2
, 如果n趨于無限大, 那么n/2的大小對于整個算式的結(jié)果影響可以忽略, 因此最終的時間復雜度用O(n^2)
表示
選擇排序
選擇排序?qū)σ粋€需要進行排序的數(shù)組進行以下操作:
1.假定數(shù)組中的第一項為最小值(min);
- 比較第一項和第二項的值;
- 若第二項比第一項小, 則假定第二項為最小值;
- 以此類推直到排序完成.
實例說明:
將數(shù)組["b", "a", "d", "c", "e"]
以字母a-z
的順序進行排序:
- 假定數(shù)組中第一項
"b"(index0)為min
; - 比較第二項"a"與第一項"b", 因"a"應在"b"之前的順序, 故
"a"(index1)為min
; - 然后將min與后面幾項比較, 由于"a"就是最小值, 因此min確定在index1的位置;
- 第一次遍歷結(jié)束后, 將假定的
min(index0)
, 與真實的min(index1)進行比較, 真實的min應該在index0的位置, 因此將兩者交換, 第一次遍歷交換之后的結(jié)果為["a", "b", "d", "c", "e"]
; - 然后開始第二次遍歷, 遍歷從第二項(
index1
的位置)開始, 這次假定第二項為最小值, 將第二項與之后幾項逐個比較, 因為"b"就在應該存在的位置, 所以不需要進行交換, 這次遍歷之后的結(jié)果為["a", "b", "d", "c", "e"]
;
6.之后開始第三次遍歷, "c"應為這次遍歷的最小值, 交換index2("d")
,index3("c")
位置, 最后結(jié)果為["a", "b", "c", "d", "e"]
; - 最后一次遍歷, 所有元素在應有位置, 不需要進行交換.
代碼實現(xiàn):
function swap(items, firstIndex, secondIndex){
var temp = items[firstIndex];
items[firstIndex] = items[secondIndex];
items[secondIndex] = temp;
};
function selectionSort(){
let items = [...document.querySelectorAll('.num-queue span')].map(num => +num.textContent);
let len = items.length, min;
for (i = 0; i < len; i++){
min = i;
for(j = i + 1; j < len; j++){
if(items[j] < items[min]){
min = j;
}
}
if(i != min){
swap(items, i, min);
}
}
return items;
};
外層循環(huán)決定每次遍歷的初始位置, 從數(shù)組的第一項開始直到最后一項. 內(nèi)層循環(huán)決定哪一項元素被比較.
選擇排序的時間復雜度為O(n^2)
.
插入排序
與上述兩種排序算法不同, 插入排序是穩(wěn)定排序算法(stable sort algorithm
), 穩(wěn)定排序算法指不改變列表中相同元素的位置, 冒泡排序和選擇排序不是穩(wěn)定排序算法, 因為排序過程中有可能會改變相同元素位置. 對簡單的值(數(shù)字或字符串)排序時, 相同元素位置改變與否影響不是很大. 而當列表中的元素是對象, 根據(jù)對象的某個屬性對列表進行排序時, 使用穩(wěn)定排序算法就很有必要了.
一旦算法包含交換(swap)這個步驟, 就不可能是穩(wěn)定的排序算法. 列表內(nèi)元素不斷交換, 無法保證先前的元素排列為止一直保持原樣. 而插入排序的實現(xiàn)過程不包含交換, 而是提取某個元素將其插入數(shù)組中正確位置.
插入排序的實現(xiàn)是將一個數(shù)組分為兩個部分, 一部分排序完成, 一部分未進行排序. 初始狀態(tài)下整個數(shù)組屬于未排序部分, 排序完成部分為空. 然后進行排序, 數(shù)組內(nèi)的第一項被加入排序完成部分, 由于只有一項, 自然屬于排序完成狀態(tài). 然后對未完成排序的余下部分的元素進行如下操作:
- 如果這一項的值應該在排序完成部分最后一項元素之后, 保留這一項在原有位置開始下一步;
- 如果這一項的值應該排在排序完成部分最后一項元素之前, 將這一項從未完成部分暫時移開, 將已完成部分的最后一項元素移后一個位置;
- 被暫時移開的元素與已完成部分倒數(shù)第二項元素進行比較;
- 如果被移除元素的值在最后一項與倒數(shù)第二項的值之間, 那么將其插入兩者之間的位置, 否則繼續(xù)與前面的元素比較, 將暫移出的元素放置已完成部分合適位置. 以此類推直到所有元素都被移至排序完成部分.
實例說明:
現(xiàn)在需要將數(shù)組var items = [5, 2, 6, 1, 3, 9];
進行插入排序:
- 5屬于已完成部分, 余下元素為未完成部分. 接下來提取出2, 因為5比2大, 于是5被移至靠右一個位置, 覆蓋2, 占用2原本存在的位置. 這樣本來存放5的位置(已完成部分的首個位置)就被空出, 而2在比5小, 因此將2置于這個位置, 此時結(jié)果為
[2, 5, 6, 1, 3, 9]
; - 接下來提取出6, 因為6比5大, 所以不操作提取出1, 1與已完成部分各個元素
(2, 5, 6)
進行比較, 應該在2之前, 因此2, 5, 6各向右移一位, 1置于已完成部分首位, 此時結(jié)果為[1, 2, 5, 6, 3, 9]
; - 對余下未完成元素進行類似操作, 最后得出結(jié)果
[1, 2, 3, 5, 6, 9]
;
代碼實現(xiàn):
function insertionSort(items) {
let len = items.length, value, i, j;
for (i = 0; i < len; i++) {
value = items[i];
for (j = i-1; j > -1 && items[j] > value; j--) {
items[j+1] = items[j];
}
items[j+1] = value;
}
return items;
};
外層循環(huán)的遍歷順序是從數(shù)組的第一位到最后一位, 內(nèi)層循環(huán)的遍歷則是從后往前, 內(nèi)層循環(huán)同時負責元素的移位.
插入排序的時間復雜度為O(n^2)
以上三種排序算法都十分低效, 因此實際應用中不要使用這三種算法, 遇到需要排序的問題, 應該首先使用JavaScript內(nèi)置的方法Array.prototype.sort();
參考: