日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

本文分享自華為云社區《深入淺出八種排序算法-云社區-華為云》,作者:嵌入式視覺 。

歸并排序和快速排序是兩種稍微復雜的排序算法,它們用的都是分治的思想,代碼都通過遞歸來實現,過程非常相似。理解歸并排序的重點是理解遞推公式和 merge() 合并函數。

一,冒泡排序(Bubble Sort)

排序算法是程序員必須了解和熟悉的一類算法,排序算法有很多種,基礎的如:冒泡、插入、選擇、快速、歸并、計數、基數和桶排序等。

冒泡排序只會操作相鄰的兩個數據。每次冒泡操作都會對相鄰的兩個元素進行比較,看是否滿足大小關系要求,如果不滿足就讓它倆互換。一次冒泡會讓至少一個元素移動到它應該在的位置,重復 n 次,就完成了 n 個數據的排序工作。

總結:如果數組有 n 個元素,最壞情況下,需要進行 n 次冒泡操作。

基礎的冒泡排序算法的 C++ 代碼如下:

// 將數據從小到大排序
void bubbleSort(int array[], int n){
    if (n<=1) return;
    for(int i=0; i<n; i++){
        for(int j=0; j<n-i; j++){
            if (temp > a[j+1]){
                temp = array[j]
                a[j] = a[j+1];
                a[j+1] = temp;
            }
        }
    }
}

實際上,以上的冒泡排序算法還可以優化,當某次冒泡操作已經不再進行數據交換時,說明數組已經達到有序,就不需要再繼續執行后續的冒泡操作了。優化后的代碼如下:

// 將數據從小到大排序
void bubbleSort(int array[], int n){
    if (n<=1) return;
    for(int i=0; i<n; i++){
        // 提前退出冒泡循環發標志位
        bool flag = False;
        for(int j=0; j<n-i; j++){
            if (temp > a[j+1]){
                temp = array[j]
                a[j] = a[j+1];
                a[j+1] = temp;
                flag = True; // 表示本次冒泡操作存在數據交換
            }
        }
        if(!flag) break;  // 沒有數據交換,提交退出
    }
}

冒泡排序的特點

  1. 冒泡過程只涉及相鄰元素的交換,只需要常量級的臨時空間,故空間復雜度為O(1),是原地排序算法
  2. 當有相鄰的兩個元素大小相等的時候,我們不做交換,相同大小的數據在排序前后不會改變順序,所以是穩定排序算法
  3. 最壞情況和平均時間復雜度都為O(n2),最好時間復雜度是O(n)。

二,插入排序(Insertion Sort)

  1. 插入排序算法將數組中的數據分為兩個區間:已排序區間和未排序區間。最初始的已排序區間只有一個元素,就是數組的第一個元素。
  2. 插入排序算法的核心思想就是取未排序區間的一個元素,在已排序區間中找到一個合適的位置插入,并保證已排序區間數據一直有序。
  3. 重復這個過程,直到未排序區間元素為空,則算法結束。

插入排序和冒泡排序一樣,也包含兩種操作,一種是元素的比較,一種是元素的移動
當我們需要將一個數據 a 插入到已排序區間時,需要拿 a 與已排序區間的元素依次比較大小,找到合適的插入位置。找到插入點之后,我們還需要將插入點之后的元素順序往后移動一位,這樣才能騰出位置給元素 a 插入。

插入排序的 C++ 代碼實現如下:

void InsertSort(int a[], int n){
    if (n <= 1) return;
    for (int i = 1; i < n; i++)  // 未排序區間范圍
    {
        
        key  = a[i];    // 待排序第一個元素
        int j = i - 1;  // 已排序區間末尾元素
        // 從尾到頭查找插入點方法
        while(key < a[j] && j >= 0){  // 元素比較
            a[j+1] = a[j];  // 數據向后移動一位
            j--;
        }
        a[j+1] = key;   // 插入數據
    }
}

插入排序的特點:

  1. 插入排序并不需要額外存儲空間,空間復雜度是O(1),所以插入排序也是一個原地排序算法。
  2. 在插入排序中,對于值相同的元素,我們可以選擇將后面出現的元素,插入到前面出現元素的后面,這樣就可以保持原有的前后順序不變,所以插入排序是穩定的排序算法。
  3. 最壞情況和平均時間復雜度都為O(n2),最好時間復雜度是O(n)。

三,選擇排序(Selection Sort)

選擇排序算法的實現思路有點類似插入排序,也分已排序區間和未排序區間。但是選擇排序每次會從未排序區間中找到最小的元素,將其放到已排序區間的末尾。

選擇排序的最好情況時間復雜度、最壞情況和平均情況時間復雜度都為O(n2),是原地排序算法,且是不穩定的排序算法

選擇排序的 C++ 代碼實現如下:

void SelectSort(int a[], int n){
    for(int i=0; i<n; i++){
        int minIndex = i;
        for(int j = i;j<n;j++){
            if (a[j] < a[minIndex]) minIndex = j;
        }
        if (minIndex != i){
            temp = a[i]; 
            a[i] = a[minIndex];
            a[minIndex] = temp;
        }
    }
}

冒泡插入選擇排序總結

 

這三種排序算法,實現代碼都非常簡單,對于小規模數據的排序,用起來非常高效。但是在大規模數據排序的時候,這個時間復雜度還是稍微有點高,所以更傾向于用時間復雜度為O(nlogn) 的排序算法。

特定算法是依賴特定的數據結構的。以上三種排序算法,都是基于數組實現的。

四,歸并排序(Merge Sort)

歸并排序的核心思想比較簡單。如果要排序一個數組,我們先把數組從中間分成前后兩部分,然后對前后兩部分分別排序,再將排好序的兩部分合并在一起,這樣整個數組就都有序了。

歸并排序使用的是分治思想。分治,顧名思義,就是分而治之,將一個大問題分解成小的子問題來解決。小的子問題解決了,大問題也就解決了。

分治思想和遞歸思想有些類似,分治算法一般用遞歸實現。分治是一種解決問題的處理思想,遞歸是一種編程技巧,這兩者并不沖突。

知道了歸并排序用的是分治思想,而分治思想一般用遞歸實現,接下來的重點就是如何用遞歸實現歸并排序。寫遞歸代碼的技巧就是,分析問題得出遞推公式,然后找到終止條件,最后將遞推公式翻譯成遞歸代碼。所以,要想寫出歸并排序的代碼,得先寫出歸并排序的遞推公式

遞推公式:
merge_sort(p…r) = merge(merge_sort(p…q), merge_sort(q+1…r))

終止條件:
p >= r 不用再繼續分解,即區間數組元素為 1 

歸并排序的偽代碼如下:

merge_sort(A, n){
    merge_sort_c(A, 0, n-1)
}
merge_sort_c(A, p, r){
    // 遞歸終止條件
    if (p>=r) then return
    // 取 p、r 中間的位置為 q
    q = (p+r)/2
    // 分治遞歸
    merge_sort_c(A[p, q], p, q)
    merge_sort_c(A[q+1, r], q+1, r)
    // 將A[p...q]和A[q+1...r]合并為A[p...r]  
    merge(A[p...r], A[p...q], A[q+1...r])
}

4.1,歸并排序性能分析

1,歸并排序是一個穩定的排序算法。分析:偽代碼中 merge_sort_c() 函數只是分解問題并沒有涉及移動元素和比較大小,真正的元素比較和數據移動在 merge() 函數部分。在合并過程中保證值相同的元素合并前后的順序不變,歸并排序排序就是一個穩定的排序算法。

2,歸并排序的執行效率與要排序的原始數組的有序程度無關,所以其時間復雜度是非常穩定的,不管是最好情況、最壞情況,還是平均情況,時間復雜度都是O(nlogn)。分析:不僅遞歸求解的問題可以寫成遞推公式,遞歸代碼的時間復雜度也可以寫成遞推公式:

 

一步步分解推導可得T(n)=2k∗T(n/2k)+k∗n 。當T(n/2k)=T(1) 時,也就是n/2k=1,我們得到k=log2n 。我們將k 值代入上面的公式,得到T(n)=Cn+nlog2n 。如果我們用大 O 標記法來表示的話,T(n) 就等于O(nlogn)。所以歸并排序的時間復雜度是O(nlogn)。

3,空間復雜度是 O(n)。分析:遞歸代碼的空間復雜度并不能像時間復雜度那樣累加。盡管算法的每次合并操作都需要申請額外的內存空間,但在合并完成之后,臨時開辟的內存空間就被釋放掉了。在任意時刻,CPU 只會有一個函數在執行,也就只會有一個臨時的內存空間在使用。臨時內存空間最大也不會超過 n 個數據的大小,所以空間復雜度是O(n)。

五,快速排序(Quicksort)

快排的思想是這樣的:如果要排序數組中下標從 p 到 r 之間的一組數據,我們選擇 p 到 r 之間的任意一個數據作為 pivot(分區點)。我們遍歷 p 到 r 之間的數據,將小于 pivot 的放到左邊,將大于 pivot 的放到右邊,將 pivot 放到中間。經過這一步驟之后,數組 p 到 r 之間的數據就被分成了三個部分,前面 p 到 q-1 之間都是小于 pivot 的,中間是 pivot,后面的 q+1 到 r 之間是大于 pivot 的。

根據分治、遞歸的處理思想,我們可以用遞歸排序下標從 p 到 q-1 之間的數據和下標從 q+1 到 r 之間的數據,直到區間縮小為 1,就說明所有的數據都有序了。

遞推公式如下:

遞推公式:
quick_sort(p,r) = quick_sort(p, q-1) + quick_sort(q, r)
終止條件:
p >= r

歸并排序和快速排序總結

歸并排序和快速排序是兩種稍微復雜的排序算法,它們用的都是分治的思想,代碼都通過遞歸來實現,過程非常相似。理解歸并排序的重點是理解遞推公式和 merge() 合并函數。同理,理解快排的重點也是理解遞推公式,還有 partition() 分區函數。

除了以上 5 種排序算法,還有 3 種時間復雜度是O(n) 的線性排序算法:桶排序、計數排序、基數排序。這八種排序算法性能總結如下圖:

 

參考資料

  • 排序(上):為什么插入排序比冒泡排序更受歡迎?
  • 排序(下):如何用快排思想在O(n)內查找第K大元素?

分享到:
標簽:算法
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定