快速排序,又稱劃分交換排序。以分治法為策略實現的快速排序演算法。 本文主要要談的是利用javascript實現in-place思想的快速排序 分治法: 在電腦科學中,分治法是建基於多項分支遞歸的一種很重要的演算法範式。字面上的解釋是“分而治之”,就是把一個複雜的問題分成兩個或更多的相同或相似的子問題, ...
快速排序,又稱劃分交換排序。以分治法為策略實現的快速排序演算法。
本文主要要談的是利用javascript實現in-place思想的快速排序
分治法:
在電腦科學中,分治法是建基於多項分支遞歸的一種很重要的演算法範式。字面上的解釋是“分而治之”,就是把一個複雜的問題分成兩個或更多的相同或相似的子問題,直到最後子問題可以簡單的直接求解,原問題的解即子問題的解的合併。(摘自維基百科)
快速排序的思想
數組中指定一個元素作為標尺,比它大的放到該元素後面,比它小的放到該元素前面,如此重覆直至全部正序排列。
快速排序分三步:
- 選基準:在數據結構中選擇一個元素作為基準(pivot)
- 劃分區:參照基準元素值的大小,劃分無序區,所有小於基準元素的數據放入一個區間,所有大於基準元素的數據放入另一區間,分區操作結束後,基準元素所處的位置就是最終排序後它應該所處的位置
- 遞歸:對初次劃分出來的兩個無序區間,遞歸調用第 1步和第 2步的演算法,直到所有無序區間都只剩下一個元素為止。
現在先說說普遍的實現方法(沒有用到原地演算法)
function quickSort(arr) { if (arr.length <= 1) return ; //取數組最接近中間的數位基準,奇數與偶數取值不同,但不印象,當然,你可以選取第一個,或者最後一個數為基準,這裡不作過多描述 var pivotIndex = Math.floor(arr.length / 2); var pivot = arr.splice(pivotIndex, 1)[0]; //左右區間,用於存放排序後的數 var left = []; var right = []; console.log('基準為:' + pivot + ' 時'); for (var i = 0; i < arr.length; i++) { console.log('分區操作的第 ' + (i + 1) + ' 次迴圈:'); //小於基準,放於左區間,大於基準,放於右區間 if (arr[i] < pivot) { left.push(arr[i]); console.log('左邊:' + (arr[i])) } else { right.push(arr[i]); console.log('右邊:' + (arr[i])) } } //這裡使用concat操作符,將左區間,基準,右區間拼接為一個新數組 //然後遞歸1,2步驟,直至所有無序區間都 只剩下一個元素 ,遞歸結束 return quickSort(left).concat([pivot], quickSort(right)); } var arr = [14, 3, 15, 7, 2, 76, 11]; console.log(quickSort(arr)); /* * 基準為7時,第一次分區得到左右兩個子集[ 3, 2,] 7 [14, 15, 76, 11]; * 以基準為2,對左邊的子集[3,2]進行劃分區排序,得到[2] 3。左子集排序全部結束 * 以基準為76,對右邊的子集進行劃分區排序,得到[14, 15, 11] 76 * 此時對上面的[14, 15, 11]以基準為15再進行劃分區排序, [14, 11] 15 * 此時對上面的[14, 11]以基準為11再進行劃分區排序, 11 [14] * 所有無序區間都只剩下一個元素,遞歸結束 * */
通過斷點調試,可得出結果。
弊端:
它需要Ω(n)的額外存儲空間,跟歸併排序一樣不好。在生產環境中,需要額外的記憶體空間,影響性能。
同時,很多人認為上邊的就是真正的快速排序了。 所以,在下麵,很有必要的推薦in-place演算法的快速排序
有關於原地演算法可參考維基百科,被牆的同學,百度也差不多。
in-place
快速排序一般是用遞歸實現,最關鍵是partition分割函數,它將數組劃分為兩部分,一部分小於pivot,另一部分大於pivot。具體原理上邊提過
function quickSort(arr) { // 交換 function swap(arr, a, b) { var temp = arr[a]; arr[a] = arr[b]; arr[b] = temp; } // 分區 function partition(arr, left, right) { /** * 開始時不知最終pivot的存放位置,可以先將pivot交換到後面去 * 這裡直接定義最右邊的元素為基準 */ var pivot = arr[right]; /** * 存放小於pivot的元素時,是緊挨著上一元素的,否則空隙里存放的可能是大於pivot的元素, * 故聲明一個storeIndex變數,並初始化為left來依次緊挨著存放小於pivot的元素。 */ var storeIndex = left; for (var i = left; i < right; i++) { if (arr[i] < pivot) { /** * 遍曆數組,找到小於的pivot的元素,(大於pivot的元素會跳過) * 將迴圈i次時得到的元素,通過swap交換放到storeIndex處, * 並對storeIndex遞增1,表示下一個可能要交換的位置 */ swap(arr, storeIndex, i); storeIndex++; } } // 最後: 將pivot交換到storeIndex處,基準元素放置到最終正確位置上 swap(arr, right, storeIndex); return storeIndex; } function sort(arr, left, right) { if (left > right) return; var storeIndex = partition(arr, left, right); sort(arr, left, storeIndex - 1); sort(arr, storeIndex + 1, right); } sort(arr, 0, arr.length - 1); return arr; } console.log(quickSort([8, 4, 90, 8, 34, 67, 1, 26, 17]));
分區的優化
這裡細心的同學可能會提出,選取不同的基準時,是否會有不同性能表現,答案是肯定的,但,因為,我是搞前端的,對演算法不是很瞭解,所以,這個坑留給厲害的人來填補。
複雜度
快速排序是排序速度最快的演算法,它的時間複雜度是O(log n)
在平均狀況下,排序n個項目要Ο(n log n)次比較。在最壞狀況下則需要Ο(n2)次比較.