JavaScript 快速排序算法
前段時間,看到一篇叫做《面試官:阮一峰版的快速排序完全是錯的》的文章,恰巧此前不久也學習了阮一峰老師的快排,非常通俗易懂易實現,不得不說,標題一下抓住了我的眼球。
文章內容就是某面試官(簡寫成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 快速排序算法