JavaScript演算法——排序演算法 下
希爾排序
希爾排序,也稱遞減增量排序演算法,是插入排序的一種更高效的改進版本。但希爾排序是非穩定排序演算法。
它與插入排序的不同之處在於,它會優先比較距離較遠的元素。希爾排序的基本思想是:先將整個待排序的記錄序列分割成為若干子序列,分別進行直接插入排序,待整個序列中的記錄"基本有序"時,再對全體記錄進行依次直接插入排序。
分割排序目的:一步步讓數字離自己正確的位置更近一點
- 實現步驟
1.選擇一個增量序列 t1,t2,……,tk,其中 ti > tj(遞減至1), tk = 1;
2.按增量序列個數 k,對序列進行 k 趟排序;
3.每趟排序,根據對應的增量 ti,將待排序列分割成若干長度為 m 的子序列,分別對各子表進行直接插入排序。僅增量因子為 1 時,整個序列作為一個表來處理,表長度即為整個序列的長度。 - 實現圖
程式碼實現
// 希爾排序 function hillSort(array){ let length = array.length; // 如果不是陣列或者陣列長度小於1 直接返回,不需要排序 if(!Array.isArray(array) || length <= 1)return; //第一層確定增量的大小,每次增量的大小減半 for (let gap = parseInt(length >> 1);gap >= 1; gap = parseInt(gap >> 1)) { // 對每個分組使用插入排序,相當於將插入排序的1換成了 n for (let i = gap; i< length ;i++){ let temp = array[i];//待排序數 let j = i; while (j- gap >= 0 && array[j - gap] > temp){ array [j] = array[j - gap]; j -= gap; } array[j] = temp;//待排序數插到小於它的數後面 } } return array; }
堆排序
堆排序可以說是一種利用堆的概念來排序的選擇排序。分為兩種方法:
1.大頂堆:每個節點的值都大於或等於其子節點的值,在堆排序演算法中用於升序排列
2.小頂堆:每個節點的值都小於或等於其子節點的值,在堆排序演算法中用於降序排列將陣列看成一個完全二叉樹,對於該完全二叉樹只需要遍歷一半的值,進行迴圈比對,把最大的結點賦值到根的位置,然後把根部的值和最後一個數值交換,排除最後一個數值繼續打造大頂堆,最終形成一個小頂堆的演算法。
- 設某結點序號為 i ,則其父結點為⌊ i /2⌋,2i為左子結點序號,2i+1為右子結點序號。其中,⌊⌋為向下取整符號。
- 當儲存了n個元素時,⌊n/2⌋+1、⌊n/2⌋+1、···、n為葉結點。
// 建立堆,其實是對data陣列做一個結構調整,使其具有堆的特性
function buildHeap(data) {
var len = data.length;
for(var i=Math.floor(len/2); i>=0; i--) {
heapAjust(data, i, len);
}
}
// 堆調整函式,即調整當前data為大根堆
function heapAjust(data, i, len) {
var child = 2*i + 1;
// 如果有孩子結點,預設情況是左孩子
while(child <= len) {
var temp = data[i];
// 如果右孩子存在且其值大於左孩子的值,則將child指向右孩子
if(child + 1 <= len && data[child] < data[child + 1]) {
child = child + 1;
}
// 如果當前結點的值小於其孩子結點的值,則交換,直至迴圈結束
if(data[i] < data[child]) {
data[i] = data[child];
data[child] = temp;
i = child;
child = 2*i + 1
}else {
break
}
}
}
// 排序
function heapSort(data) {
var data = data.slice(0);
if(!(data instanceof Array)) {
return null;
}
if(data instanceof Array && data.length == 1) {
return data;
}
// 將data陣列改造為“堆”的結構
buildHeap(data);
var len = data.length;
// 下面需要注意的時候引數的邊界,參考文件裡面程式中i的值是不對的
for(var i=len-1; i>=0; i--) {
swap(data, i, 0);
heapAjust(data, 0, i-1);
}
return data;
}
計數排序
計數排序的核心在於將輸入的資料值轉化為鍵儲存在額外開闢的陣列空間中。
作為一種線性時間複雜度的排序,計數排序要求輸入的資料必須是有確定範圍的整數。
計數排序是分散式排序方法。分散式排序使用已組織好的輔助資料結構(稱為桶),然後進行合併,得到排好序的陣列。
計數排序使用一個用來儲存每個元素在原始陣列中出現次數的臨時陣列。在所有元素都計數完成後,臨時陣列已排好序並可迭代以構建排序後的結果陣列。
它是用來排序整數的優秀演算法(它是一個整數排序演算法),時間複雜度為O(n+k),其中k是臨時計數陣列的大小;但是,它確實需要更多的記憶體來存放臨時陣列。
function countingSort(arr, maxValue) {
var bucket = new Array(maxValue+1),
sortedIndex = 0;
arrLen = arr.length,
bucketLen = maxValue + 1;
for (var i = 0; i < arrLen; i++) {
if (!bucket[arr[i]]) {
bucket[arr[i]] = 0;
}
bucket[arr[i]]++;
}
for (var j = 0; j < bucketLen; j++) {
while(bucket[j] > 0) {
arr[sortedIndex++] = j;
bucket[j]--;
}
}
return arr;
}
桶排序
桶排序是計數排序的升級版。它利用了函式的對映關係,高效與否的關鍵就在於這個對映函式的確定。
為了使桶排序更加高效,我們需要做到這兩點:
1.在額外空間充足的情況下,儘量增大桶的數量
2.使用的對映函式能夠將輸入的N個數據均勻的分配到K個桶中
同時,對於桶中元素的排序,選擇何種比較排序演算法對於效能的影響至關重要。
-
什麼時候最快(Best Cases):
當輸入的資料可以均勻的分配到每一個桶中 -
什麼時候最慢(Worst Cases):
當輸入的資料被分配到了同一個桶中 -
程式碼實現:
function bucketSort(arr, bucketSize) {
if (arr.length === 0) {
return arr;
}
var i;
var minValue = arr[0];
var maxValue = arr[0];
for (i = 1; i < arr.length; i++) {
if (arr[i] < minValue) {
minValue = arr[i]; //輸入資料的最小值
} else if (arr[i] > maxValue) {
maxValue = arr[i]; //輸入資料的最大值
}
}
//桶的初始化
var DEFAULT_BUCKET_SIZE = 5; //設定桶的預設數量為5
bucketSize = bucketSize || DEFAULT_BUCKET_SIZE;
var bucketCount = Math.floor((maxValue - minValue) / bucketSize) + 1;
var buckets = new Array(bucketCount);
for (i = 0; i < buckets.length; i++) {
buckets[i] = [];
}
//利用對映函式將資料分配到各個桶中
for (i = 0; i < arr.length; i++) {
buckets[Math.floor((arr[i] - minValue) / bucketSize)].push(arr[i]);
}
arr.length = 0;
for (i = 0; i < buckets.length; i++) {
insertionSort(buckets[i]); //對每個桶進行排序,這裡使用了插入排序
for (var j = 0; j < buckets[i].length; j++) {
arr.push(buckets[i][j]);
}
}
return arr;
}
基數排序
基數排序有兩種方法:
MSD 從高位開始進行排序
LSD 從低位開始進行排序
- 程式碼實現(LSD):
// LSD Radix Sort
// 比較整型
var counter = [];
// 定義一個函式 arr待排序陣列 maxDigit陣列中最大數的位數,例如[1,10,100]的maxDigit為3
function radixSort(arr, maxDigit) {
var mod = 10;
var dev = 1;
for (var i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
// 把待排序的陣列 arr 中的每一位整數,插入對應的容器
for(var j = 0; j < arr.length; j++) {
// 從個位開始,得到陣列中每個數的每一位並儲存在 bucket 變數中
// bucket 變數的值可能為 0 1 2 3 4 5 6 7 8 9
// 與之對應的 counter[bucket] 容器為 0 1 2 3 4 5 6 7 8 9
var bucket = parseInt((arr[j] % mod) / dev);
// 如果目前 bucket 變數的值對應的 counter[bucket] 容器還不存在(未初始化),則建立(初始化)一個新的空容器
if(counter[bucket]==null) {
counter[bucket] = [];
}
// 現在把這個 bucket 變數的值插入對應的 counter[bucket] 容器的尾部
counter[bucket].push(arr[j]);
}
// 把 counter[bucket] 容器裡的數依次取出
var pos = 0;
for(var j = 0; j < counter.length; j++) {
// 定義一個變數 value 用於儲存conter[j].shift
var value = null;
if(counter[j]!=null) {
while ((value = counter[j].shift()) != null) {
arr[pos++] = value;
}
}
}
}
return arr;
}
- 基數排序 , 計數排序 與桶排序的比較:
這三種排序演算法都利用了桶的概念,但對桶的使用方法上有明顯差異:
1.基數排序:根據鍵值的每位數字來分配桶
2.計數排序:每個桶只儲存單一鍵值
3.桶排序:每個桶儲存一定範圍的數值
本文來自部落格園,作者:starking_front-end,轉載請註明原文連結:https://www.cnblogs.com/starking-985/p/15966089.html