1. 程式人生 > >聊一聊快速排序(Js)

聊一聊快速排序(Js)

無序 temp 現在 直接 odi 算法 time itl ref

快速排序

基本思路

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

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

我的理解

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

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

最後

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

文章連接:

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

百科:百度百科

聊一聊快速排序(Js)