排序算法之冒泡法、選擇法、快速排序

前言

對一組數據進行排序在我們日常編程的過程中經常用到排序算法。下面就經常用到的冒泡法、選擇法還有快速排序法進行一下回顧。

 

關於時間複雜的一個計算方法:(簡單版)

步驟:

1、找到執行次數最多的語句

2、語句執行語句的數量級

3、用O表示結果

然後:

1、用常數1取代運行時間中的所有加法常數

2、在修改後的運行次數函數中,只保留最高階項

3、如果最高階項存在且不是1,那麼我們就去除於這個項相乘的常數。比如3n^2我們取n^2

最後就可以得到你們想要的結果了。

 

一、冒泡法

分析:

冒泡法是把兩個相鄰的元素做比較,(以從小到大排列爲例),如果兩個相鄰的元素中,前一個元素比後一個元素大,那麼需要交換一下兩個元素的位置。依次執行此操作,直到把最大的元素放到最後的位置。再從頭開始循環執行(注:已經放到後面,排好位置的不用再做比較)

具體實現:

private static void method1(int[] arr){
        for (int i=0;i<arr.length-1;i++){
            for (int j=0;j<arr.length-1-i;j++){
                if (arr[j]>arr[j+1]){
                    int temp=arr[j];
                    arr[j]=arr[j+1];
                    arr[j+1]=temp;
                }
            }
        }

}

我們來看冒泡法的時間複雜度:

冒泡排序一共要進行(n-1)次循環,每一次循環都要進行當前n-1次比較,所以一共的比較次數是:
(n-1) + (n-2) + (n-3) + … + 1 = n*(n-1)/2;

相當於是:(n-1)+(n-2)+(n-3)+...+(n-(n-1))=(n-1)*n-(1+2+3+...+(n-1))=(n-1)*n-((n-1+1)*(n-1)/2)=(n-1)*n-(n*(n-1)/2)=n*(n-1)/2=n^2-2n

根據我們的步驟走,保留最高次項,去掉相乘的常數就可以得到時間複雜度爲:O(n^2),所以冒泡排序的時間複雜度是 O(n^2)。

 

接下來我們說一種冒泡法的升級版,把代碼做一些改動,來增加冒泡法的效率

具體實現如下:

/**
     * 冒泡法的升級版
     * @param arr
     */
    public static void bubbleSort(int arr[]) {
        boolean didSwap;
        for (int i=0;i<arr.length-1;i++){
            didSwap=false;

            for (int j=0;j<arr.length-1-i;j++){
                if (arr[j]>arr[j+1]){
                    int temp=arr[j];
                    arr[j]=arr[j+1];
                    arr[j+1]=temp;

                    didSwap = true;
                }
            }

            if(didSwap == false)
                return;
        }
    }

由代碼可知,我們相對於第一種冒泡法,增加了一個變量didSwap,那麼爲什麼只增加了這麼一個變量就能提高冒泡法的工作效率?

看第一層for循環,在第一層for循環裏,初始化didSwap=false;第二層for循環是用來給相鄰的兩個元素做比較,如果滿足條件,則調換兩個相鄰元素的位置,並且把disSwap變量置爲true。反過來想,如果經歷過第二層for循環之後,那麼這個didSwap變量依然爲false。說明在第二層for循環的時候,沒有滿足arr[j]>arr[j+1]的條件,進而說明此時,整個數組中的元素已經是從小到大排好的順序了,無需再進行調換,所以也就無需再進行元素的比較,所以這時,跳出所有的循環:

if(didSwap == false)
                return;

所以,我們知道了,通過這一個簡單的變量,實現了冒泡法效率的提升。

二、選擇法

分析:選擇法是從第一個元素開始,拿出當前位置的數據和後面的數據做比較,(以從小到大排列爲例)如果當前位置的數據比後面數據大,那麼交換兩個數據的位置。接着往下比較,直到和最後一個元素比較完,那麼第一輪輪訓比較結束,這時第一個位置得到的數據爲所有數據中最小的一個。  緊接着開始第二輪輪訓:從第二個元素開始,仿照第一次輪訓,依次和後面的元素進行比較,直到和最後一個元素比較完爲止。第三次輪訓。。。。。。。直到所有輪訓都結束,那麼得到的這組數據就是從小到大排列好的數據。

具體實現:

private static void method2(int[] arr){
        for (int i=0;i<arr.length-1;i++){
            for (int j=i+1;j<arr.length;j++){
                if (arr[j]<arr[i]){
                    int temp=arr[i];
                    arr[i]=arr[j];
                    arr[j]=temp;
                }
            }
        }
}

我們來看選擇法的時間複雜度:

(n-1)+(n-2)+(n-3)+...+(n-(n-1))=(n-1)*n-(1+2+3+...+(n-1))=(n-1)*n-((n-1+1)*(n-1)/2)=(n-1)*n-(n*(n-1)/2)=n*(n-1)/2=n^2-2n

時間複雜度同冒泡法一樣也是:O(n^2)。

三、快速排序算法

分析:快速排序算法通過多次比較和交換來實現排序,其排序流程如下:

(1)首先設定一個分界值,通過該分界值將數組分成左右兩部分。

(2)將大於或等於分界值的數據集中到數組右邊,小於分界值的數據集中到數組的左邊。此時,左邊部分中各元素都小於或等於分界值,而右邊部分中各元素都大於或等於分界值。

(3)然後,左邊和右邊的數據可以獨立排序。對於左側的數組數據,又可以取一個分界值,將該部分數據分成左右兩部分,同樣在左邊放置較小值,右邊放置較大值。右側的數組數據也可以做類似處理。

(4)重複上述過程,可以看出,這是一個遞歸定義。通過遞歸將左側部分排好序後,再遞歸排好右側部分的順序。當左、右兩個部分各數據排序完成後,整個數組的排序也就完成了。

具體實現:

private static void quickSort(int[] arr,int low,int high ){
        int i,j,temp,t;
        if (low>high){
            return;
        }
        i=low;
        j=high;
        //temp是基準位
        temp=arr[low];
        while(i<j){
            //先看右邊,依次往左遞減
            while(temp<=arr[j]&&i<j){
                j--;
            }

            //再看左邊,依次往右遞增
            while(temp>=arr[i]&&i<j){
                i++;
            }

            //如果滿足條件則交換
            if (i<j){
                t=arr[i];
                arr[i]=arr[j];
                arr[j]=t;
            }

        }

        //最後將基準爲與i和j相等位置的數字交換
        arr[low]=arr[i];
        arr[i]=temp;

        //遞歸調用左半數組
        quickSort(arr,low,i-1);

        //遞歸調用右半數組
        quickSort(arr,i+1,high);

    }

我們來看一看快速排序算法的時間複雜度:

快速排序的一次劃分算法從兩頭交替搜索,直到low和high重合,因此其時間複雜度是O(n);而整個快速排序算法的時間複雜度與劃分的趟數有關。

理想的情況是,每次劃分所選擇的中間數恰好將當前序列幾乎等分,經過log2n趟劃分,便可得到長度爲1的子表。這樣,整個算法的時間複雜度爲O(nlog2n)。

最壞的情況是,每次所選的中間數是當前序列中的最大或最小元素,這使得每次劃分所得的子表中一個爲空表,另一子表的長度爲原表的長度-1。這樣,長度爲n的數據表的快速排序需要經過n趟劃分,使得整個排序算法的時間複雜度爲O(n2)。

因此快速排序的最差時間複雜度和冒泡排序是一樣的都是O(N2),它的平均時間複雜度爲O(NlogN)。

 

注意:常用的時間複雜度所耗費的時間從小到大依次是:

O(1 )< O(logn) < O(n) < O(n*logn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)

總結:由上可以看出正常情況下來說,快速排序的速度是要大於冒泡排序和選擇排序的。

四、插入排序算法

插入排序實際上是從數組的第二個數(即索引爲1的數)開始,與前面的每一個數從右到左依次做比較,如果這個數比前面的某個數小,那麼互換兩個數的位置,具體實現如下:

    /**
     * 插入排序
     */
    public void insertSort(int[] arr){
        for (int i=1;i<arr.length;i++){
            for (int j=i;j>0;j--){
                if (arr[j]<arr[j-1]){
                    int temp=arr[j];
                    arr[j]=arr[j-1];
                    arr[j-1]=temp;
                }
            }
        }
    }

下面借用某大神的一張圖,來展示常見的幾種排序方式的時間複雜度:

 

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