聊一聊快速排序(Js)


快速排序

基本思路

雙指針+遞歸分治(本質是一個創建二叉樹搜索樹的過程)

通過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都比另外一部分的所有數據都要小,然后再按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列。

我的理解

上面的基本思路是參考網絡上大佬的文章整理的出來的,我來說說我的理解。

  1. 在將要排序的數據中選取一個數作為基准數,將這些數據中比所選取的基准數小的數放在所選取基准數的左邊為左數組,將比所選取基准數大的數組放在右邊為右數組。

  2. 通過遞歸的方式重復循環1中的過程達到排序的目的。

下面是我的代碼

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.開始第一次的遞歸處理,尾指針先從右往左掃,掃到第一個小於(注意是小於,而不是小於等於哦)基准數的位置停住,這時候頭指針再從左往右掃,掃到第一個大於基准數的位置停住,這時候是下面的圖示狀態:

注意:這里如果基准數選區的第一個數,應該尾指針先往左側掃,若基准數選取為最后一個屬則,應是頭指針向往右掃)

 

交換兩個指針所指的數,成為了下面的狀態:

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);

 

最后

最后記一筆,快速排序是不穩定排序也就是說,多個相同的值的相對位置也許會在算法結束時產生變動。

 

文章連接:

某乎:微軟前端社招筆試詳解

百科:百度百科


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM