排序算法之快速排序

        基本概念html

        快速排序是很是流行、應用很是普遍的排序算法,並且實現簡單,適用於各類不一樣的輸入數據,在通常應用中比其餘排序算法都要快不少。快速排序是基於分治思想原地排序的排序算法,將長度爲N的數組排序所需時間和NlgN成正比,並且內循環比大多數排序算法都要短小和簡單,所以通常狀況比其餘排序算法效率高。它的缺點是很是脆弱,某些狀況可能致使它的性能達到平方級別,所以實現時必須很是當心才能避免低劣的性能。
        快速排序的總體思路是根據某個元素將一個數組分紅左右兩個子數組,以該元素爲界限,左側子數組的值都小於或等於該元素,右側子數組的值都大於或等於該元素(這一過程稱爲切分,下面會介紹)。而後遞歸地對數組的左右兩個子數組繼續進行上述操做,直到全部元素排列完成。上述每一趟切分都能排定一個元素(切分元素),若是左子數組和右子數組都是有序的,那麼由左子數組、切分元素、右子數組組成的數組必定是有序的。java

        切分的詳細過程
        通常選取數組第一個元素爲切分元素,也是那個會被排定的元素,而後定義兩個指針,一個指針從數組左側第二個元素開始向右側遍歷數組,直到找到一個大於或等於切分元素的元素時中止;另外一個指針從數組右側最後一個元素開始向左側遍歷數組,直到找到一個小於或等於切分元素的元素時中止,而後交換兩個元素的位置。如此繼續,直到兩個指針相遇,而後將切分元素和左子數組最右側元素交換位置,並返回交換後的切分元素位置便可。這樣每一趟切分都能排定切分元素,並保證切分元素左側的元素都小於或等於它,右側的元素都大於或等於它。算法

        切分過程示意圖以下:數組

        切分過程詳細代碼:dom

    public static int partition(int[] arr,int p,int q){
        int i=p,j=q+1;  //i指針從數組左邊向右遍歷,j指針從數組右邊向左遍歷
        int val = arr[p];  //將數組第一個元素設置爲切分元素
        while(true){
            while(arr[++i] < val){  //找到大於或等於切分元素的元素時中止
                if(i == q) break;
            }
            while(arr[--j] > val){  //找到小於或等於切分元素的元素時中止
                if(j == p) break;
            }
            if(i >= j) break;  //兩指針相遇,終止整個循環
            int num = arr[i];  //交換i、j指針處元素,使小於切分元素的元素在左邊,大於切分元素的元素在右邊
            arr[i] = arr[j];
            arr[j] = num;
        }
        arr[p] = arr[j];  //將切分元素和左子數組最右側元素交換位置
        arr[j] = val;
        
        return j;  //返回切分元素位置
    }

        有了上面每一趟切分的詳細過程以後,就能夠對數組遞歸地進行切分。
        遞歸過程代碼以下:性能

    //快速排序(遞歸)
    public static void quickSort(int[] arr){
        if(arr == null) return;
        quickSort(arr,0,arr.length-1);
    }
    public static void quickSort(int[] arr,int p,int q){
        if(p >= q) return;
        int i = partition(arr,p,q);  //切分的詳細過程,返回切分元素位置
        quickSort(arr,p,i-1);  //遞歸切分切分元素左側元素
        quickSort(arr,i+1,q);  //遞歸切分切分元素右側元素
    }

        快速排序完整代碼:學習

    //快速排序(遞歸)
    public static void quickSort(int[] arr){
        if(arr == null) return;
        quickSort(arr,0,arr.length-1);
    }
    public static void quickSort(int[] arr,int p,int q){
        if(p >= q) return;
        int i = partition(arr,p,q);  //切分的詳細過程,返回切分元素位置
        quickSort(arr,p,i-1);  //遞歸切分切分元素左側元素
        quickSort(arr,i+1,q);  //遞歸切分切分元素右側元素
    }
    public static int partition(int[] arr,int p,int q){
        int i=p,j=q+1;  //i指針從數組左邊向右遍歷,j指針從數組右邊向左遍歷
        int val = arr[p];  //將數組第一個元素設置爲切分元素
        while(true){
            while(arr[++i] < val){  //找到大於或等於切分元素的元素時中止
                if(i == q) break;
            }
            while(arr[--j] > val){  //找到小於或等於切分元素的元素時中止
                if(j == p) break;
            }
            if(i >= j) break;  //兩指針相遇,終止整個循環
            int num = arr[i];  //交換i、j指針處元素,使小於切分元素的元素在左邊,大於切分元素的元素在右邊
            arr[i] = arr[j];
            arr[j] = num;
        }
        arr[p] = arr[j];  //將切分元素和左子數組最右側元素交換位置
        arr[j] = val;
        
        return j;  //返回切分元素位置
    }

        性能分析與改進
        從代碼能夠看出,快速排序的內循環很是簡潔,元素的比較和移動次數不多,所以通常比歸併排序、希爾排序等速度要快。固然,快速排序的效率取決於切分的數組是否平衡,平衡與否取決於切分元素的值。若是切分的數組是平衡的,那麼快速排序算法性能與歸併排序同樣;若是切分的數組是不平衡的,那麼快速排序的性能接近於插入排序。理想的切分元素是能將數組正好切分紅兩個相等的子數組,而實際中每每不是這樣,兩個子數組的長度每每不相等,左子數組可能比右子數組大或右子數組比左子數組大。最極端的狀況是左子數組爲空或右子數組爲空,這種狀況每每發生在數組已經有序或切分元素是最大或最小元素,這時快速排序的效率會很是低,應儘可能避免這種狀況發生,能夠經過隨機打亂數組避免這種狀況發生。將長度爲N的無重複數組排序,快速排序平均須要約2NlnN次比較,最極端狀況最多須要約N²/2次比較。所以,快速排序在平均狀況下的時間複雜度爲O(NlgN),最壞狀況下時間複雜度爲O(N²)
        雖然快速排序已經比其餘排序算法快不少了,但它依然有改進的餘地。對於大多數基於遞歸的排序算法而言(好比快速排序或歸併排序),遞歸產生的小數組也會調用排序算法自身,對它們使用插入排序比快速排序或歸併排序要快,所以對於長度爲5-15之間的小數組可使用插入排序。對於含有大量重複元素的數組,快速排序的遞歸會使全部元素都相等的子數組常常出現,對這些子數組有很大的改進潛力,能使算法性能由線性對數級別提升到線性級別。改進方法經過「三向切分的快速排序」,根據切分元素將數組分爲三個子數組,第一個子數組的元素都小於切分元素,第二個子數組的元素都等於切分元素,第三個子數組的元素都大於切分元素。ui

        與歸併排序的區別
        快速排序和歸併排序都是分治思想的排序算法,均可以經過遞歸方式進行排序。二者之間是互補的,歸併排序將數組分紅兩個子數組分別進行排序,而後將兩個有序的子數組歸併,來將整個數組排序;而快速排序則是當兩個子數組都有序時,整個數組天然就有序了。歸併排序的遞歸調用發生在處理整個數組以前,將數組等分爲兩半;快速排序的遞歸調用發生在處理整個數組以後,切分的位置取決於數組的內容。二者都能使算法的時間複雜度達到線性對數級別,但歸併排序中有一個輔助數組,須要額外的空間,而快速排序是原地排序,所以整體上來看,快速排序優於歸併排序,通常狀況下優先使用快速排序。歸併排序的詳細介紹見 排序算法之歸併排序spa

        下面代碼生成5000000個隨機數,並複製到兩個數組中(兩個數組元素相同),而後分別用快速排序和歸併排序算法進行排序,比較排序時間。代碼以下:指針

package Test;

import java.util.Random;

public class Compare {
    
    //快速排序(遞歸)
    public static void quickSort(int[] arr){
        if(arr == null) return;
        quickSort(arr,0,arr.length-1);
    }
    public static void quickSort(int[] arr,int p,int q){
        if(p >= q) return;
        int i = partition(arr,p,q);  //切分的詳細過程,返回切分元素位置
        quickSort(arr,p,i-1);  //遞歸切分切分元素左側元素
        quickSort(arr,i+1,q);  //遞歸切分切分元素右側元素
    }
    public static int partition(int[] arr,int p,int q){
        int i=p,j=q+1;  //i指針從數組左邊向右遍歷,j指針從數組右邊向左遍歷
        int val = arr[p];  //將數組第一個元素設置爲切分元素
        while(true){
            while(arr[++i] < val){  //找到大於或等於切分元素的元素時中止
                if(i == q) break;
            }
            while(arr[--j] > val){  //找到小於或等於切分元素的元素時中止
                if(j == p) break;
            }
            if(i >= j) break;  //兩指針相遇,終止整個循環
            int num = arr[i];  //交換i、j指針處元素,使小於切分元素的元素在左邊,大於切分元素的元素在右邊
            arr[i] = arr[j];
            arr[j] = num;
        }
        arr[p] = arr[j];  //將切分元素和左子數組最右側元素交換位置
        arr[j] = val;
        
        return j;  //返回切分元素位置
    }
    
    //歸併排序(遞歸,自頂向下)
    public static void mergeSort(int[] arr){  //本方法只會執行一次,下面兩個方法執行屢次
        if(arr == null) return;
        int[] aux = new int[arr.length];  //輔助數組
        mergeSort(arr,aux,0,arr.length-1);
    }
    public static void mergeSort(int[] arr,int[] aux,int p,int q){
        if(p>=q) return;
        int mid = (p+q)>>1;
        mergeSort(arr,aux,p,mid);  //左半塊歸併
        mergeSort(arr,aux,mid+1,q);  //右半塊歸併
        merge(arr,aux,p,mid,q);  //歸併詳細過程
    }
    public static void merge(int[] arr,int[] aux,int p,int mid,int q){
        for(int k=p;k<=q;k++){  //先複製到輔助數組中
            aux[k] = arr[k];
        }
        int i=p,j=mid+1;  //i、j指向輔助數組左右半塊指針,從起始位置開始
        for(int k=p;k<=q;k++){  //k指向原數組arr[],根據i、j指針位置判斷左右半塊是否遍歷完
            if(i > mid)  arr[k] = aux[j++];  //左半塊遍歷完
            else if(j>q) arr[k] = aux[i++];  //右半塊遍歷完
            else if(aux[j]>aux[i]) arr[k] = aux[i++];
            else arr[k] = aux[j++];
        }
    }
    public static void main(String[] args) {
        Random random = new Random();
        int[] arg = new int[5000000];
        for(int n=0;n<5000000;n++){  //從[0-5000000]中生成5000000個隨機數
            arg[n] = random.nextInt(5000000);
        }
        int[] arg1 = new int[arg.length];
        int[] arg2 = new int[arg.length];
        for(int n=0;n<arg.length;n++){  //將隨機生成的數組複製到2個數組中
            arg1[n] = arg[n];
            arg2[n] = arg[n];
        }
        System.out.println("複製完成");
        long startTime1 = System.currentTimeMillis();  //獲取開始時間
        quickSort(arg1);
        long endTime1 = System.currentTimeMillis();  //獲取結束時間
        long startTime2 = System.currentTimeMillis();  //獲取開始時間
        mergeSort(arg2);
        long endTime2 = System.currentTimeMillis();  //獲取結束時間
        
        System.out.println("快速排序執行時間:"+(endTime1-startTime1)+"ms");  //567ms 567ms 573ms 568ms 563ms
        System.out.println("歸併排序執行時間:"+(endTime2-startTime2)+"ms");  //907ms 886ms 880ms 906ms 886ms
    }
}

        快速排序5次時間分別爲:567ms 567ms 573ms 568ms 563ms,平均時間爲567.6ms。

        歸併排序5次時間分別爲:907ms 886ms 880ms 906ms 886ms,平均時間爲893.0ms。

        經過排序的平均時間來看,對於通常狀況下的隨機數組,快速排序比歸併排序速度快,大約快30%-40%,所以通常狀況下優先選擇快速排序算法。

        轉載請註明出處 https://www.cnblogs.com/Y-oung/p/9074044.html

        工做、學習、交流或有任何疑問,請聯繫郵箱:yy1340128046@163.com

相關文章
相關標籤/搜索