Array.sort 演算法原理(插入排序\快速排序in-place實現)

夜貓碩發表於2020-11-12

javascript Array.sort 原理實現

v8 引擎排序演算法原始碼(710 line)
js sort原理
十大經典演算法

js 提供了 sort 方法,方便對陣列進行排序,然而不同引擎對 js 的 sort 方法解析可能存在差異,本文基於 v8 引擎對排序 sort 的實現,分析其使用的 插入排序 和 快速排序 。
本文不致力於分析 v8 引擎原始碼,僅分析內部使用的兩種演算法

插入排序
下面是一張動圖演示。

基本思想
從左往右遍歷陣列,每次將遍歷的項插入到前面的已經排序好的有序陣列中,通過構建有序序列,對於未排序資料,在已排序序列中從後向前掃描,找到相應位置並插入。
演算法步驟

預設第一項已經排序
從陣列第二項開始遍歷,取出當前遍歷的新元素。
從當前項前一個往前查詢,如果查詢的項比新元素大,就向後移動一位
找到新元素在有序序列中的位置(查詢的項比新元素小,那新元素因為位於該元素後,也就是空出的空位中)放入新元素
繼續遍歷,重複2-4.

程式碼實現
function InsertionSort(arr, from = 0, to = arr.length) {
// 從陣列第二項遍歷
for (var i = from + 1; i < to; i++) {
// 取出新元素
var element = arr[i];
// 從新元素位置向前查詢
for (var j = i - 1; j >= from; j–) {
// 快取查詢的項
var tmp = arr[j];
// 計算是否是需要插入的位置
// 此處可修改插入邏輯,正序倒序
var order = tmp - element;
if (order > 0) {
// 不是插入位置,查詢項後移
arr[j + 1] = tmp;
} else {
// 是插入位置,退出迴圈,插入元素
break;
}
}
// 退出迴圈插入元素
arr[j + 1] = element;
}
};
複製程式碼
快速排序
快速排序,又稱劃分交換排序。以分治法為策略實現的快速排序演算法。這裡主要實現 in-place 思想的快速排序
演算法思想
快速排序的基本思想:通過一趟排序將待排記錄分隔成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分的關鍵字小,則可分別對這兩部分記錄繼續進行排序,以達到整個序列有序。
演算法步驟

選基準:在資料結構中選擇一個元素作為基準(pivot)
劃分割槽:參照基準元素值的大小,劃分無序區,所有小於基準元素的資料放入一個區間,所有大於基準元素的資料放入另一區間,分割槽操作結束後,基準元素所處的位置就是最終排序後它應該所處的位置
遞迴:對初次劃分出來的兩個無序區間,遞迴呼叫第 1步和第 2步的演算法,直到所有無序區間都只剩下一個元素為止。

程式碼實現
這裡根據分割槽操作的實現方法分為下面兩種實現方式

挖坑把基準元素往中間夾逼方式

取出第一項作為基準元素(可理解為第一項空出坑位)
從後往前找小於基準元素的元素,填入坑位,空出後面的坑位。
從前往後找大於基準元素的元素,填入後方空位,空出前面坑位
重複2-3
當位置正好到中間時結束,把基準元素放入最後空出的空位

/**

  • https://www.runoob.com/w3cnote/quick-sort.html
  • 快速排序演算法
  • @param {*} arr
  • @param {*} left
  • @param {*} right
    */

function quickSort(arr, left = 0, right = arr.length - 1) {

function partition(arr, left, right) {
const povit = arr[left];
while (left < right) {
while (left < right && arr[right] >= povit) {
right–;
}
if (left < right) {
arr[left] = arr[right]; //將s[right]填到s[left]中,s[right]就形成了一個新的坑
left++;
}

  while (left < right && arr[left] < povit) {
    left++;
  }

  if (left < right) {
    arr[right] = arr[left]; //將s[right]填到s[left]中,s[right]就形成了一個新的坑
    right--;
  }
}

arr[left] = povit;
return left;

}

if (left < right) {
// 分治
const partitionIndex = partition(arr, left, right);
quickSort(arr, left, partitionIndex);
quickSort(arr, partitionIndex + 1, right);
}
return arr;
}
複製程式碼

順序遍歷分割槽思想

取最右邊的元素為基準
記錄已經比對後小於基準元素的陣列最後位置
從左向右遍歷陣列
小於基準則移動到比對後的最後位置,並更新最後位置
遍歷完成把基準元素新增到最後位置之後。

function quickSort1(arr) {
// 交換
function swap(arr, a, b) {
[arr[a],arr[b]] = [arr[b],arr[a]]
}

// 分割槽
function partition(arr, left, right) {
/**
* 開始時不知最終pivot的存放位置,可以先將pivot交換到後面去
* 這裡直接定義最右邊的元素為基準
/
const pivot = arr[right];
/
*
* 存放小於pivot的元素時,是緊挨著上一元素的,否則空隙裡存放的可能是大於pivot的元素,
* 故宣告一個storeIndex變數,並初始化為left來依次緊挨著存放小於pivot的元素。
/
let storeIndex = left;
for (let i = left; i < right; i++) {
if (arr[i] < pivot) {
/
*
* 遍歷陣列,找到小於的pivot的元素,(大於pivot的元素會跳過)
* 將迴圈i次時得到的元素,通過swap交換放到storeIndex處,
* 並對storeIndex遞增1,表示下一個可能要交換的位置
*/
swap(arr, storeIndex, i);
storeIndex++;
}
}
// 最後: 將pivot交換到storeIndex處,基準元素放置到最終正確位置上
swap(arr, right, storeIndex);
return storeIndex;
}

function sort(arr, left, right) {
if (left > right) return;

const storeIndex = partition(arr, left, right);
sort(arr, left, storeIndex - 1);
sort(arr, storeIndex + 1, right);

}

sort(arr, 0, arr.length - 1);
return arr;
}

相關文章