六種排序算法的JavaScript實現

前端閱讀室

本文將介紹數據排序的基本算法和高級算法。這些算法都只依賴數組來存儲數據。javascript

數組測試平臺

首先咱們構造一個數組測試平臺類html

function CArray(numElements) {
    this.dataStore = [];
    this.numElements = numElements;
    this.toString = toString;
    this.clear = clear;
    this.setData = setData;
    this.swap = swap;
}
function setData() {
    for (var i = 0; i < this.numElements; ++i) {
    this.dataStore[i] = Math.floor(Math.random() * (this.numElements + 1));
    }
}
function clear() {
    for (var i = 0; i < this.numElements; ++i) {
    this.dataStore[i] = 0;
    }
}
function toString() {
    var restr = "";
    for (var i = 0; i < this.numElements; ++i) {
    restr += this.dataStore[i] + " ";
    if (i > 0 && i % 10 == 0) {
        restr += "\n";
    }
    }
    return restr;
}
function swap(arr, index1, index2) {
    var temp = arr[index1];
    arr[index1] = arr[index2];
    arr[index2] = temp;
}
複製代碼

使用測試平臺類前端

var numElements = 100;
var myNums = new CArray(numElements);
myNums.setData();
console.log(myNums.toString());
複製代碼

基本排序算法

這些算法很是逼真地模擬了人類在現實生活中對數據的排序。java

冒泡排序

它是最慢的排序算法之一,但也是一種最容易實現的排序算法。 之因此叫冒泡排序是由於使用這種排序算法排序時,數據值會像氣泡同樣從數組的一端漂浮到另外一端。假設正在將一組數字按照升序排列,較大的值會浮動到數組的右側,而較小的值則會浮動到數組的左側。之因此會產生這種現象是由於算法會屢次在數組中移動,比較相鄰的數據,當左側值大於右側值時將它們進行互換。算法

冒泡排序的代碼shell

function bubbleSort() {
    var numElements = this.dataStore.length;
    for (var outer = numElements; outer >= 2; --outer) {
        for (var inner = 0; inner < outer - 1; ++inner) {
            if (this.dataStore[inner] > this.dataStore[inner + 1]) {
                this.swap(this.dataStore, inner, inner + 1);
            }
        }
    }
}
複製代碼

排序過程(手動輸入的測試數據) 數組

avatar
外層循環限定了未排序的範圍(從numElements到2),內層循環從左側的數據開始逐步比較交換,使得未排序範圍中最大的數移動到了最右側,外層排序範圍不斷縮小,直到還剩兩個未排序元素時再比較交換便完成了排序

選擇排序

選擇排序從數組的開頭開始,將第一個元素和其餘元素進行比較。檢查完全部元素後,最小的元素會被放到數組的第一個位置,而後算法會從第二個位置繼續。這個過程一直進行,當進行到數組的倒數第二個位置時,全部數據便完成了排序。數據結構

function selectionSort() {
    var min;
    for (var outer = 0; outer <= this.dataStore.length - 2; ++outer) {
        min = outer;
        for (var inner = outer + 1; inner <= this.dataStore.length - 1; ++inner) {
            if (this.dataStore[inner] < this.dataStore[min]) {
                min = inner;
            }
        }
        swap(this.dataStore, outer, min);
    }
}
複製代碼

排序過程 less

avatar

插入排序

插入排序有兩個循環。外循環將數組元素挨個移動,而內循環則對外循環中選中的元素進行比較。若是外循環中選中的元素比內循環中選中的元素小,那麼數組元素會向右移動,爲內循環中的這個元素騰出位置。dom

function insertionSort() {
    var temp, inner;
    for (var outer = 1; outer <= this.dataStore.length - 1; ++outer) {
    temp = this.dataStore[outer];
    inner = outer;
    while (inner > 0 && (this.dataStore[inner - 1] >= temp)) {
        this.dataStore[inner] = this.dataStore[inner - 1];
        --inner;
    }
    this.dataStore[inner] = temp;
    }
}
複製代碼

基本排序算法的計時比較

10000個隨機數測試

bubbleSort();// 100ms左右
selectionSort();// 50ms左右
insertionSort();// 27ms左右
複製代碼

選擇排序和插入排序要比冒泡排序快,插入排序是這三種算法中最快的。

高級排序算法

希爾排序

希爾排序在插入排序的基礎上作了很大的改善。它會首先比較距離較遠的元素,而非相鄰的元素。這樣可使離正確位置很遠的元素更快地回到合適的位置。當開始用這個算法遍歷數據集時,全部元素之間的距離會不斷減少,直處處理到數據集的末尾,這時算法比較的就是相鄰元素了。希爾排序的工做原理是,經過定義一個間隔序列來表示排序過程當中進行比較的元素之間有多遠的間隔。咱們能夠動態定義間隔序列,不過大部分場景算法要用到的間隔序列能夠提早定義好。Marchin Ciura在發表的一篇論文中定義的間隔序列爲:701,301,132,57,23,10,4,1。這裏咱們經過一個小的數據集合來看看這個算法是怎麼運行的。

avatar

function shellsort() {
    var temp;
    for (var g = 0; g < this.gaps.length; ++g) {
    for (var i = this.gaps[g]; i < this.dataStore.length; ++i) {
        temp = this.dataStore[i];
        for (var j = i; j >= this.gaps[g] && this.dataStore[j-this.gaps[g]] > temp; j -= this.gaps[g]) {
        this.dataStore[j] = this.dataStore[j - this.gaps[g]];
        }
        this.dataStore[j] = temp;
    }
    }
}
複製代碼

咱們須要在CArray類中增長對間隔序列的定義:

this.gaps = [5,3,1];
複製代碼

計算動態間隔序列

Sedgewick的算法經過下面的代碼片斷來決定初始間隔值:

var N = this.dataStore.length;
var h = 1;
while (h < N/3) {
    h = 3 * h + 1;
}
複製代碼

間隔值肯定好後,這個函數就能夠像以前定義的shellsort()函數同樣運行了,惟一的區別是,回到外循環以前的最後一條語句會計算一個新的間隔值:

h = (h-1)/3;
複製代碼

動態計算間隔序列的希爾排序

function shellsort1() {
    var N = this.dataStore.length;
    var h = 1;
    while (h < N/3) {
        h = 3 * h + 1;
    }
    while (h >= 1) {
        for (var i = h; i < N; i ++) {
            for (var j = i; j >= h && this.dataStore[j] < this.dataStore[j-h]; j -= h) {
                this.swap(this.dataStore, j, j - h);
            }
        }
        h = (h-1)/3;
    }
}
複製代碼

對於10000個隨機數的排序測試:

myNums.shellsort(); // 20ms左右
myNums.shellsort1(); // 8ms左右
複製代碼

歸併排序

歸併排序把一系列排好序的子序列合併成一個大的完整有序序列。咱們須要兩個排好序的子數組,而後經過比較數據大小,從最小的數據開始插入,最後合併獲得第三個數組。然而,在實際狀況中,歸併排序還有一些問題,咱們須要更大的空間來合併存儲兩個子數組。

自底向上的歸併排序 一般來說,歸併排序會使用遞歸的算法來實現。然而,在JavaScript中這種方式不太可行,由於遞歸的深度太深了。因此,咱們使用一種非遞歸的方式來實現這個算法,這種策略稱爲自底向上的歸併排序。 這個算法首先將數據集分解爲一組只有一個元素的數組。而後經過建立一組左右子數組將它們慢慢合併起來,每次合併都保存一部分排好序的數據,直到最後剩下的這個數組全部的數據都已完美排序。

avatar
算法代碼

function mergeSort() {
    var arr = this.dataStore;
    if (arr.length < 2) {
    return;
    }
    var step = 1;
    var left, right;
    while (step < arr.length) {
    left = 0;
    right = step;
    while (right + step <= arr.length) {
        this.mergeArrays(arr, left, left+step, right, right+step);
        left = right + step;
        right = left + step;
    }
    if (right < arr.length) {
        this.mergeArrays(arr, left, left+step, right, arr.length);
    }
    step *= 2;
    }
}

function mergeArrays(arr, startLeft, stopLeft, startRight, stopRight) {
    var rightArr = new Array(stopRight - startRight + 1);
    var leftArr = new Array(stopLeft - startLeft + 1);
    k = startRight;
    for (var i = 0; i < (rightArr.length-1); ++i) {
    rightArr[i] = arr[k];
    ++k;
    }
    k = startLeft;
    for (var i = 0; i < (leftArr.length-1); ++i) {
    leftArr[i] = arr[k];
    ++k;
    }
    rightArr[rightArr.length - 1] = Infinity;
    leftArr[leftArr.length - 1] = Infinity;
    var m = 0;
    var n = 0;
    for (var k = startLeft; k < stopRight; ++k) {
    if (leftArr[m] <= rightArr[n]) {
        arr[k] = leftArr[m];
        m++;
    } else {
        arr[k] = rightArr[n];
        n++;
    }
    }
}
複製代碼

快速排序

快速排序是處理大數據集最快的排序算法之一。它是一種分而治之的算法,經過遞歸的方法將數據依次分解爲包含較小元素和較大元素的不一樣子序列。該算法不斷重複這個步驟直到全部數據都是有序的。 這個算法首先要在列表中選擇一個元素做爲基準值(pivot)。數據排序圍繞基準值進行,將列表中小於基準值的元素移到數組的底部,將大於基準值的元素移到數組的頂部。

avatar

算法僞代碼

  1. 選擇一個基準元素,將列表分隔成兩個子序列
  2. 對列表從新排序,將全部小於基準值的元素放在基準值的前面,全部大於基準值得元素放在基準值的後面
  3. 分別對較小元素的子序列和較大元素的子序列重複步驟1和2 程序以下:
function qSort(list) {
    var list;
    if (list.length == 0) {
        return [];
    }
    var lesser = [];
    var greater = [];
    var pivot = list[0];
    for (var i = 1; i < list.length; i ++) {
        if (list[i] < pivot) {
        lesser.push(list[i]);
        } else {
        greater.push(list[i]);
        }
    }
    return this.qSort(lesser).concat(pivot, this.qSort(greater));
}
複製代碼

在qSort函數返回前輸出排序過程

console.log(lesser.concat(pivot, greater).toString());
複製代碼

avatar
10000個隨機數排序測試

qSort(); // 17ms左右
複製代碼

快速排序算法很是適用於大型數據集合;在處理小數據集時性能反而會降低。

(附)測試平臺代碼

<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
  <script> function CArray(numElements) { // this.dataStore = [72, 54, 58, 30, 31, 78, 2, 77, 82, 72]; this.dataStore = []; // this.dataStore = [44, 75, 23, 43, 55, 12, 64, 77 ,33]; this.numElements = numElements; this.toString = toString; this.clear = clear; this.setData = setData; this.swap = swap; this.bubbleSort = bubbleSort; this.selectionSort = selectionSort; this.insertionSort = insertionSort; this.shellsort = shellsort; this.shellsort1 = shellsort1; this.mergeSort = mergeSort; this.mergeArrays = mergeArrays; this.qSort = qSort; this.gaps = [5, 3, 1]; } function setData() { for (var i = 0; i < this.numElements; ++i) { this.dataStore[i] = Math.floor(Math.random() * (this.numElements + 1)); } } function clear() { for (var i = 0; i < this.numElements; ++i) { this.dataStore[i] = 0; } } function toString() { var restr = ""; for (var i = 0; i < this.numElements; ++i) { restr += this.dataStore[i] + " "; if (i > 0 && i % 10 == 0) { restr += "\n"; } } return restr; } function swap(arr, index1, index2) { var temp = arr[index1]; arr[index1] = arr[index2]; arr[index2] = temp; } function selectionSort() { var min; for (var outer = 0; outer <= this.dataStore.length - 2; ++outer) { min = outer; for (var inner = outer + 1; inner <= this.dataStore.length - 1; ++inner) { if (this.dataStore[inner] < this.dataStore[min]) { min = inner; } } swap(this.dataStore, outer, min); } } function bubbleSort() { var numElements = this.dataStore.length; for (var outer = numElements; outer >= 2; --outer) { for (var inner = 0; inner < outer - 1; ++inner) { if (this.dataStore[inner] > this.dataStore[inner + 1]) { this.swap(this.dataStore, inner, inner + 1); } } } } function insertionSort() { var temp, inner; for (var outer = 1; outer <= this.dataStore.length - 1; ++outer) { temp = this.dataStore[outer]; inner = outer; while (inner > 0 && (this.dataStore[inner - 1] >= temp)) { this.dataStore[inner] = this.dataStore[inner - 1]; --inner; } this.dataStore[inner] = temp; } } function shellsort() { var temp; for (var g = 0; g < this.gaps.length; ++g) { for (var i = this.gaps[g]; i < this.dataStore.length; ++i) { temp = this.dataStore[i]; for (var j = i; j >= this.gaps[g] && this.dataStore[j-this.gaps[g]] > temp; j -= this.gaps[g]) { this.dataStore[j] = this.dataStore[j - this.gaps[g]]; } this.dataStore[j] = temp; } } } function shellsort1() { var N = this.dataStore.length; var h = 1; while (h < N/3) { h = 3 * h + 1; } while (h >= 1) { for (var i = h; i < N; i ++) { for (var j = i; j >= h && this.dataStore[j] < this.dataStore[j-h]; j -= h) { this.swap(this.dataStore, j, j - h); } } h = (h-1)/3; } } function mergeSort() { var arr = this.dataStore; if (arr.length < 2) { return; } var step = 1; var left, right; while (step < arr.length) { left = 0; right = step; while (right + step <= arr.length) { this.mergeArrays(arr, left, left+step, right, right+step); left = right + step; right = left + step; } if (right < arr.length) { this.mergeArrays(arr, left, left+step, right, arr.length); } step *= 2; } } function mergeArrays(arr, startLeft, stopLeft, startRight, stopRight) { var rightArr = new Array(stopRight - startRight + 1); var leftArr = new Array(stopLeft - startLeft + 1); k = startRight; for (var i = 0; i < (rightArr.length-1); ++i) { rightArr[i] = arr[k]; ++k; } k = startLeft; for (var i = 0; i < (leftArr.length-1); ++i) { leftArr[i] = arr[k]; ++k; } rightArr[rightArr.length - 1] = Infinity; leftArr[leftArr.length - 1] = Infinity; var m = 0; var n = 0; for (var k = startLeft; k < stopRight; ++k) { if (leftArr[m] <= rightArr[n]) { arr[k] = leftArr[m]; m++; } else { arr[k] = rightArr[n]; n++; } } } function qSort(list) { var list; if (list.length == 0) { return []; } var lesser = []; var greater = []; var pivot = list[0]; for (var i = 1; i < list.length; i ++) { if (list[i] < pivot) { lesser.push(list[i]); } else { greater.push(list[i]); } } return this.qSort(lesser).concat(pivot, this.qSort(greater)); } var numElements = 200000; var myNums = new CArray(numElements); myNums.setData(); // console.log(myNums.toString()); var startTime = new Date().getTime(); // myNums.insertionSort();// 27ms左右 // myNums.bubbleSort();// 100ms左右 // myNums.selectionSort();// 50ms左右 // myNums.shellsort(); // 20ms左右 // myNums.shellsort1(); // 8ms左右 // myNums.mergeSort(); // 9ms左右 myNums.dataStore = myNums.qSort(myNums.dataStore); // 17ms左右 var endTime = new Date().getTime(); console.log('耗時: ' + (endTime - startTime) + '毫秒'); // console.log(myNums.toString()); </script>
</body>
</html>
複製代碼

參考

  1. 《數據結構與算法JavaScript描述》

前端閱讀室
相關文章
相關標籤/搜索