常用比較類排序算法

前言

本篇文章基本是從
常用排序算法總結(jié)(一)
快速排序
引申而來,其中大部分代碼和描述都來自這兩篇文章。

時間復(fù)雜度

在計算機(jī)科學(xué)中,算法的時間復(fù)雜度是一個函數(shù),它定量描述了該算法的運行時間。這是一個代表算法輸入值的字符串的長度的函數(shù)。時間復(fù)雜度常用大O符號表述,不包括這個函數(shù)的低階項和首項系數(shù)。使用這種方式時,時間復(fù)雜度可被稱為是漸近的,亦即考察輸入值大小趨近無窮時的情況。例如,如果一個算法對于任何大小為n的輸入,它至多需要 5n^3 + 3n 的時間運行完畢,那么它的漸近時間復(fù)雜度是 O(n^3)。

空間復(fù)雜度

時間復(fù)雜度是指在計算機(jī)科學(xué)與工程領(lǐng)域完成一個算法所需要的時間,是衡量一個算法優(yōu)劣的重要參數(shù)。時間復(fù)雜度越小,說明該算法效率越高,則該算法越有價值。
空間復(fù)雜度是指計算機(jī)科學(xué)領(lǐng)域完成一個算法所需要占用的存儲空間,一般是輸入?yún)?shù)的函數(shù)。它是算法優(yōu)劣的重要度量指標(biāo),一般來說,空間復(fù)雜度越小,算法越好。

列表

算法復(fù)雜度對比.jpg

穩(wěn)定性

排序算法穩(wěn)定性的簡單形式化定義為:如果Ai = Aj,排序前Ai在Aj之前,排序后Ai還在Aj之前,則稱這種排序算法是穩(wěn)定的。通俗地講就是保證排序前后兩個相等的數(shù)的相對順序不變。

對于不穩(wěn)定的排序算法,只要舉出一個實例,即可說明它的不穩(wěn)定性;而對于穩(wěn)定的排序算法,必須對算法進(jìn)行分析從而得到穩(wěn)定的特性。需要注意的是,排序算法是否為穩(wěn)定的是由具體算法決定的,不穩(wěn)定的算法在某種條件下可以變?yōu)榉€(wěn)定的算法,而穩(wěn)定的算法在某種條件下也可以變?yōu)椴环€(wěn)定的算法。

排序算法如果是穩(wěn)定的,那么從一個鍵上排序,然后再從另一個鍵上排序,第一個鍵排序的結(jié)果可以為第二個鍵排序所用。基數(shù)排序就是這樣,先按低位排序,逐次按高位排序,低位排序后元素的順序在高位也相同時是不會改變的。

簡單選擇排序

選擇排序也是一種簡單直觀的排序算法。它的工作原理很容易理解:初始時在序列中找到最小(大)元素,放到序列的起始位置作為已排序序列;然后,再從剩余未排序元素中繼續(xù)尋找最小(大)元素,放到已排序序列的末尾。以此類推,直到所有元素均排序完畢。

注意選擇排序與冒泡排序的區(qū)別:冒泡排序通過依次交換相鄰兩個順序不合法的元素位置,從而將當(dāng)前最小(大)元素放到合適的位置;而選擇排序每遍歷一次都記住了當(dāng)前最小(大)元素的位置,最后僅需一次交換操作即可將其放到合適的位置。

// 分類 -------------- 內(nèi)部比較排序
// 數(shù)據(jù)結(jié)構(gòu) ---------- 數(shù)組
// 最差時間復(fù)雜度 ---- O(n^2)
// 最優(yōu)時間復(fù)雜度 ---- O(n^2)
// 平均時間復(fù)雜度 ---- O(n^2)
// 所需輔助空間 ------ O(1)
// 穩(wěn)定性 ------------ 不穩(wěn)定
void SelectionSort(int A[], int n) {
    for (int i = 0; i < n - 1; i++) {        // i為已排序序列的末尾
        int min = i;
        for (int j = i + 1; j < n; j++) {     // 未排序序列
            if (A[j] < A[min]) {              // 找出未排序序列中的最小值
                min = j;
            }
        }
        if (min != i){
            Swap(A, min, i);    // 放到已排序序列的末尾,該操作很有可能把穩(wěn)定性打亂,所以選擇排序是不穩(wěn)定的排序算法
        }
    }
}

選擇排序是不穩(wěn)定的排序算法,不穩(wěn)定發(fā)生在最小元素與A[i]交換的時刻。

比如序列:{ 5, 8, 5, 2, 9 },一次選擇的最小元素是2,然后把2和第一個5進(jìn)行交換,從而改變了兩個元素5的相對次序。

冒泡排序

重復(fù)地走訪過要排序的元素,依次比較相鄰兩個元素,如果他們的順序錯誤就把他們調(diào)換過來,直到?jīng)]有元素再需要交換,排序完成。這個算法的名字由來是因為越小(或越大)的元素會經(jīng)由交換慢慢“浮”到數(shù)列的頂端。

冒泡排序算法的運作如下:

  • 比較相鄰的元素,如果前一個比后一個大,就把它們兩個調(diào)換位置。
  • 對每一對相鄰元素作同樣的工作,從開始第一對到結(jié)尾的最后一對。這步做完后,最后的元素會是最大的數(shù)。
  • 針對所有的元素重復(fù)以上的步驟,除了最后一個。
  • 持續(xù)每次對越來越少的元素重復(fù)上面的步驟,直到?jīng)]有任何一對數(shù)字需要比較。
// 分類 -------------- 內(nèi)部比較排序
// 數(shù)據(jù)結(jié)構(gòu) ----------  數(shù)組
// 最差時間復(fù)雜度 ----   O(n^2)
// 最優(yōu)時間復(fù)雜度 ----   如果能在內(nèi)部循環(huán)第一次運行時,使用一個旗標(biāo)來表示有無需要交換的可能,可以把最優(yōu)時間復(fù)雜度降低到O(n)
// 平均時間復(fù)雜度 ----   O(n^2)
// 所需輔助空間 ------   O(1)
// 穩(wěn)定性 ------------  穩(wěn)定
void bubbleSort(int[] arr) {
    for (int i = 0; i < arr.length - 1; i++) {
        //設(shè)定一個標(biāo)記,若為true,則表示此次循環(huán)沒有進(jìn)行交換,也就是待排序列已經(jīng)有序,排序已然完成
        boolean flag = true;。
        for (int j = 0; j < arr.length - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
            swap(arr,j,j+1);
            flag = false;
            }
        }
        if (flag) {
            break;
        }
    }
}

最好的情況是已經(jīng)排好序,只需要n-1比較即可完成;最差的情況是倒序,需要n-1 + n-2 + n-3 + ...+ 2 + 1 = n(n-1)/2次比較,時間復(fù)雜度是O(n*n)。

這種寫法,冒泡排序是穩(wěn)定的,如果改成arr[j] >= arr[j + 1],那么就是不穩(wěn)定的。

雞尾酒排序(冒泡排序的優(yōu)化)

雞尾酒排序,也叫定向冒泡排序,是冒泡排序的一種改進(jìn)。此算法與冒泡排序的不同處在于從低到高然后從高到低,而冒泡排序則僅從低到高去比較序列里的每個元素。他可以得到比冒泡排序稍微好一點的效能。

// 分類 -------------- 內(nèi)部比較排序
// 數(shù)據(jù)結(jié)構(gòu) ---------- 數(shù)組
// 最差時間復(fù)雜度 ---- O(n^2)
// 最優(yōu)時間復(fù)雜度 ---- 如果序列在一開始已經(jīng)大部分排序過的話,會接近O(n)
// 平均時間復(fù)雜度 ---- O(n^2)
// 所需輔助空間 ------ O(1)
// 穩(wěn)定性 ------------ 穩(wěn)定
void CocktailSort(int A[]) {
    int n = A.length;
    int left = 0;                            // 初始化邊界
    int right = n - 1;
    while (left < right) {
        for (int i = left; i < right; i++) {   // 前半輪,將最大元素放到后面
            if (A[i] > A[i + 1]) {
                Swap(A, i, i + 1);
            }
        }
        right--;
        for (int i = right; i > left; i--) {  // 后半輪,將最小元素放到前面
            if (A[i - 1] > A[i]) {
                Swap(A, i - 1, i);
            }
        }
        left++;
    }
}

以序列(2,3,4,5,1)為例,雞尾酒排序只需要訪問一次序列就可以完成排序,但如果使用冒泡排序則需要四次。但是在亂數(shù)序列的狀態(tài)下,雞尾酒排序與冒泡排序的效率都很差勁。

簡單插入排序

直接插入排序基本思想是每一步將一個待排序的記錄,插入到前面已經(jīng)排好序的有序序列中去,直到插完所有元素為止。

插入排序在實現(xiàn)上,通常采用in-place排序(即只需用到O(1)的額外空間的排序),因而在從后向前掃描過程中,需要反復(fù)把已排序元素逐步向后挪位,為最新元素提供插入空間。

具體算法描述如下:

  • 從第一個元素開始,該元素可以認(rèn)為已經(jīng)被排序
  • 取出下一個元素,在已經(jīng)排序的元素序列中從后向前掃描
  • 如果該元素(已排序)大于新元素,將該元素移到下一位置
  • 重復(fù)步驟3,直到找到已排序的元素小于或者等于新元素的位置
  • 將新元素插入到該位置后
  • 重復(fù)步驟2~5
// 分類 ------------- 內(nèi)部比較排序
// 數(shù)據(jù)結(jié)構(gòu) ---------- 數(shù)組
// 最差時間復(fù)雜度 ---- 最壞情況為輸入序列是降序排列的,此時時間復(fù)雜度O(n^2)
// 最優(yōu)時間復(fù)雜度 ---- 最好情況為輸入序列是升序排列的,此時時間復(fù)雜度O(n)
// 平均時間復(fù)雜度 ---- O(n^2)
// 所需輔助空間 ------ O(1)
// 穩(wěn)定性 ------------ 穩(wěn)定
void insertionSort(int[] arr) {
    for (int i = 1; i < arr.length; i++) {
        int j = i;
        while (j > 0 && arr[j] < arr[j - 1]) {
            swap(arr,j,j-1);
            j--;
        }
    }
}

簡單插入排序在最好情況下,需要比較n-1次,無需交換元素,時間復(fù)雜度為O(n);在最壞情況下,時間復(fù)雜度依然為O(n2)。插入排序不適合對于數(shù)據(jù)量比較大的排序應(yīng)用。但是,如果需要排序的數(shù)據(jù)量很小,比如量級小于千,那么插入排序還是一個不錯的選擇。 插入排序在工業(yè)級庫中也有著廣泛的應(yīng)用,在STL的sort算法和stdlib的qsort算法中,都將插入排序作為快速排序的補(bǔ)充,用于少量元素的排序(通常為8個或以下)。

二分插入排序(插入排序優(yōu)化)

對于插入排序,如果比較操作的代價比交換操作大的話,可以采用二分查找法來減少比較操作的次數(shù),我們稱為二分插入排序。

// 分類 -------------- 內(nèi)部比較排序
// 數(shù)據(jù)結(jié)構(gòu) ---------- 數(shù)組
// 最差時間復(fù)雜度 ---- O(n^2)
// 最優(yōu)時間復(fù)雜度 ---- O(nlogn)
// 平均時間復(fù)雜度 ---- O(n^2)
// 所需輔助空間 ------ O(1)
// 穩(wěn)定性 ------------ 穩(wěn)定
void InsertionSortDichotomy(int A[]) {
    int n = A.length;
    for (int i = 1; i < n; i++) {
        int target = A[i];
        int left = 0;
        int right = i - 1;
        while (left <= right) {   // 二分查找,找到新元素在排好序的序列中的位置
            int mid = (left + right) / 2;
            if (A[mid] > target)
                right = mid - 1;
            else
                left = mid + 1;
        }
        for (int j = i - 1; j >= left; j--) { // 整體后移一位
            A[j + 1] = A[j];
        }
        A[left] = target;
    }
}

當(dāng)n較大時,二分插入排序的比較次數(shù)比直接插入排序的最差情況好得多,但比直接插入排序的最好情況要差,所當(dāng)以元素初始序列已經(jīng)接近升序時,直接插入排序比二分插入排序比較次數(shù)少。二分插入排序元素移動次數(shù)與直接插入排序相同,依賴于元素初始序列。

希爾排序

希爾排序,也叫遞減增量排序,是插入排序的一種更高效的改進(jìn)版本。希爾排序是不穩(wěn)定的排序算法。

希爾排序是基于插入排序的以下兩點性質(zhì)而提出改進(jìn)方法的:

  • 插入排序在對幾乎已經(jīng)排好序的數(shù)據(jù)操作時,效率高,即可以達(dá)到線性排序的效率
  • 插入排序一般來說是低效的,因為插入排序每次只能將數(shù)據(jù)移動一位

希爾排序通過將比較的全部元素分為幾個區(qū)域來提升插入排序的性能。這樣可以讓一個元素可以一次性地朝最終位置前進(jìn)一大步。然后算法再取越來越小的步長進(jìn)行排序,算法的最后一步就是普通的插入排序,但是到了這步,需排序的數(shù)據(jù)幾乎是已排好的了(此時插入排序較快)。

假設(shè)有一個很小的數(shù)據(jù)在一個已按升序排好序的數(shù)組的末端。如果用復(fù)雜度為O(n^2)的排序(冒泡排序或直接插入排序),可能會進(jìn)行n次的比較和交換才能將該數(shù)據(jù)移至正確位置。而希爾排序會用較大的步長移動數(shù)據(jù),所以小數(shù)據(jù)只需進(jìn)行少數(shù)比較和交換即可到正確位置。

// 分類 -------------- 內(nèi)部比較排序
// 數(shù)據(jù)結(jié)構(gòu) ---------- 數(shù)組
// 最差時間復(fù)雜度 ---- 根據(jù)步長序列的不同而不同。已知最好的為O(n(logn)^2)
// 最優(yōu)時間復(fù)雜度 ---- O(n)
// 平均時間復(fù)雜度 ---- 根據(jù)步長序列的不同而不同。
// 所需輔助空間 ------ O(1)
// 穩(wěn)定性 ------------ 不穩(wěn)定
void ShellSort(int A[]) {
    int n = A.length;
    int h = n/3;
    while (h <= n) {                         // 生成初始增量
        h = 3 * h + 1;
    }
    while (h >= 1){
        for (int i = h; i < n; i++){
            int j = i - h;
            int target = A[i];
            while (j >= 0 && A[j] > target){
                A[j + h] = A[j];
                j = j - h;
            }
            A[j + h] = target;
        }
        h = (h - 1) / 3;                    // 遞減增量
    }
}
void ShellSort(int a[]) {
    int n = a.length;
    for (int gap = n/3; gap > 0; gap = gap/3) {
        for(int j = gap; j < n; j++) {
            for(int i = j - gap; i >= 0 && a[i] > a[i + gap]; i -= gap) {
                swap(a, i, i + gap);
            }
        }
    }
}

希爾排序是不穩(wěn)定的排序算法,雖然一次插入排序是穩(wěn)定的,不會改變相同元素的相對順序,但在不同的插入排序過程中,相同的元素可能在各自的插入排序中移動,最后其穩(wěn)定性就會被打亂。

比如序列:{ 3, 5, 10, 8, 7, 2, 8, 1, 20, 6 },h=2時分成兩個子序列 { 3, 10, 7, 8, 20 } 和 { 5, 8, 2, 1, 6 } ,未排序之前第二個子序列中的8在前面,現(xiàn)在對兩個子序列進(jìn)行插入排序,得到 { 3, 7, 8, 10, 20 } 和 { 1, 2, 5, 6, 8 } ,即 { 3, 1, 7, 2, 8, 5, 10, 6, 20, 8 } ,兩個8的相對次序發(fā)生了改變。

歸并排序

歸并排序是創(chuàng)建在歸并操作上的一種有效的排序算法,效率為O(nlogn),1945年由馮·諾伊曼首次提出。

歸并排序的實現(xiàn)分為遞歸實現(xiàn)與非遞歸(迭代)實現(xiàn)。遞歸實現(xiàn)的歸并排序是算法設(shè)計中分治策略的典型應(yīng)用,我們將一個大問題分割成小問題分別解決,然后用所有小問題的答案來解決整個大問題。非遞歸(迭代)實現(xiàn)的歸并排序首先進(jìn)行是兩兩歸并,然后四四歸并,然后是八八歸并,一直下去直到歸并了整個數(shù)組。

歸并排序算法主要依賴歸并(Merge)操作。歸并操作指的是將兩個已經(jīng)排序的序列合并成一個序列的操作,歸并操作步驟如下:

  • 申請空間,使其大小為兩個已經(jīng)排序序列之和,該空間用來存放合并后的序列
  • 設(shè)定兩個指針,最初位置分別為兩個已經(jīng)排序序列的起始位置
  • 比較兩個指針?biāo)赶虻脑兀x擇相對小的元素放入到合并空間,并移動指針到下一位置
  • 重復(fù)步驟3直到某一指針到達(dá)序列尾
  • 將另一序列剩下的所有元素直接復(fù)制到合并序列尾
// 分類 -------------- 內(nèi)部比較排序
// 數(shù)據(jù)結(jié)構(gòu) ---------- 數(shù)組
// 最差時間復(fù)雜度 ---- O(nlogn)
// 最優(yōu)時間復(fù)雜度 ---- O(nlogn)
// 平均時間復(fù)雜度 ---- O(nlogn)
// 所需輔助空間 ------ O(n)
// 穩(wěn)定性 ------------ 穩(wěn)定

// 遞歸
void sort(int[] arr,int left,int right,int[] temp) {
    if(left < right) {
        int mid = (left+right)/2;
        sort(arr,left,mid,temp);//左邊歸并排序,使得左子序列有序
        sort(arr,mid+1,right,temp);//右邊歸并排序,使得右子序列有序
        merge(arr,left,mid,right,temp);//將兩個有序子數(shù)組合并操作
    }
}

// 非遞歸
void MergeSortIteration(int[] arr, int len, int[] temp) {   // 非遞歸(迭代)實現(xiàn)的歸并排序(自底向上)
    int left, mid, right;// 子數(shù)組索引,前一個為A[left...mid],后一個子數(shù)組為A[mid+1...right]
    for (int i = 1; i < len; i *= 2) {       // 子數(shù)組的大小i初始為1,每輪翻倍
        left = 0;
        while (left + i < len) {            // 后一個子數(shù)組存在(需要歸并)
            mid = left + i - 1;
            right = mid + i < len ? mid + i : len - 1;// 后一個子數(shù)組大小可能不夠
            merge(arr, left, mid, right, temp);
            left = right + 1;               // 前一個子數(shù)組索引向后移動
        }
    }
}

void merge(int[] arr,int left,int mid,int right,int[] temp){
        int i = left;//左序列指針
        int j = mid+1;//右序列指針
        int t = 0;//臨時數(shù)組指針
        while (i<=mid && j<=right){
            if(arr[i]<=arr[j]){
                temp[t++] = arr[i++];
            }else {
                temp[t++] = arr[j++];
            }
        }
        while(i<=mid){//將左邊剩余元素填充進(jìn)temp中
            temp[t++] = arr[i++];
        }
        while(j<=right){//將右序列剩余元素填充進(jìn)temp中
            temp[t++] = arr[j++];
        }
        t = 0;
        //將temp中的元素全部拷貝到原數(shù)組中
        while(left <= right){
            arr[left++] = temp[t++];
        }
    }
}

歸并排序除了可以對數(shù)組進(jìn)行排序,還可以高效的求出數(shù)組小和(即單調(diào)和)以及數(shù)組中的逆序?qū)Γ斠?a href="http://www.lxweimin.com/p/3ab5033074f1" target="_blank">這篇博文。

堆排序

堆排序是指利用堆這種數(shù)據(jù)結(jié)構(gòu)所設(shè)計的一種選擇排序算法。堆排序是一種選擇排序,它的最壞,最好,平均時間復(fù)雜度均為O(nlogn),它也是不穩(wěn)定排序。堆是一種近似完全二叉樹的結(jié)構(gòu)(通常堆是通過一維數(shù)組來實現(xiàn)的),并滿足性質(zhì):以最大堆(也叫大根堆、大頂堆)為例,其中父結(jié)點的值總是大于它的孩子節(jié)點。這篇文章詳解

該數(shù)組從邏輯上講就是一個堆結(jié)構(gòu),我們用簡單的公式來描述一下堆的定義就是:

  • 大頂堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
  • 小頂堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]

堆排序的基本思想是:將待排序序列構(gòu)造成一個大頂堆,此時,整個序列的最大值就是堆頂?shù)母?jié)點。將其與末尾元素進(jìn)行交換,此時末尾就為最大值。然后將剩余n-1個元素重新構(gòu)造成一個堆,這樣會得到n個元素的次小值。如此反復(fù)執(zhí)行,便能得到一個有序序列了。

// 分類 -------------- 內(nèi)部比較排序
// 數(shù)據(jù)結(jié)構(gòu) ---------- 數(shù)組
// 最差時間復(fù)雜度 ---- O(nlogn)
// 最優(yōu)時間復(fù)雜度 ---- O(nlogn)
// 平均時間復(fù)雜度 ---- O(nlogn)
// 所需輔助空間 ------ O(1)
// 穩(wěn)定性 ------------ 不穩(wěn)定

public void sort(int []arr){
    //1.構(gòu)建大頂堆
    for(int i=arr.length/2-1;i>=0;i--){
        //從第一個非葉子結(jié)點從下至上,從右至左調(diào)整結(jié)構(gòu)
        adjustHeap(arr,i,arr.length);
    }
    //2.調(diào)整堆結(jié)構(gòu)+交換堆頂元素與末尾元素
    for(int j=arr.length-1;j>0;j--){
        swap(arr,0,j);//將堆頂元素與末尾元素進(jìn)行交換
        adjustHeap(arr,0,j);//重新對堆進(jìn)行調(diào)整
    }
}

/**
 * 調(diào)整大頂堆(僅是調(diào)整過程,建立在大頂堆已構(gòu)建的基礎(chǔ)上)
 */
public void adjustHeap(int []arr,int i,int length) {
    int temp = arr[i];//先取出當(dāng)前元素i
    for(int k=i*2+1;k<length;k=k*2+1) {//從i結(jié)點的左子結(jié)點開始,也就是2i+1處開始
        if(k+1 < length && arr[k] < arr[k+1]) {//如果左子結(jié)點小于右子結(jié)點,k指向右子結(jié)點
            k++;
        }
        if(arr[k] > temp){//如果子節(jié)點大于父節(jié)點,將子節(jié)點值賦給父節(jié)點(不用進(jìn)行交換)
            arr[i] = arr[k];
            i = k;
        }else{
            break;
        }
    }
    arr[i] = temp;//將temp值放到最終的位置
}

堆排序是一種選擇排序,整體主要由構(gòu)建初始堆+交換堆頂元素和末尾元素并重建堆兩部分組成。其中構(gòu)建初始堆經(jīng)推導(dǎo)復(fù)雜度為O(n),在交換并重建堆的過程中,需交換n-1次,而重建堆的過程中,根據(jù)完全二叉樹的性質(zhì),[log2(n-1),log2(n-2)...1]逐步遞減,近似為nlogn。所以堆排序時間復(fù)雜度一般認(rèn)為就是O(nlogn)級。

堆排序是不穩(wěn)定的排序算法,不穩(wěn)定發(fā)生在堆頂元素與A[i]交換的時刻。比如序列:{ 9, 5, 7, 5 },堆頂元素是9,堆排序下一步將9和第二個5進(jìn)行交換,得到序列 { 5, 5, 7, 9 },再進(jìn)行堆調(diào)整得到{ 7, 5, 5, 9 },重復(fù)之前的操作最后得到{ 5, 5, 7, 9 }從而改變了兩個5的相對次序。

快速排序

快速排序是由東尼·霍爾所發(fā)展的一種排序算法。在平均狀況下,排序n個元素要O(nlogn)次比較。在最壞狀況下則需要O(n^2)次比較,但這種狀況并不常見。事實上,快速排序通常明顯比其他O(nlogn)算法更快,因為它的內(nèi)部循環(huán)可以在大部分的架構(gòu)上很有效率地被實現(xiàn)出來。

快速排序使用分治策略(Divide and Conquer),每一次我們要取一個元素作為樞紐值,以這個數(shù)字來將序列劃分為兩部分。在此我們采用三數(shù)取中法,也就是取左端、中間、右端三個數(shù),然后進(jìn)行排序,將中間數(shù)作為樞紐值。

// 分類 ------------ 內(nèi)部比較排序
// 數(shù)據(jù)結(jié)構(gòu) --------- 數(shù)組
// 最差時間復(fù)雜度 ---- 每次選取的基準(zhǔn)都是最大(或最小)的元素,導(dǎo)致每次只劃分出了一個分區(qū),需要進(jìn)行n-1次劃分才能結(jié)束遞歸,時間復(fù)雜度為O(n^2)
// 最優(yōu)時間復(fù)雜度 ---- 每次選取的基準(zhǔn)都是中位數(shù),這樣每次都均勻的劃分出兩個分區(qū),只需要logn次劃分就能結(jié)束遞歸,時間復(fù)雜度為O(nlogn)
// 平均時間復(fù)雜度 ---- O(nlogn)
// 所需輔助空間 ------ 主要是遞歸造成的棧空間的使用(用來保存left和right等局部變量),取決于遞歸樹的深度,一般為O(logn),最差為O(n)       
// 穩(wěn)定性 ---------- 不穩(wěn)定

public static void quickSort(int[] arr, int left, int right) {
    if(left >= right) {
        return;
    }

    int pivotPosition = partition(arr, left, right);
    quickSort(arr, left, pivotPosition - 1);
    quickSort(arr, pivotPosition + 1, right);
}

public static int partition(int[] arr, int left, int right) {
    int pivot = arr[right];
    int pivotPosition = left;

    for(int i = left; i < right; i++) {
        if(arr[i] < pivot) {
            swap(arr, pivotPosition++, i);
        }
    }

    swap(arr, pivotPosition, right);

    return pivotPosition;
}

快速排序是不穩(wěn)定的排序算法,不穩(wěn)定發(fā)生在基準(zhǔn)元素與A[tail+1]交換的時刻。

比如序列:{ 1, 3, 4, 2, 8, 9, 8, 7, 5 },基準(zhǔn)元素是5,一次劃分操作后5要和第一個8進(jìn)行交換,從而改變了兩個元素8的相對次序。

關(guān)于算法,每一次看都有不同的感受,索性總結(jié)一遍。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 概述 排序有內(nèi)部排序和外部排序,內(nèi)部排序是數(shù)據(jù)記錄在內(nèi)存中進(jìn)行排序,而外部排序是因排序的數(shù)據(jù)很大,一次不能容納全部...
    蟻前閱讀 5,220評論 0 52
  • 概述:排序有內(nèi)部排序和外部排序,內(nèi)部排序是數(shù)據(jù)記錄在內(nèi)存中進(jìn)行排序,而外部排序是因排序的數(shù)據(jù)很大,一次不能容納全部...
    每天刷兩次牙閱讀 3,743評論 0 15
  • 排序的基本概念 在計算機(jī)程序開發(fā)過程中,經(jīng)常需要一組數(shù)據(jù)元素(或記錄)按某個關(guān)鍵字進(jìn)行排序,排序完成的序列可用于快...
    Jack921閱讀 1,459評論 1 4
  • 概述排序有內(nèi)部排序和外部排序,內(nèi)部排序是數(shù)據(jù)記錄在內(nèi)存中進(jìn)行排序,而外部排序是因排序的數(shù)據(jù)很大,一次不能容納全部的...
    Luc_閱讀 2,299評論 0 35
  • 從開始到現(xiàn)在,你知道自己為什么是這個樣子嗎? 今天早上說服兒子跟隨我一起去洪山區(qū)文化大廈觀看一場公益演出,因為我有...
    知子花開閱讀 240評論 0 2