真正的快速排序算法javascript實現

原理分析:

其基本思想是選取其中一個值,經過一輪排序,將小於此值的數據放置其左(右)邊,大於此值的數據放置於其右(左)邊,

這樣就完成一次快速排序,進而通過遞歸對左右兩側分別進行快速排序,直到排序組內都剩餘1項爲止。

還是以數組[1,4,7,2,6,8,3,5,9]舉例:

假設我們選取中間值6作爲中間比較值那麼經過一次快速排序之後結果應該是[1,4,2,3,5][6][7,8,9]

然後再對兩側重複遞歸快速排序

左邊[1][2][4,3,5]

右邊[7][8][9]

對[4,3,5]繼續快速排序得到[3][4,5]

最後對[4,5]排序[4][5]

這樣經過1,2,3,4,5輪快速排序之後將各個單獨項連接起來即完成排序

結果[1,2,3,4,5,6,7,8,9]

ok,這很容易理解,這個過程是應用算法中快速排序遞歸去實現我們所謂的"快速排序".

真正的快速排序

接下來我們來看看算法中的快速排序是如何進行的,也就是每一次遞歸時候內部是經過了什麼樣的快速排序算法去執行

以我們第一輪快速排序舉例:

原始數組:[1,4,7,2,6,8,3,5,9]

初始;i=0,j=8;

步驟一:先從j到左搜尋小於6的進行交換:找到該值後將j賦予該下標

步驟二:然後再從i到右搜尋大於6的進行交換:找到該值將i賦予該下標

直到i與j碰面爲止;

具體排序步驟如下:

第一趟排序:

    排序前:i=0,j=8;[1,4,7,2,6,8,3,5,9]

    步驟一:從右向左遞減找到5下標爲7

    排序後: i=0,j=7;[1,4,7,2,5,8,3,6,9]

第二趟排序:

    排序前:i=0,j=7;[1,4,7,2,5,8,3,6,9]

    步驟二後:從左到右遞增找到大於6的下標爲2的7;

    排序後:i=2,j=7;[1,4,6,2,5,8,3,7,9]

第三趟排序:

排序前:i=2,j=7;[1,4,6,2,5,8,3,7,9]

步驟一:從下標爲7向左找到小於6的值3下標爲6;

排序後:i=2,j=6;[1,4,3,2,5,8,6,7,9]

第四趟排序:

    排序前:i=2,j=6;[1,4,3,2,5,8,6,7,9]

    步驟二:從下標爲2向右遞增找到大於6的下標爲5的8;

    排序後:i=5,j=6;[1,4,3,2,5,6,8,7,9]

第五趟排序:

    排序前:i=5,j=6;[1,4,3,2,5,6,8,7,9]

    步驟一:從下標爲6向左找到小於6的值5,不過此時的5下標已經與i的值相同即j--後與i碰頭了。於是第一輪排序終止

    排序後:i=5,j=6;[1,4,3,2,5,6,8,7,9]

我們可以看出實際排序經歷了四趟。到第五趟時候就停止了;這就是快速排序的真正算法

之後以6爲中介將左右數據分開遞歸第一輪執行順序,直到左右兩側都剩餘一個值爲止;

兩種代碼實現:

傳統的以及網上盛行的"快速排序"是這樣實現的

function QuickSort(arr){

    if(arr.length<=1){    return arr;  }

    var self = arguments.callee;

    var left = [],right = [],middle=[];

    var mid = arr[Math.floor(arr.length/2)];

    for(var i=0;i<arr.length;i++){    

        if(arr[i]<mid){

            left.push(arr[i])

        }else if(arr[i]>mid){

            right.push(arr[i]);

        }else{

            middle.push(arr[i]);

        }

    }

  return [].concat(self(left),middle,self(right));

}

而根據我所理解的快速排序算法衍生而來的js排序如下:

function quickSort(arr){

    if(arr.length<2){

        return arr;

    }

    var self = arguments.callee;

    var left = [],right = [],middle=[];

    var k=Math.floor(arr.length/2),start=0,end=arr.length-1,x=arr[k];

    rtl(arr,start,end);

    function rtl(arr, i, j){

        if(i<j){

            if(arr[j] <= x){

                var temp=arr[j],t=j;

                arr[j]=x;

                arr[k]=temp;

                j=k;

                k=t;

                ltr(arr,start,end);                  

            }

            else{

               end--;

               rtl(arr,i,end);
            }

        }

    }

    function ltr(arr,i,j){

            if(i<j){

                if(arr[i]>x){

                    var temp=arr[i],t=i;

                    arr[i]=x;

                    arr[k]=temp;
                    i=k;

                    k=t;

                    rtl(arr,start,end)

                }

                else{

                    start++

                    ltr(arr,start,j);
                }

            }

    }

   return [].concat(self(arr.slice(0,k)),arr[k],self(arr.slice(k+1,arr.length)));
}

其中k始終保持着當前關鍵比較值的下標在此我們一開始是選取的中間的值作爲關鍵值,最後遞歸循環得到由大到小的排序結果。

比較兩種方式:

第一種:也就是傳統的"快速排序"其實不是真正的快速排序 而是根據分治法加上冒泡以及遞歸綜合起來的一種排序,其時間複雜度依然是跟冒泡一樣爲O(n^2)

因爲每一次分組進行比較都是將關鍵值跟所有對比一遍,不過優點還是代碼優美,容易理解;

第二種:雖然代碼看上去稍微複雜一些,不過是應用底層的快速排序思想去實現,其時間複雜度爲O(nlogn)-O(n^2)

有關複雜度計算可以參考算法複雜度

也就是說第二種的時間複雜度在最差情況下跟第一種是一樣的也是O(n^2)而最好的情況僅僅是O(nlogn)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章