JS家的排序算法 十大經典算法排序總結

2021-02-18 野狗


✦ ✦ ✦ ✦ ✦ ✦ ✦ ✦

引子

有句話怎麼說來著:

雷鋒推倒雷峰塔,Java implements JavaScript.

當年,想憑藉抱Java大腿火一把而不惜把自己名字給改了的JavaScript(原名LiveScript),如今早已光芒萬丈。node JS的出現更是讓JavaScript可以前後端通吃。雖然Java依然制霸企業級軟體開發領域(C/C + +的大神們不要打我。。。),但在Web的江湖,JavaScript可謂風頭無兩,坐上了頭把交椅。

然而,在傳統的計算機算法和數據結構領域,大多數專業教材和書籍的默認語言都是Java或者C/C+ +。這給最近想惡補算法和數據結構知識的我造成了一定困擾,因為我想尋找一本以JavaScript為默認語言的算法書籍。當我了解到O』REILLY家的動物叢書系列裡有一本叫做《數據結構與算法JavaScript描述》時,便興奮的花了兩天時間把這本書從頭到尾讀了一遍。它是一本很好的針對前端開發者們的入門算法書籍,可是,它有一個很大的缺陷,就是裡面有很多明顯的小錯誤,明顯到就連我這種半路出家的程序猿都能一眼看出來。還有一個問題是,很多重要的算法和數據結構知識並沒有在這本書裡被提到。這些問題對於作為一個晚期強迫症患者的我來說簡直不能忍。於是乎,一言不合我就決定自己找資料總結算法。那麼,我就從算法領域裡最基礎的知識點——排序算法總結起好了。

我相信以下的代碼裡一定會有某些bug或錯誤或語法不規範等問題是我自己無法發現的,所以敬請各位大神能夠指出錯誤,因為只有在不斷改錯的道路上我才能取得長久的進步。

✦ ✦ ✦

十大經典算法排序總結對比

一張圖概括:


主流排序算法概覽

名詞解釋:

n: 數據規模

k:「桶」的個數

In-place: 佔用常數內存,不佔用額外內存

Out-place: 佔用額外內存

穩定性:排序後2個相等鍵值的順序和排序之前它們的順序相同

 1   冒泡排序(Bubble Sort)

冒泡排序須知:

作為最簡單的排序算法之一,冒泡排序給我的感覺就像Abandon在單詞書裡出現的感覺一樣,每次都在第一頁第一位,所以最熟悉。。。冒泡排序還有一種優化算法,就是立一個flag,當在一趟序列遍歷中元素沒有發生交換,則證明該序列已經有序。但這種改進對於提升性能來說並沒有什麼太大作用。。。

什麼時候最快(Best Cases):

當輸入的數據已經是正序時(都已經是正序了,我還要你冒泡排序有何用啊。。。。)

什麼時候最慢(Worst Cases):

當輸入的數據是反序時(寫一個for循環反序輸出數據不就行了,幹嘛要用你冒泡排序呢,我是閒的嗎。。。)

冒泡排序動圖演示:


Bubble Sort 動圖演示

冒泡排序JavaScript代碼實現:

(上下滑動看代碼)

function bubbleSort(arr) {

    var len = arr.length;

    for (var i = 0; i < len; i++) {

        for (var j = 0; j < len - 1 - i; j++) {

            if (arr[j] > arr[j+1]) {        //相鄰元素兩兩對比

                var temp = arr[j+1];        //元素交換

                arr[j+1] = arr[j];

                arr[j] = temp;

            }

        }

    }

    return arr;

}

 2   選擇排序(Selection Sort)

選擇排序須知:

在時間複雜度上表現最穩定的排序算法之一,因為無論什麼數據進去都是O(n²)的時間複雜度。。。所以用到它的時候,數據規模越小越好。唯一的好處可能就是不佔用額外的內存空間了吧。

選擇排序動圖演示:


Selection Sort 動圖演示

選擇排序JavaScript代碼實現:

(上下滑動看代碼)

function selectionSort(arr) {

    var len = arr.length;

    var minIndex, temp;

    for (var i = 0; i < len - 1; i++) {

        minIndex = i;

        for (var j = i + 1; j < len; j++) {

            if (arr[j] < arr[minIndex]) {     //尋找最小的數

                minIndex = j;                 //將最小數的索引保存

            }

        }

        temp = arr[i];

        arr[i] = arr[minIndex];

        arr[minIndex] = temp;

    }

    return arr;

}

 3   插入排序(Insertion Sort)

插入排序須知:

插入排序的代碼實現雖然沒有冒泡排序和選擇排序那麼簡單粗暴,但它的原理應該是最容易理解的了,因為只要打過撲克牌的人都應該能夠秒懂。當然,如果你說你打撲克牌摸牌的時候從來不按牌的大小整理牌,那估計這輩子你對插入排序的算法都不會產生任何興趣了。。。

插入排序和冒泡排序一樣,也有一種優化算法,叫做拆半插入。對於這種算法,得了懶癌的我就套用教科書上的一句經典的話吧:感興趣的同學可以在課後自行研究。。。

插入排序動圖演示:


Insertion Sort 動圖演示

插入排序JavaScript代碼實現:

(上下滑動看代碼)

function insertionSort(arr) {

    var len = arr.length;

    var preIndex, current;

    for (var i = 1; i < len; 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;

}

 4   希爾排序(Shell Sort)

希爾排序須知:

希爾排序是插入排序的一種更高效率的實現。它與插入排序的不同之處在於,它會優先比較距離較遠的元素。希爾排序的核心在於間隔序列的設定。既可以提前設定好間隔序列,也可以動態的定義間隔序列。動態定義間隔序列的算法是《算法(第4版》的合著者Robert Sedgewick提出的。在這裡,我就使用了這種方法。

希爾排序JavaScript代碼實現:

(上下滑動看代碼)

function shellSort(arr) {

    var len = arr.length,

        temp,

        gap = 1;

    while(gap < len/3) {          //動態定義間隔序列

        gap =gap*3+1;

    }

    for (gap; gap > 0; gap = Math.floor(gap/3)) {

        for (var i = gap; i < len; i++) {

            temp = arr[i];

            for (var j = i-gap; j >= 0 && arr[j] > temp; j-=gap) {

                arr[j+gap] = arr[j];

            }

            arr[j+gap] = temp;

        }

    }

    return arr;

}

 5   歸併排序(Merge Sort)

歸併排序須知:

作為一種典型的分而治之思想的算法應用,歸併排序的實現由兩種方法:

自上而下的遞歸(所有遞歸的方法都可以用迭代重寫,所以就有了第2種方法)

自下而上的迭代

在《數據結構與算法JavaScript描述》中,作者給出了自下而上的迭代方法。但是對於遞歸法,作者卻認為:

However, it is not possible to do so in JavaScript, as the recursion goes too deep

for the language to handle.

然而,在 JavaScript 中這種方式不太可行,因為這個算法的遞歸深度對它來講太深了。

說實話,我不太理解這句話。意思是JavaScript編譯器內存太小,遞歸太深容易造成內存溢出嗎?還望有大神能夠指教。

和選擇排序一樣,歸併排序的性能不受輸入數據的影響,但表現比選擇排序好的多,因為始終都是O(n log n)的時間複雜度。代價是需要額外的內存空間。

歸併排序動圖演示:


Merge Sort 動圖演示

歸併排序JavaScript代碼實現:

(上下滑動看代碼)

function mergeSort(arr) {  //採用自上而下的遞歸方法

    var len = arr.length;

    if(len < 2) {

        return arr;

    }

    var middle = Math.floor(len / 2),

        left = arr.slice(0, middle),

        right = arr.slice(middle);

    return merge(mergeSort(left), mergeSort(right));

}

function merge(left, right)

{

    var result = [];

    while (left.length && right.length) {

        if (left[0] <= right[0]) {

            result.push(left.shift());

        } else {

            result.push(right.shift());

        }

    }

    while (left.length)

        result.push(left.shift());

    while (right.length)

        result.push(right.shift());

    return result;

}

 6   快速排序(Quick Sort)

快速排序須知:

又是一種分而治之思想在排序算法上的典型應用。本質上來看,快速排序應該算是在冒泡排序基礎上的遞歸分治法。

快速排序的名字起的是簡單粗暴,因為一聽到這個名字你就知道它存在的意義,就是快,而且效率高! 它是處理大數據最快的排序算法之一了。雖然Worst Case的時間複雜度達到了O(n²),但是人家就是優秀,在大多數情況下都比平均時間複雜度為O(n log n) 的排序算法表現要更好,可是這是為什麼呢,我也不知道。。。好在我的強迫症又犯了,查了N多資料終於在《算法藝術與信息學競賽》上找到了滿意的答案:

快速排序的最壞運行情況是O(n²),比如說順序數列的快排。但它的平攤期望時間是O(n log n) ,且O(n log n)記號中隱含的常數因子很小,比複雜度穩定等於O(n log n)的歸併排序要小很多。所以,對絕大多數順序性較弱的隨機數列而言,快速排序總是優於歸併排序。

快速排序動圖演示:


Quick Sort 動圖演示

快速排序JavaScript代碼實現:

(上下滑動看代碼)

function quickSort(arr, left, right) {

    var len = arr.length,

        partitionIndex,

        left = typeof left != 'number' ? 0 : left,

        right = typeof right != 'number' ? len - 1 : right;

    if (left < right) {

        partitionIndex = partition(arr, left, right);

        quickSort(arr, left, partitionIndex-1);

        quickSort(arr, partitionIndex+1, right);

    }

    return arr;

}

function partition(arr, left ,right) {     //分區操作

    var pivot = left,                      //設定基準值(pivot)

        index = pivot + 1;

    for (var i = index; i <= right; i++) {

        if (arr[i] < arr[pivot]) {

            swap(arr, i, index);

            index++;

        }        

    }

    swap(arr, pivot, index - 1);

    return index-1;

}

function swap(arr, i, j) {

    var temp = arr[i];

    arr[i] = arr[j];

    arr[j] = temp;

}

 7   堆排序(Heap Sort)

堆排序須知:

堆排序可以說是一種利用堆的概念來排序的選擇排序。分為兩種方法:

大頂堆:每個節點的值都大於或等於其子節點的值,在堆排序算法中用於升序排列

小頂堆:每個節點的值都小於或等於其子節點的值,在堆排序算法中用於降序排列

堆排序動圖演示:


Heap Sort 動圖演示

堆排序JavaScript代碼實現:

(上下滑動看代碼)

var len;    //因為聲明的多個函數都需要數據長度,所以把len設置成為全局變量

function buildMaxHeap(arr) {   //建立大頂堆

    len = arr.length;

    for (var i = Math.floor(len/2); i >= 0; i--) {

        heapify(arr, i);

    }

}

function heapify(arr, i) {     //堆調整

    var left = 2 * i + 1,

        right = 2 * i + 2,

        largest = i;

    if (left < len && arr[left] > arr[largest]) {

        largest = left;

    }

    if (right < len && arr[right] > arr[largest]) {

        largest = right;

    }

    if (largest != i) {

        swap(arr, i, largest);

        heapify(arr, largest);

    }

}

function swap(arr, i, j) {

    var temp = arr[i];

    arr[i] = arr[j];

    arr[j] = temp;

}

function heapSort(arr) {

    buildMaxHeap(arr);

    for (var i = arr.length-1; i > 0; i--) {

        swap(arr, 0, i);

        len--;

        heapify(arr, 0);

    }

    return arr;

}

 8   計數排序(Counting Sort)

計數排序須知:

計數排序的核心在於將輸入的數據值轉化為鍵存儲在額外開闢的數組空間中。

作為一種線性時間複雜度的排序,計數排序要求輸入的數據必須是有確定範圍的整數。

計數排序動圖演示:


Counting Sort 動圖演示

計數排序JavaScript代碼實現:

(上下滑動看代碼)

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;

}

 9   桶排序(Bucket Sort)

桶排序須知:

桶排序是計數排序的升級版。它利用了函數的映射關係,高效與否的關鍵就在於這個映射函數的確定。

為了使桶排序更加高效,我們需要做到這兩點:

在額外空間充足的情況下,儘量增大桶的數量

使用的映射函數能夠將輸入的N個數據均勻的分配到K個桶中

同時,對於桶中元素的排序,選擇何種比較排序算法對於性能的影響至關重要。

什麼時候最快(Best Cases):

當輸入的數據可以均勻的分配到每一個桶中

什麼時候最慢(Worst Cases):

當輸入的數據被分配到了同一個桶中

桶排序JavaScript代碼實現:

(上下滑動看代碼)

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;

}

 10   基數排序(Radix Sort)

基數排序須知:

基數排序有兩種方法:

MSD 從高位開始進行排序

LSD 從低位開始進行排序

基數排序 vs 計數排序 vs 桶排序

這三種排序算法都利用了桶的概念,但對桶的使用方法上有明顯差異:

基數排序:根據鍵值的每位數字來分配桶

計數排序:每個桶只存儲單一鍵值

桶排序:每個桶存儲一定範圍的數值

LSD基數排序動圖演示:


Radix Sort 動圖演示

基數排序JavaScript代碼實現:

(上下滑動看代碼)

//LSD Radix Sort

var counter = [];

function radixSort(arr, maxDigit) {

    var mod = 10;

    var dev = 1;

    for (var i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {

        for(var j = 0; j < arr.length; j++) {

            var bucket = parseInt((arr[j] % mod) / dev);

            if(counter[bucket]==null) {

                counter[bucket] = [];

            }

            counter[bucket].push(arr[j]);

        }

        var pos = 0;

        for(var j = 0; j < counter.length; j++) {

            var value = null;

            if(counter[j]!=null) {

                while ((value = counter[j].shift()) != null) {

                      arr[pos++] = value;

                }

          }

        }

    }

    return arr;

}

✦ ✦ ✦

寫在最後

排序算法實在是博大精深,還有hin多hin多我沒有總結到或者我自己還沒弄明白的算法,僅僅是總結這十種排序算法都把我寫哭了。。。

因此,以後如果我掌握了更多的排序姿勢,我一定還會回來的!

✦ ✦ ✦ ✦ ✦ ✦ ✦ ✦

延伸閱讀(點擊標題):

坐在馬桶上看算法:鄰居好說話,冒泡排序

【啊哈!算法】最快最簡單的排序:桶排序

作者:不是小羊的肖恩

原文:http://www.jianshu.com/p/1b4068ccd505


點擊「閱讀原文」,看更多

精選文章

↓↓↓

相關焦點

  • 十大經典排序算法最強總結
    排序算法屬於經典基礎算法基本功,筆試面試基本都會涉及和考察的,有原題也有變化,不過基礎的幾大排序算法還是得儘可能熟悉
  • 十大經典排序算法之希爾排序算法
    希爾排序之前我們講過冒泡排序、選擇排序、插入排序,它們的時間複雜度都是 主要思想希爾排序的思想簡單點說就是有跨度的插入排序,這個跨度會逐漸變小,直到變為 1,變為 1 的時候也就是我們之前講的簡單插入排序了。規範點的描述就是,選擇一組遞減的整數作為增量序列,最小的增量必須為 1。
  • 十大經典排序算法(下)
    (Heapsort)是指利用堆這種數據結構所設計的一種排序算法。作為一種線性時間複雜度的排序,計數排序要求輸入的數據必須是有確定範圍的整數。計數排序(Counting sort)是一種穩定的排序算法。計數排序使用一個額外的數組C,其中第i個元素是待排序數組A中值等於i的元素的個數。然後根據數組C來將A中的元素排到正確的位置。它只能對整數進行排序。
  • 十大經典排序算法最強總結(含JAVA代碼實現)
    所以我根據這幾天看的文章,整理了一個較為完整的排序算法總結,本文中的所有算法均有JAVA實現,經本人調試無誤後才發出,如有錯誤,請各位前輩指出。0、排序算法說明0.1 排序的定義對一序列對象根據某個關鍵字進行排序。
  • JavaScript 數據結構與算法之美 - 十大經典排序算法匯總(上)
    筆者寫的 JavaScript 數據結構與算法之美系列用的語言是 JavaScript ,旨在入門數據結構與算法和方便以後複習。文中包含了十大經典排序算法的思想、代碼實現、一些例子、複雜度分析、動畫、還有算法可視化工具。
  • 八大經典排序算法詳解
    我把最經典的八大排序算法原理和代碼也都整理出來了,內容如下,希望對大家能有所幫助。插入排序•基本思想:每步將一個待排序的紀錄,按其關鍵碼值的大小插入前面已經排序的文件中適當位置上,直到全部插入完為止。•算法適用於少量數據的排序,時間複雜度為O(n^2)。是穩定的排序方法。
  • 經典排序算法三 選擇排序(JAVA實現)
    選擇排序原理:每一趟從待排序的記錄中選出最小的元素,順序放在已排好序的序列最後,直到全部記錄排序完畢。也就是:每一趟在n-i+1(i=1,2,…n-1)個記錄中選取關鍵字最小的記錄作為有序序列中第i個記錄。基於此思想的算法主要有簡單選擇排序、樹型選擇排序和堆排序。
  • 十大經典排序算法(動圖+代碼)
    下個月又將舉行信息奧賽了,一起來複習十大經典排序算法。
  • 程式設計師必備的幾種常見排序算法和搜索算法總結
    本文轉載自【微信公眾號:趣談前端,ID:beautifulFront】經微信公眾號授權轉載,如需轉載與原文作者聯繫前言最近為了鞏固一下自己的算法基礎,又把算法書裡的基本算法刷了一遍, 特地總結一下前端工程師需要了解的排序算法和搜索算法知識,雖然還有很多高深算法需要了解
  • 排序算法總結(1):冒泡排序
    冒泡排序是一種交換排序。什麼是交換排序呢?交換排序:兩兩比較待排序的關鍵字,並交換不滿足次序要求的那對數,直到整個表都滿足次序要求為止。算法思想它重複地走訪過要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把他們交換過來。
  • 經典排序算法全攻略
    這次跳槽季的面試中有沒有被算法難倒呢?不久前,一位程式設計師就因為算法問題被面試官吐槽了:清華就這水平?咳,清華的水平小編不敢妄作評論,不過算法是真心重要!就連李開復老師都曾經說過:「算法遠遠比日新月異語言重要得多。算法是本質,是『萬變不離其宗』的東西」。算法不僅作為理論基礎,微軟造作系統的研發更新、谷歌搜索、百度地圖引導等都需要強大的算法在背後支撐。
  • 排序算法之插入排序
    排序算法在編程領域中起著舉足輕重的作用,在目標檢索、機器學習、數值計算、圖像處理等領域有著廣泛地應用。為了追本溯源,公眾號特推出常用經典排序算法系列推文,讓小夥伴們深入了解排序算法的實現原理,同時也提升matlab編程能力。
  • 排序算法
    數據結構中的幾種排序算法:插入排序分為直接插入排序和希爾排序。
  • Python實現八大經典排序算法
    一、前言在面試題中可能會遇到排序算法,畢竟作為程式設計師內功心法,熟練掌握排序算法是很重要的,本文總結了八大經典排序算法的 Python 實現。排序算法是《數據結構與算法》中最基本的算法之一。排序算法可以分為內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部的排序記錄,在排序過程中需要訪問外存。常見的內部排序算法有:插入排序、希爾排序、選擇排序、冒泡排序、歸併排序、快速排序、堆排序、基數排序等。
  • 排序算法專題之插入排序
    經典插入排序例題1.使用插入排序算法對列表a升序排序。函數名:insert_sort(a)參數表:a -- 待排序列表。返回值:該方法沒有返回值,但是會對列表的對象進行升序排序。算法思想:外層循環控制待排序數組的左邊界,即a[i:]為待排序部分;內層循環掃描a[:i],將元素a[i]插入到適當位置def insert_sort (a):    for i in range(1, len(a)):        t, j =  ①                           while j >
  • 力扣(LeetCode)- 常見的排序算法總結
    如何分析一個排序算法好壞時間複雜度、比較和交換次數、原地排序(空間複雜度為(1))、排序穩定性(相等元素之間的位置先後順序不變)。有序度:是數組中具有有序關係的元素對的個數逆序度:和有序度相反。選擇排序是不穩定排序,所以應用最少。
  • 排序算法:歸併排序
    在日常開發中經常會遇到一類問題,就是對一個集合的數據進行排序掌握一些排序算法,對於日常開發是非常有幫助今天介紹一下歸併排序法01算法邏輯02時間複雜度由上圖邏輯可以得出,歸併排序的循環次數為由算法導論的主定理可以推導出,可見之前的文章(文末有連結)歸併排序的時間複雜度為03空間複雜度由上圖邏輯可以得出,歸併排序每次分解都是要復一份新的數據片段
  • 程式設計師必知必會的十大排序算法
    緒論身為程式設計師,十大排序是所有合格程式設計師所必備和掌握的,並且熱門的算法比如快排、歸併排序還可能問得比較細緻,對算法性能和複雜度的掌握都有要求。本文將帶你捋一捋常見的十大排序算法,讓你輕輕鬆鬆掌握!首先對於排序來說,大多數人對排序的概念停留在冒泡排序或者JDK中的Arrays.sort(),手寫各種排序對很多人來說都是一種奢望,更別說十大排序算法了。對於排序的分類,可以根據不同的維度比如複雜度、內外部、比較非比較等維度來分類。
  • 排序算法:快速排序
    在日常開發中經常會遇到一類問題,就是對一個集合的數據進行排序掌握一些排序算法,對於日常開發是非常有幫助今天介紹一下快速排序法01算法邏輯02時間複雜度由上圖邏輯可以得出,參照遞歸表達式漸進複雜度分析快速排序的時間複雜度遞歸表達式為,其中a=2,b=2,d=1由算法導論的主定理可以推導出,快速排序的時間複雜度為03空間複雜度由上圖邏輯可以得出
  • 排序算法:選擇排序
    在日常開發中經常會遇到一類問題,就是對一個集合的數據進行排序掌握一些排序算法,對於日常開發是非常有幫助的今天介紹一下選擇排序法01算法邏輯02時間複雜度由上圖邏輯可以得出,選擇排序的循環次數為由循環次數可以得出,選擇排序的時間複雜度為03空間複雜度由上圖邏輯可以得出