以上是常見的幾種排序算法,首先思考一下, Array.prototype.sort()
使用了上面的那種算法喃?前端
sort()
方法用原地算法對數組的元素進行排序,並返回數組。默認排序順序是在將元素轉換爲字符串,而後比較它們的UTF-16代碼單元值序列時構建的git— MDNgithub
const array = [1, 30, 4, 21, 100000];
array.sort();
console.log(array);
// [1, 100000, 21, 30, 4]
const numbers = [4, 2, 5, 1, 3];
numbers.sort((a, b) => a - b);
console.log(numbers)
// [1, 2, 3, 4, 5]
複製代碼
關於 Array.prototype.sort()
,ES 規範並無指定具體的算法,在 V8 引擎中, 7.0 版本以前 ,數組長度小於10時, Array.prototype.sort()
使用的是插入排序,不然用快速排序。面試
在 V8 引擎 7.0 版本以後 就捨棄了快速排序,由於它不是穩定的排序算法,在最壞狀況下,時間複雜度會降級到 O(n2)。算法
因而採用了一種混合排序的算法:TimSort 。數組
這種功能算法最初用於Python語言中,嚴格地說它不屬於以上10種排序算法中的任何一種,屬於一種混合排序算法:markdown
在數據量小的子數組中使用插入排序,而後再使用歸併排序將有序的子數組進行合併排序,時間複雜度爲 O(nlogn)
。函數
在 解答 v8 sort 源碼前,咱們先看看 TimSort 具體是如何實現的,幫助咱們閱讀源碼oop
Timsort 是 Tim Peter 在 2001 年爲 Python 語言特地創造的,主要是 基於現實數據集中存在者大量的有序元素(不須要從新排序) 。 Timsort 會遍歷全部數據,找出數據中全部有序的分區(run),而後按照必定的規則將這些分區(run)歸併爲一個。性能
具體過程爲:
_runs_
,一個 run 能夠認爲是已經排序的小數組,也包括以逆向排序的,由於這些數組能夠簡單地翻轉(reverse)就成爲一個run如何避免歸併長度相差很大 run 呢?在 Timsort 排序過程當中,會存在一個棧用於記錄每一個 run 的起始索引位置與長度, 依次將 run 壓入棧中,若棧頂 A 、B、C 的長度
|C| > |B| + |A|
|B| > |A|
在上圖的例子中,由於 | A |> | B |
,因此B被合併到了它先後兩個runs(A、C)中較小的一個 | A |
,而後 | A |
再與 | C |
。 依據這個法則,可以儘可能使得大小相同的 run 合併,以提升性能。注意Timsort是穩定排序故只有相鄰的 run 才能歸併。
因此,對於已經排序好的數組,會以 O(n) 的時間內完成排序,由於這樣的數組將只產生單個 run ,不須要合併操做。最壞的狀況是 O(n log n) 。這樣的算法性能參數,以及 Timsort 天生的穩定性是咱們最終選擇 Timsort 而非 Quicksort 的幾個緣由。
瞭解的 Timsort 的基本思想與排序過程後,咱們手寫一個簡易版的 Timsort :
// 順序合併兩個小數組left、right 到 result
function merge(left, right) {
let result = [],
ileft = 0,
iright = 0
while(ileft < left.length && iright < right.length) {
if(left[ileft] < right[iright]){
result.push(left[ileft ++])
} else {
result.push(right[iright ++])
}
}
while(ileft < left.length) {
result.push(left[ileft ++])
}
while(iright < right.length) {
result.push(right[iright ++])
}
return result
}
// 插入排序
function insertionSort(arr) {
let n = arr.length;
let preIndex, current;
for (let i = 1; i < n; i++) {
preIndex = i - 1;
current = arr[i];
while (preIndex >= 0 && arr[preIndex] > current) {
arr[preIndex + 1] = arr[preIndex];
preIndex--;
}
arr[preIndex + 1] = current;
}
return arr;
}
// timsort
function timsort(arr) {
// 空數組或數組長度小於 2,直接返回
if(!arr || arr.length < 2) return arr
let runs = [],
sortedRuns = [],
newRun = [arr[0]],
length = arr.length
// 劃分 run 區,並存儲到 runs 中,這裏簡單的按照升序劃分,沒有考慮降序的run
for(let i = 1; i < length; i++) {
if(arr[i] < arr[i - 1]) {
runs.push(newRun)
newRun = [arr[i]]
} else {
newRun.push(arr[i])
}
if(length - 1 === i) {
runs.push(newRun)
break
}
}
// 因爲僅僅是升序的run,沒有涉及到run的擴充和降序的run,所以,其實這裏沒有必要使用 insertionSort 來進行 run 自身的排序
for(let run of runs) {
insertionSort(run)
}
// 合併 runs
sortedRuns = []
for(let run of runs) {
sortedRuns = merge(sortedRuns, run)
}
return sortedRuns
}
// 測試
var numbers = [4, 2, 5, 1, 3]
timsort(numbers)
// [1, 2, 3, 4, 5]
複製代碼
簡易版的,完整的實現能夠查看 v8 array-sort 實現,下面咱們就來看一下
即 TimSort 在 v8 中的實現,具體實現步驟以下:
下面重點解讀 3 個核心函數:
ComputeMinRunLength
:用來計算 minRunLength
CountAndMakeRun
:計算第一個 run
的長度MergeCollapse
:調整 pendingRuns
,使棧長度大於 3
時,全部 run
都知足 run[n]>run[n+1]+run[n+2]
且 run[n+1]>run2[n+2]
// 計算最小合併序列長度 minRunLength
macro ComputeMinRunLength(nArg: Smi): Smi {
let n: Smi = nArg;
let r: Smi = 0; // Becomes 1 if any 1 bits are shifted off.
assert(n >= 0);
// 若是小於 64 ,則返回n(該值過小,沒法打擾那些奇特的東西)
// 不然不斷除以2,獲得結果在 32~64 之間
while (n >= 64) {
r = r | (n & 1);
n = n >> 1;
}
const minRunLength: Smi = n + r;
assert(nArg < 64 || (32 <= minRunLength && minRunLength <= 64));
return minRunLength;
}
複製代碼
// 計算第一個 run 的長度
macro CountAndMakeRun(implicit context: Context, sortState: SortState)(
lowArg: Smi, high: Smi): Smi {
assert(lowArg < high);
// 這裏保存的纔是咱們傳入的數組數據
const workArray = sortState.workArray;
const low: Smi = lowArg + 1;
if (low == high) return 1;
let runLength: Smi = 2;
const elementLow = UnsafeCast<JSAny>(workArray.objects[low]);
const elementLowPred = UnsafeCast<JSAny>(workArray.objects[low - 1]);
// 調用比對函數來比對數據
let order = sortState.Compare(elementLow, elementLowPred);
// TODO(szuend): Replace with "order < 0" once Torque supports it.
// Currently the operator<(Number, Number) has return type
// 'never' and uses two labels to branch.
const isDescending: bool = order < 0 ? true : false;
let previousElement: JSAny = elementLow;
// 遍歷子數組並計算 run 的長度
for (let idx: Smi = low + 1; idx < high; ++idx) {
const currentElement = UnsafeCast<JSAny>(workArray.objects[idx]);
order = sortState.Compare(currentElement, previousElement);
if (isDescending) {
if (order >= 0) break;
} else {
if (order < 0) break;
}
previousElement = currentElement;
++runLength;
}
if (isDescending) {
ReverseRange(workArray, lowArg, lowArg + runLength);
}
return runLength;
}
複製代碼
// 調整 pendingRuns ,使棧長度大於3時,全部 run 都知足 run[n]>run[n+1]+run[n+2] 且 run[n+1]>run2[n+2]
transitioning macro MergeCollapse(context: Context, sortState: SortState) {
const pendingRuns: FixedArray = sortState.pendingRuns;
// Reload the stack size because MergeAt might change it.
while (GetPendingRunsSize(sortState) > 1) {
let n: Smi = GetPendingRunsSize(sortState) - 2;
if (!RunInvariantEstablished(pendingRuns, n + 1) ||
!RunInvariantEstablished(pendingRuns, n)) {
if (GetPendingRunLength(pendingRuns, n - 1) <
GetPendingRunLength(pendingRuns, n + 1)) {
--n;
}
MergeAt(n); // 將第 n 個 run 和第 n+1 個 run 進行合併
} else if (
GetPendingRunLength(pendingRuns, n) <=
GetPendingRunLength(pendingRuns, n + 1)) {
MergeAt(n); // 將第 n 個 run 和第 n+1 個 run 進行合併
} else {
break;
}
}
}
複製代碼