算法之常見排序算法-冒泡排序、歸併排序、快速排序

引言面試

    對於編程中琳琅滿目的算法,本人向來是不善此道也不精於此的,而提及排序算法,也只是會冒泡排序。還記得當初剛作開發工做面試第一家公司時,面試官便讓手寫冒泡排序(入職以後才知道,這面試官就是一個冒泡排序"病態"愛好者,逢面試必考冒泡排序-__-)。後來看吳軍的一些文章,提到提升效率的關鍵就是少作事情不作無用功,便對這不起眼的排序算法有了興趣。恰好今天週末有閒,遂研究一二,與各位道友共享。算法

    冒泡排序時間之因此效率低,就是由於將全部數都一視同仁不作區分挨個比較,這是最普通的作事方法,因此效率也是最普通的,時間複雜度爲N的平方;而歸併排序效率高,則是採用了分治的思想,將一個總體分紅多個小份,每一個小份排好序以後再互相比較,這樣就比冒泡快了很多,時間複雜度爲NlogN;快速排序的平均時間複雜度也是NlogN,可是實際的耗費時間會比歸併排序快兩三倍(固然快排在最壞的狀況下時間複雜度仍是N的平方,比歸併排序大),它的平均執行時間能比歸併更快一些是由於它每次分組時不是隨機分組而是相對有序的分組,即先從數組中隨機取一個數做爲基數,而後將數據移動,使得基數一邊的數都比它小,另外一邊的數都比它大,再在兩邊各取一個基數進行相同的移動、分組操做,遞歸下去,這樣每一個細分的小組都在總體的大數組中有個位置,合併時直接按從小到大將各個分組合並起來便可,因此通常狀況下會比歸併快一些。編程

    瞭解了思想以後,再用代碼實現相對就會容易不少。此處就再借用一個直觀一點的例子來講明歸併與快排兩者的區別。假設有1000個學生,想對他們的成績進行排序。方法1借用歸併排序的思想,具體這樣作:將這1000我的分紅10組,將每組的100人進行排序,排完以後再在各組之間從小到大依次進行比較,最後獲得整個的成績排名。方法2借用快速排序的思想,具體需這樣作:將1000我的也是分紅10組,可是是按分數段分,0-10分的放在一組,10-20分的放在一組,20-30分的放在一組,依次類推,分完組以後再在各個小組中進行排序,而當你合併各個小組時,只需將其按從小到大的順序直接合並就行,無需跟方法1同樣將各小組中的數據取出來跟其餘小組中的數據挨個比較。看到這裏,想必各位道友對快排比歸併排序還要快一些的緣由就有了解了。數組

    算法能夠理解成作事的技巧或者說套路,咱們對其的理解能夠不止於編程,徹底能夠推廣出去。好比歸併的分治法,將一個大事情拆解成多個小事件,解決起來就會方便不少。閒話扯了一大堆,下面就將我本身寫的排序給你們貼出來,附帶上註釋講解,若是有以前不瞭解的道友,相信看完以後便會念頭通透,原地飛昇 >_<。ui

正文spa

歸併排序指針

// 歸併排序
    public static void mergeSort (int[] arr) {
        // 建一個臨時數據來存放數據
        int[] temp = new int[arr.length];
        mergeSort(arr, 0, arr.length - 1, temp);
    }

    private static void mergeSort(int[] arr, int left, int right, int[] temp) {
        if (left < right) { // 若是起始下標跟結束下標差值小於1,則不進行操做
            int mid = (left + right) / 2;
            mergeSort(arr, left, mid, temp); // 分組,將左邊分爲一組,遞歸調用進行排序
            mergeSort(arr, mid+1, right, temp); // 將右邊分爲一組
            merge(arr, left, mid, right, temp); //將左右分組合並
        }
    }

    private static void merge(int[] arr, int left, int mid, int right, int[] temp) {
        int i = left; // 定義左指針
        int j = mid + 1; // 定義右指針
        int t = 0; // 給temp臨時數組用的指針
        while (i <= mid && j <= right) { // 設置左右指針的移動邊界
            if (arr[i] <= arr[j]) { // 此處是升序,故誰小誰先賦給臨時數組
                temp[t++] = arr[i++];
            } else {
                temp[t++] = arr[j++];
            }
        }
        while (i <= mid) { // 若是左邊有剩餘,則放在temp中
            temp[t++] = arr[i++];
        }
        while (j <= right) { // 若是右邊有剩餘,依次放入temp中
            temp[t++] = arr[j++];
        }
        t = 0;
        // 此時temp中已是arr數組中下標從left到right之間排好序的數據了,由於temp每次都是從0開始賦值,因此需將排好序的數放回arr的對應位置
        while (left <= right) {
            // 將left到right之間排好序的數據放回arr中,此時left到right之間的數就是最終排好序的數
            arr[left++] = temp[t++];
        }
    }

快速排序code

 1 // 快速排序
 2     public static void quickSort (int[] arr, int left, int right) {
 3         // 先將異常狀況處理掉
 4         if (arr == null || arr.length < 2) {
 5             return;
 6         }
 7         if (right <= left) {
 8             return;
 9         }
10         if (right - left == 1 && arr[left] <= arr[right]) {
11             return;
12         }
13         // 取第一個數爲基準數(基數取哪一個都行,此處是爲了方便)
14         int index = arr[left];
15         int i = left + 1; // 左指針
16         int j = right; // 右指針
17         while (i < j && i < right && j > left) { // 設置指針的移動邊界
18             while (arr[j] > index && j > left) {j--;} // 找到從右邊數第一個比index小的數
19             while (arr[i] < index && i < right) {i++;} // 找到從左邊數第一個比index大的數
20             if (i < j) { // 交換這兩個數  若是i == j,說明兩者定位到了同一個位置,則不用交換;若是i > j,說明兩者已經相遇而後背向而行了,也不交換
21                 int temp = arr[i];
22                 arr[i] = arr[j];
23                 arr[j] = temp;
24             }
25         }
26         // 執行完上面循環後,arr已是左邊比index小,右邊比index大的數組了,只是基準數仍在基準位置left處,需放到它應該在的位置
27         if (j != left && arr[j] != arr[left]) {
28             // j最後停留位置的數,確定是一個小於等於index的值,因此若是不是同一個位置的話,直接將兩者調換一下位置便可
29             int temp = arr[j];
30             arr[j] = arr[left];
31             arr[left] = temp;
32         }
33         quickSort(arr, left, j-1); // 將基準數左邊排序
34         quickSort(arr, j+1, right); // 將基準數右邊排序
35     }

此次的排序算法就到這裏,若是有不妥之處,還請道友指正。後面若是遇到有意思的算法題,也會跟道友們分享,下期見!blog

相關文章
相關標籤/搜索