1. 程式人生 > >JavaScript 快速排序算法

JavaScript 快速排序算法

chrome 理解 索引 什麽 算法 true 操作 數值 ID

前段時間,看到一篇叫做《面試官:阮一峰版的快速排序完全是錯的》的文章,恰巧此前不久也學習了阮一峰老師的快排,非常通俗易懂易實現,不得不說,標題一下抓住了我的眼球。

文章內容就是某面試官(簡寫成A,下同)微博公開說阮一峰老師(簡寫成R,下同)快排是完全錯誤的,重點是,所有面試者的快排都是R的,Google 前端快排 也都是R的,一個A認為完全錯誤的算法還一統前端的天下了,也許A在發博的時候帶了情緒,亦或是有別的原因,措辭犀利,引起了前端界一波爭議。而以上,都不是我關註的重點,我把重點投到了算法上:

一、阮一峰老師的快排js實現

思路:

1、選擇數組中間數作為基數,並從數組中取出此基數;

2、準備兩個數組容器,遍歷數組,逐個與基數比對,較小的放左邊容器,較大的放右邊容器;

3、遞歸處理兩個容器的元素,並將處理後的數據與基數按大小合並成一個數組,返回。

實現:

    var quickSort = function(arr) {

      if (arr.length <= 1) { return arr; }

      var pivotIndex = Math.floor(arr.length / 2);

      var pivot = arr.splice(pivotIndex, 1)[0];

      var left = [];

      var right = [];

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

        if (arr[i] < pivot) {

          left.push(arr[i]);

        } else {

          right.push(arr[i]);

        }

      }

      return quickSort(left).concat([pivot], quickSort(right));

    };

總結:

R的思路非常清晰,選擇基數為參照,劃分數組,分而治之,對於新手來理解快排的核心思想“參照-劃分-遞歸”,很容易理解 。

既實現了排序,又符合快速排序的思想,為什麽還會為人所詬病呢?原來是因為:

1、R取基數用的是splice()函數取,而不是算法中常用的取下標。基數只是一個參照對象,在比對的時候,只要能從數組中取到即可,所以只需要知道它的索引即可,調用函數刪除基數只會更耗時;

2、根據基數來劃分時,R專門生成兩個數組來存儲,從而占用了更多的存儲空間(增加了空間復雜度)。

嚴格上講,R的代碼僅僅是用快速排序的思想實現了排序,也算是快速排序,但是還有很多改進之處。

二、文章中提出的快排js實現

思路:

1、通過下標取中間數為基數;

2、從起點往後尋找比基數大的,記錄為下標 i;再從終點往前尋找比基數小的,記錄為下標 j,當 i <= j時,原地交換數值;

3、重復步驟2,直到遍歷所有元素,並記錄遍歷的最後一個下標 i,以此下標為分界線,分為左右兩邊,分別重復步驟1~3實現遞歸排序;

實現(為方便理解,在原文基礎上有所合並):

    // 快排改進——黃佳新
    var devide_Xin = function (array, start, end) {
        if(start >= end) return array;
        var baseIndex = Math.floor((start + end) / 2), // 基數索引
             i = start,
             j = end;

        while (i <= j) {
            while (array[i] < array[baseIndex]) {
                i++;
            }
            while (array[j] > array[baseIndex])  {
                j--;
            }

            if(i <= j) {
                var temp = array[i];
                array[i] = array[j];
                array[j] = temp;
                i++;
                j--;
            }
        }
        return i;
    }

    var quickSort_Xin = function (array, start, end) {
        if(array.length < 1) {
            return array;
        }
        var index = devide_Xin(array, start, end);
        if(start < index -1) {
            quickSort_Xin(array, start, index - 1);
        }
        if(end > index) {
            quickSort_Xin(array, index, end);
        }

        return array;
    }

總結:

1、用下標取基數,只有一個賦值操作,跟快;

2、原地交換,不需要新建多余的數組容器存儲被劃分的數據,節省存儲;

比較:

相較而言,理論分析,實現二確實是更快速更省空間,那麽事實呢?

技術分享圖片

以上是實現一與實現二在chrome上測試耗時的統計結果,測試方案為:各自隨機生成100萬個數(亂序),分別完成排序,統計耗時。

結論:

事實上,亂序排序,實現二更快。

三、網上其他的快排js實現

思路:

1、通過下表取排序區間的第0個數為基數

2、排序區間基數以後,從右往左,尋找比基數小的,從左往右,尋找比基數大的,原地交換;

3、重復步驟2直到 i >= j;

4、將基數與下標為 i 的元素原地交換,從而實現劃分;

5、遞歸排序基數左邊的數,遞歸排序基數右邊的數,返回數組。

實現:

    var quickSort_New = function(ary, left, right) {
        if(left >= right) {
            return ary;
        }

        var i = left,
             j = right;
             base = ary[left];

        while (i < j) {
            // 從右邊起,尋找比基數小的數
            while (i<j && ary[j] >= base) {
                j--;
            }

            // 從左邊起,尋找比基數大的數
            while (i<j && ary[i] <= base) {
                i++
            } 

            if (i<j) {
                var temp = ary[i];
                ary[i] = ary[j];
                ary[j] = temp;
            }
        }

        ary[left] = ary[i];
        ary[i] = base;

        quickSort_New(ary, left, i-1);
        quickSort_New(ary, i+1, right);

        return ary;
    }

總結:

除選基數不同以外,其他與實現二類似。

另外:

比較一下實現二與實現三的速度,結果如下:

技術分享圖片

多次測試結果均為:實現二耗時略小於實現三,偶爾出現大於的情況,但相差不大。

算法是計算機基礎,無論前後端,都應該重視,前端入門門檻低,確實很多前端算法功底差,但前端也有挺多東西要學,並不是A所說的“天花板低”。還是埋起頭來學習吧!

完~

JavaScript 快速排序算法