做爲一個前端,排序算法你有了解過嗎?

前言

前天看到知乎上有一篇文章在吐槽阮一峯老師的快速排序算法,這裏插一句題外話,我以爲人非聖賢孰能無過,盡信書不如無書,學習的過程也就是不斷髮現錯誤改正錯誤的過程,有人幫咱們糾正了這個錯誤咱們應該開心,可是我以爲不該該批判阮一峯老師,他也在不斷地學習,不斷地糾錯成長,因此你們都同樣,無所謂誤導,若是出錯的不是他,是更厲害的牛人呢?JavaScript的做者呢?因此你們都會出錯,咱們也應該多思考,抱着懷疑的態度接納,時刻思考這是否是最優的解法,還有沒有更好的呢,我想這纔是咱們應該作的.
而我,做爲一個計算機專業的前端,卻不能很好地實現各類思想的排序算法,我以爲很慚愧,因此我就抽時間仔細查看了<<數據結構與算法分析:C語言描述+中文版.pdf>>這本書,下面我就對我理解的各類思想的排序算法作一下總結,但願能夠給你們一些參考和收穫,若有不妥之處,煩請指出,也能夠分享大家以爲更好地想法,我以爲你們一塊兒學習一塊兒進步是最快樂的事~前端

1. 應當熟悉的相關概念

1.1 時間複雜度

(1) 時間複雜度的概念
算法的時間複雜度是一個函數,他定性地描述了某個算法的運行時間,經常使用大O符號,不包括這個函數的低階項和高階項係數.
(2) 計算方法算法

  • 通常狀況下,算法中基本操做的執行次數是問題規模n的某個函數,用T(n)表示,如有某個輔助函數f(n),使得當n趨近於無窮大時,T(n)/f(n)的極限值爲不爲零的常數,則f(n)是T(n)的同數量級函數,記做T(n) = O(f(n)),稱O(f(n))爲算法的漸進時間複雜度,簡稱爲時間複雜度.
  • 分析: 隨時模塊n的增大,算法的執行時間的增加率和f(n)的增加率成正比,因此f(n)越小,算法的時間複雜度越低,算法的效率越高.
  • 在計算時間複雜度的時候,先找出算法的基本操做,而後計算出基本操做的執行次數,找出T(n)的同數量級f(n)(它的同數量級通常有如下: 1, log₂n,n,nlog₂n,n的平方,n的三次方),若T(n) / f(n)求極限獲得一常數c,則時間複雜度T(n) = O(f(n)):

舉例以下:shell

for(i = 1; i<= n; i++) {
    for(j = 1; j <= n; j++) {
        c[i][j] = 0;   // 該步驟屬於基本操做的執行次數: n的平方
        for( k= 1;k <= n; k++) {
            c[i][j] += a[i][k] * b[k][j];   // 該步驟屬於基本操做的執行次數: n的三次方
        }
    }
}

咱們能夠獲得T(n) = n^3 + n^2,咱們能夠肯定n^3爲T(n)的同數量級,f(n)=n^3;而後T(n) / f(n) = 1 + 1/n 求極限爲常數1,因此該算法的時間複雜度爲:
T(n) = O(n^3);數組

說明: 爲了方便我接下來都是使用N來代指數組元素個數的.
個人建議: 我建議你們先看代碼,看不懂代碼的時候對着代碼看圖解,這樣方便更好的理解前端工程師

2. 排序算法

2.1 冒泡排序

2.1.1 主要思想:

冒泡排序的主要思想就是對一個長度爲n的數組進行遍歷, i從n-1到1的,數組的前i個元素的最大值放在i位置上,假想冒泡排序是一個豎着的水柱,遍歷的過程就是,大的值(重的)不斷沉下來,小的值(輕的)不斷浮上去,這樣遍歷結束後,每一個位置上的值都比他前面的值大,排序結束.數據結構

2.1.2 時間複雜度

最壞狀況下的時間複雜度: o(n^2);
最好狀況下的時間複雜度: o(n^2);dom

2.1.3 冒泡排序過程圖解:

冒泡排序圖解

2.1.4 代碼實現:

冒泡排序-非遞歸實現

function bubbleSort(arr) {
    for(var i = arr.length - 1; i > 1; i--) {
        for(var j=0; j < i; j++) {
            if(arr[j] > arr[j+1]) {
                var temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
    return arr;
}
var arr =  [34,8,64,51,32,21];
bubbleSort(arr);  // [8, 21, 32, 34, 51, 64]

冒泡排序-遞歸實現

function bubbleSort(arr, n) {
    if(n <= 1) {
        return arr;
    } else {
        for(var j=0; j < n - 1; j++) {
            if(arr[j] > arr[j+1]) {
                var temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
        return bubbleSort(arr, --n);
    }
}
var arr =  [34,8,64,51,32,21];
bubbleSort(arr, arr.length);  // [8, 21, 32, 34, 51, 64]

2.2 選擇排序

2.2.1 主要思想:

選擇排序的主要思想就是i 從 0 循環到到length - 1, 依次找出待排數組中從 i 到 length - 1 位置上的最小值放在 i 位置上.這樣最後獲得的數組就是排好序的數組了.函數

2.2.2 時間複雜度

最壞狀況下的時間複雜度: o(n^2);
最好狀況下的時間複雜度: o(n^2);post

2.2.3 選擇排序過程圖解:

選擇排序圖解

2.2.4 代碼實現:

選擇排序-非遞歸實現

function selectSort(arr) {
    var len = arr.length, min = 0;
    for(var i = 0;i < len - 1; i++) {
        min = i;   // 默認最小值的位置
        for(var j = i + 1; j < len; j++){
            if(arr[min] > arr[j]) {
                min = j;
            }
        }
        if(min != i) {
            var temp = arr[min];arr[min] = arr[i]; arr[i] = temp;
        }  
    }
    return arr;
}
var arr =  [34,8,64,51,32,21];
selectSort(arr);

選擇排序-遞歸實現

function selectSort(arr, n, min) {
    var len = arr.length;
    if(n < len - 1) {
        for(var j = n + 1; j < len; j++){
            if(arr[min] > arr[j]) {
                min = j;
            }
        }
        if(min != n) {
            var temp = arr[min];arr[min] = arr[n]; arr[n] = temp;
        } 
        n++;
        return  selectSort(arr, n, min);
    }
    return arr;
}
var arr =  [34,8,64,51,32,21];
selectSort(arr, 0, 0);

2.3 插入排序

2.3.1 主要思想:

插入排序有 n-1 趟排序組成,對於 i=1 到 i=n-1 趟,內層循環j從 i 到 1, 若是這其中有 j-1 位置上的元素大於 i 位置上的元素,就將該元素後移,知道條件不成立退出循環,這個時候大的值都被移動到後面了,j這個位置就是i位置上的元素應該在的位置.這樣保證了每次循環i位置前的全部元素都是排好序的,新的循環就只須要 將 i 位置上的元素 和 j-1(也就是初始的 i-1) 位置上的元素做比較,若是大於則無需再往前比較,若是小於則繼續往前比較後移.學習

2.3.2 時間複雜度

最壞狀況下的時間複雜度: o(n^2);
最好狀況下的時間複雜度: o(n);

2.3.3 排序過程圖解:

插入排序過程圖解

2.3.4 代碼實現

插入排序-非遞歸實現

function insertSort(arr) {
    var n = arr.length,temp = 0;
    for(var i = 1; i < n; i++) {
        temp = arr[i];
        for(j = i; j > 0 && arr[j-1] > temp; j--) {
            arr[j] = arr[j - 1];
        }
        arr[j] = temp;
    }
    return arr;
}
var arr =  [34,8,64,51,32,21];
insertSort(arr);  // [8, 21, 32, 34, 51, 64]

插入排序-遞歸實現

function insertSort(arr, n) {
    if(n > 0 && n < arr.length){
        var i = j = n, temp = arr[n];
        while(j > 0 && arr[j - 1] > temp) {
            arr[j] = arr[j - 1];
            j--;
        }
        arr[j] = temp;
        i++;
        return insertSort(arr, i);
    }
    return arr;
}
var arr =  [34,8,64,51,32,21];
insertSort(arr, 1); // [8, 21, 32, 34, 51, 64]; // 這個函數的調用限定了第一次調用n的值只能傳1

2.4 快速排序

顧名思義,快速排序是在實踐中最快的已知排序算法,它的平均運行時間是O(Nlog₂N).快速排序的關鍵在於樞紐元的選取,有一種比較推薦的選取方法就是選取左端的值,右端的值,中間位置的值(L(left + right) / 2)這三個數的中位數.舉例: 輸入爲8,1,4,9,6,3,5,2,7,0, 左邊元素8, 右邊元素0,中間位置上的元素L(0+9)/2是4位置上的元素是6,L在表示向下取整.
8,0,6的中位數,先排序0,6,8, 這三個數的中位數是6.

2.4.1 基本思想

經過一趟排序將要排序的部分分割成獨立的兩部分,其中一部分數據都比另一部分的全部數據都要小,而後再按此方法對這兩部分數據進行快速排序,整個排序過程能夠遞歸進行,依次達到整個數據變成有序序列.

實現步驟

  • 第一步: 設置兩個變量i,j,排序開始的時候: i=left,j=right-1,left和right分別表示要進行快速排序序列的起始索引和結束索引;
  • 第二步: 從數組中隨機選取一個元素,將其與arr[left]進行交換,即privot = arr[left],保證每一次的基準值都在序列的最左邊;
  • 第三步: 由j開始向前搜索,即由後開始向前搜索(j--),找到第一個小於privot 的值arr[j],將arr[i]與arr[j]互換;
  • 第四步: 從i開始向後搜索,即由前開始向後搜索(i++),找到一個大於privot 的arr[i],將arr[i]與arr[j]互換;
  • 第五步: 重複第三步和第四步,直到不知足i<j;
  • 第六步: 重複第二步到第四步,依次對i位置左右兩邊的元素進行快速排序,直到left大於等於right爲止.

2.4.2 時間複雜度:

平均狀況下的時間複雜度: o(nlog₂n);
最好狀況下的時間複雜度: o(n);

2.4.3 排序過程圖解

快速排序過程圖解

2.4.4 代碼實現:

快速排序-遞歸實現

function quickSort(arr, left, right) {
    if(left >= right) return;
    var i = left;
    var j = right - 1;
    var privot = arr[left];
    //console.log(privot);
    while(i < j) {
        while(i<j  && arr[j] >= privot) j--;
        arr[i] = arr[j];
        while(i<j && arr[i] <= privot) i++;
        arr[j]=arr[i];
    }
    arr[i]=privot;
    quickSort(arr, left, i);
    quickSort(arr, i+1, right);
}
var arr = [49,38,65,97,76,13,27,49,55,04];
quickSort(arr, 0, arr.length);

快速排序-非遞歸實現

function mainProduce(arr, left, right) {
        var i = left, j = right - 1;
        var rendomIndex = Math.floor(Math.random() * (j - i)) + left;
        var temp = arr[left];arr[left] = arr[rendomIndex];arr[rendomIndex] = temp;
        var privot = arr[left];
        while(i < j) {
            while(i<j  && arr[j] >= privot) j--;
            var temp = arr[i];arr[i] = arr[j];arr[j] = temp;
            while(i<j && arr[i] <= privot) i++;
            var temp = arr[j];arr[j] = arr[i];arr[i] = temp;
        }
        arr[i]=privot;
        return i;
    }
    function quickSort(arr, left, right) {
        var s = [];
        if(left < right) {
            var mid = mainProduce(arr, left, right);
            if(mid > left + 1) {
                s.push(left);s.push(mid);
            }
            if(mid < right - 1) {
                s.push(mid + 1);s.push(right);
            }
            
            while(s.length !== 0) {
                right = s.pop();
                left = s.pop();
                mid = mainProduce(arr, left, right);
                if(mid > left + 1) {
                    s.push(left);s.push(mid);
                }
                if(mid < right - 1) {
                    s.push(mid + 1);s.push(right);
                }
            }
        }
        return arr;
    }
    var arr = [49,38,65,97,76,13,27,49,55,04];
    quickSort(arr, 0, arr.length);

2.5 希爾排序

2.5.1 主要思想

希爾排序是把記錄按照下標的必定增量分組,對每組使用插入排序;隨着增量逐漸減小,分割的數組愈來愈大,當增量減至1,整個數組排序完成,算法終止.

主要步驟

  • 第一步: 選取一個增量d,初始值是Math.floor(len/2);
  • 第二步: 而後將數組中間隔爲增量d的組成新的分組,而後對這個分組的元素排序,完成排序後,增量除以2獲得新的增量;
  • 第三步: 重複第二步,直到增量爲1,間隔爲1的元素組成的分組就是整個數組,而後再對整個數組進行插入排序,獲得最後排序後數組.

希爾排序是不穩定的,它在不斷地交換的過程當中會改變原來相等的元素的順序.

2.5.2 時間複雜度

平均狀況下的時間複雜度: o(nlog₂n);
最好狀況下的時間複雜度: o(n);

2.5.3 排序過程圖解

排序過程圖解

圖片源於自百度百科: 圖片來源

2.5.4 代碼實現:

希爾排序-遞歸實現

function shellSort(arr, increment) {
    var len = arr.length;
    if(increment > 0) {
        for(var i = increment; i < len; i++) {
            for(var j = i - increment; j >= 0 && arr[j] > arr[j + increment]; j -= increment) {
                    var temp = arr[j];
                    arr[j] = arr[j + increment];
                    arr[j + increment] = temp;
            }
        }
        return shellSort(arr, Math.floor(increment/2));
    }
     return arr; 
}
var arr = [49,38,65,97,76,13,27,49,55,04];
shellSort(arr, Math.floor(arr.length / 2));

希爾排序-非遞歸實現

function shellSort(arr) {
        var len = arr.length;
        for(var increment = Math.floor(len / 2); increment > 0; increment = Math.floor(increment / 2)) {
                for(var i = increment; i < len; i++) {
                        for(var j = i - increment; j >= 0 && arr[j] > arr[j + increment]; j -= increment) {
                                var temp = arr[j];
                                arr[j] = arr[j + increment];
                                arr[j + increment] = temp;
                        }
                }
        }
        return arr;
}
var arr = [49,38,65,97,76,13,27,49,55,04];
shellSort(arr);

2.6 歸併排序

2.6.1 主要思想

希爾排序的主要思想就是遞歸將數組層層分割,直到分割成最小的單元,而後再比較,提供一個新的空數組arrayC,將分割的左右兩個數組中小的數放進數組,而後再層層回溯向上合併.獲得最終的arrayC就是排序後的數組.

2.6.2 時間複雜度

平均狀況下的時間複雜度: O(nlog₂n);
最好狀況下的時間複雜度: O(nlog₂n) ;

2.6.3 排序過程圖解

歸併排序過程圖解

2.6.4 代碼實現:

歸併排序-遞歸實現

var result = [];
function mergeArray(left, right) {
    result = [];
    while(left.length > 0 && right.length > 0) {
        if(left[0] < right[0]) {
            result.push(left.shift());
        } else {
            result.push(right.shift());
            }
    }
    return result.concat(left).concat(right);
}
function mergerSort(arr) {
    if(arr.length <= 1) {
        return arr;
    }

    var middle = Math.floor(arr.length / 2);
    var left = arr.slice(0, middle);
    var right = arr.slice(middle);
    return mergeArray(mergerSort(left), mergerSort(right));
}
var arr = [49,38,65,97,76,13,27,49,55,04];
mergerSort(arr, 0, arr.length);

因爲歸併排序的非遞歸實現比較複雜,我這裏就不作講解了,我以爲若是真的須要用到,讀者可自行研究.

總結

這是我寫的最用心的一篇博客了,萬事開頭難,我已經開頭了,就是一種突破.但願我能夠繼續堅持下去,不斷充電,不斷輸出,成爲一個優秀的前端工程師,加油 ^-^ ^-^.
歡迎幫我糾正錯誤和有疑問的人與我交流, it will be my pleasure. 個人qq號: 2510909248.

推薦閱讀
1) 十大經典排序算法總結(JavaScript描述)

相關文章
相關標籤/搜索