雙指針+遞歸分治(本質是一個建立二叉樹搜索樹的過程)前端
經過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的全部數據都比另一部分的全部數據都要小,而後再按此方法對這兩部分數據分別進行快速排序,整個排序過程能夠遞歸進行,以此達到整個數據變成有序序列。git
上面的基本思路是參考網絡上大佬的文章整理的出來的,我來講說個人理解。算法
在將要排序的數據中選取一個數做爲基準數,將這些數據中比所選取的基準數小的數放在所選取基準數的左邊爲左數組,將比所選取基準數大的數組放在右邊爲右數組。數組
經過遞歸的方式重複循環1中的過程達到排序的目的。網絡
下面是個人代碼dom
let testArray = [3, 1, 2, 5, 6, 4]; let quickSort = (array) => { if (array.length < 2) return array; let leftArray = []; let rightArray = []; let baseDigit = array[0]; array.forEach(element => { if (element < baseDigit) { leftArray.push(element); } else if (element > baseDigit) { rightArray.push(element); } }); return quickSort(leftArray).concat(baseDigit, quickSort(rightArray)) }; quickSort(testArray);
基本思路跟我上述理解大同小異,主要來看看這篇文章具體的實現過程。下面借用原文的圖來說解(原文的圖作的很好就不單獨畫圖了,主要講一講原文沒解釋須要注意的地方,和對該篇文章作一個補充),底部附原文連接。函數
1.數組[2,3,1,5,6,4],建立兩指針,一個只想頭一個指向尾,再肯定一個基準數。性能
(注意:爲了方便後面遞歸是可以肯定基準數,這裏基準數選取,第一個數或者最後一個數)測試
2.開始第一次的遞歸處理,尾指針先從右往左掃,掃到第一個小於(注意是小於,而不是小於等於哦)基準數的位置停住,這時候頭指針再從左往右掃,掃到第一個大於基準數的位置停住,這時候是下面的圖示狀態:ui
(注意:這裏若是基準數選區的第一個數,應該尾指針先往左側掃,若基準數選取爲最後一個屬則,應是頭指針嚮往右掃)
交換兩個指針所指的數,成爲了下面的狀態:
3.兩個數交換完畢,右指針此時指的是arr[2] = 3, 左指針指着arr[1] = 1;交換完畢後右指針繼續從當前位置往左掃,掃到1的時候發現和左指針相遇了,那麼這個時候就結束左右指針的掃描,左右指針同時指着arr[1] = 1,即:
此時退出循環掃描的過程,交換基準數與左右指針同時所指的數的位置,開頭說了,基準數我選擇的是arr[0] = 2, 指針指的是arr[1] = 1; 交換事後就變成了:
這時候就發現基準數已經出如今了它排完序後應該在的位置(排完序後是[1,2,3,4,5,6],2出如今了第2位),比這個基準數小的數組出如今了它的左邊([1]出如今了2的左邊),比基準數大的出如今了它的右邊([3,5,6,4]出如今了2的右邊)。
4.以後的過程就是對左右數組的分別遞歸處理。
function quickSort(arr, begin, end) { //遞歸出口 if(begin >= end) return; var l = begin; // 左指針 var r = end; //右指針 var temp = arr[begin]; //基準數,這裏取數組第一個數 //左右指針相遇的時候退出掃描循環 while(l < r) { //右指針從右向左掃描,碰到第一個小於基準數的時候停住 while(l < r && arr[r] >= temp) r --; //左指針從左向右掃描,碰到第一個大於基準數的時候停住 while(l < r && arr[l] <= temp) l ++; //交換左右指針所停位置的數 [arr[l], arr[r]] = [arr[r], arr[l]]; } //最後交換基準數與指針相遇位置的數 [arr[begin], arr[l]] = [arr[l], arr[begin]]; //遞歸處理左右數組 quickSort(arr, begin, l - 1); quickSort(arr, l + 1, end); } var arr = [2,3,4,1,5,6] quickSort(arr, 0, 5); console.log(arr)
百科上的思路跟上述某乎文章基本一致,不過再細節方面不一樣,這裏主要講已將它們不一樣的地方,詳情請參考原文。(需注意之處也在和上文相同不在贅述)
主要的不一樣之處在於再上述2,3步驟。
百科上給的方式是:假設讓右指針先掃,掃到了比基準數小的,就講該數與基準數值交換位置,此時左指針指向基準數,再讓左指針往右掃描,掃到比基準數大的交換左右指針數值,兩指針相遇時直接退出此次遞歸,經過這樣的的方式來達到第一次遞歸的目的。
上文中則是:假設讓右指針先掃,掃到了比基準數小的,指針停住,再讓左指針往右掃描掃到比基準數大的數再停住,而後交換兩指針指向的值,反覆調用,兩指針相遇時與基準數的數值進行交換。
相對於理解來講我認爲是,百科的方式更容易理解(實際上是我先理解了百科的方式讓後想到了本身的思路,最後才理解了某乎的方式)。
const quickSort = (array) => { const sort = (arr, left = 0, right = arr.length - 1) => { if (left >= right) {//若是左邊的索引大於等於右邊的索引說明整理完畢 return } let i = left let j = right const baseVal = arr[j] // 取無序數組最後一個數爲基準值 while (i < j) {//把全部比基準值小的數放在左邊大的數放在右邊 while (i < j && arr[i] <= baseVal) { //找到一個比基準值大的數交換 i++ } arr[j] = arr[i] // 將較大的值放在右邊若是沒有比基準值大的數就是將本身賦值給本身(i 等於 j) while (j > i && arr[j] >= baseVal) { //找到一個比基準值小的數交換 j-- } arr[i] = arr[j] // 將較小的值放在左邊若是沒有找到比基準值小的數就是將本身賦值給本身(i 等於 j) } arr[j] = baseVal // 將基準值放至中央位置完成一次循環(這時候 j 等於 i ) sort(arr, left, j-1) // 將左邊的無序數組重複上面的操做 sort(arr, j+1, right) // 將右邊的無序數組重複上面的操做 } const newArr = array.concat() // 爲了保證這個函數是純函數拷貝一次數組 sort(newArr) return newArr }
既然這裏給出了三種方式來實現快排,那咱們就來測試一下性能
因爲百科的方法有問題再5位數以上會報錯10000後面不測試百科方法
第一個數個人方法
在1000個相同隨機數的狀況下
在100000個相同隨機數的狀況下
從性能上講是某乎的方法更高。
附測試代碼
// 個人方法 let myQuickSort = (array) => { if (array.length < 2) return array; let leftArray = []; let rightArray = []; let baseDigit = array[0]; array.forEach(element => { if (element < baseDigit) { leftArray.push(element); } else if (element > baseDigit) { rightArray.push(element); } }); return myQuickSort(leftArray).concat(baseDigit, myQuickSort(rightArray)) }; // 某乎的方法 let moHu = (arr, begin, end) => { //遞歸出口 if (begin >= end) return; var l = begin; // 左指針 var r = end; //右指針 var temp = arr[begin]; //基準數,這裏取數組第一個數 //左右指針相遇的時候退出掃描循環 while (l < r) { //右指針從右向左掃描,碰到第一個小於基準數的時候停住 while (l < r && arr[r] >= temp) r--; //左指針從左向右掃描,碰到第一個大於基準數的時候停住 while (l < r && arr[l] <= temp) l++; //交換左右指針所停位置的數 [arr[l], arr[r]] = [arr[r], arr[l]]; } //最後交換基準數與指針相遇位置的數 [arr[begin], arr[l]] = [arr[l], arr[begin]]; //遞歸處理左右數組 moHu(arr, begin, l - 1); moHu(arr, l + 1, end); }; //百科的方法 let baiKe = (array) => { let sort = (arr, left = 0, right = arr.length - 1) => { if (left >= right) {//若是左邊的索引大於等於右邊的索引說明整理完畢 return } let i = left; let j = right; const baseVal = arr[j];// 取無序數組最後一個數爲基準值 while (i < j) {//把全部比基準值小的數放在左邊大的數放在右邊 while (i < j && arr[i] <= baseVal) { //找到一個比基準值大的數交換 i++ } arr[j] = arr[i]; // 將較大的值放在右邊若是沒有比基準值大的數就是將本身賦值給本身(i 等於 j) while (j > i && arr[j] >= baseVal) { //找到一個比基準值小的數交換 j-- } arr[i] = arr[j] // 將較小的值放在左邊若是沒有找到比基準值小的數就是將本身賦值給本身(i 等於 j) } arr[j] = baseVal; // 將基準值放至中央位置完成一次循環(這時候 j 等於 i ) sort(arr, left, j - 1); // 將左邊的無序數組重複上面的操做 sort(arr, j + 1, right) // 將右邊的無序數組重複上面的操做 }; const newArr = array.concat();// 爲了保證這個函數是純函數拷貝一次數組 sort(newArr); return newArr }; // 生成一個1-count的隨機數組 let createTestArray = (count) => { let temArray = []; while (count > 0) { temArray.unshift(count); count--; } let i = temArray.length; while (i) { let j = Math.floor(Math.random() * i--); [temArray[j], temArray[i]] = [temArray[i], temArray[j]]; } return temArray; }; // 測試 let testQuickSort = (name, func, arr, moHu) => { if (!!moHu) { console.time(name); func(arr, moHu.begin, moHu.end); console.timeEnd(name); return; } console.time(name); func(arr); console.timeEnd(name); }; // 生成1-100000的隨機數組 const testArray = createTestArray(100000); testQuickSort('myQuickSort', myQuickSort, testArray); testQuickSort('moHu', moHu, testArray, {begin: 0, end: 99999}); // testQuickSort('baiKe', baiKe, testArray);
某乎:微軟前端社招筆試詳解
百科:百度百科