1. 程式人生 > >高階排序演算法【2】--快速排序、歸併排序、堆排序

高階排序演算法【2】--快速排序、歸併排序、堆排序

4、快速排序

  • 從數列中挑出一個元素,稱為基準; 
  • 重新排列數列,所有元素比基準小的擺放在基準前面,所有元素比基準大的擺在基準後面; 
  • 在這個分割槽結束之後,該基準就位於數列的中間位置; 
  • 遞迴地對基準左右兩邊的數列進行排序。
快速排序程式碼——第一步

def quick_sort(data, left, right):
    if left < right:
        mid = partition(data, left, right)
        quick_sort(data, left, mid - 1)
        quick_sort(data, mid + 1, right)

快速排序程式碼——第二步
def partition(data, left, right):
    left =0;right = len(data)-1
    tmp = data[left]
    while left < right:
        while left < right and data[right] >= tmp:
            right -= 1
        data[left] = data[right]
        while left < right and data[left] <= tmp:
            left += 1
        data[right] = data[left]
    data[left] = tmp  ###很顯然,當left ==right時,迴圈會終止,即實現了左邊的數都比tmp小,右邊的數都比tmp大
    return left  ##left==right,所以返回left和right是一樣的結果

5、歸併排序(Merge Sort)

        分治法:很多有用的演算法結構上是遞迴的,為了解決一個特定問題,演算法一次或者多次遞迴呼叫其自身以解決若干子問題。 這些演算法典型地遵循分治法的思想:將原問題分解為幾個規模較小但是類似於原問題的子問題,遞迴求解這些子問題, 然後再合併這些問題的解來建立原問題的解。

現在我們就來看下歸併排序是是如何利用分治法解決問題的。

  • 分解:將待排序的 n 個元素分成各包含 n/2 個元素的子序列
  • 解決:使用歸併排序遞迴排序兩個子序列
  • 合併:合併兩個已經排序的子序列以產生已排序的答案

                     

當陣列被完全分隔成只有單個元素的陣列時,我們需要把它們合併回去,每次兩兩合併成一個有序的序列。

                    

一次歸併程式碼
def merge(li, low, mid, high):
    i = low      #這裡的low是左半段的第一個元素的索引
    j = mid + 1  #這裡設定的mid是左半段的最後一個元素的索引,因此j就是右半段的第一個元素的索引
    ltmp = []    #新增一個列表來儲存
    while i <= mid and j <= high:
        if li[i] <= li[j]:
            ltmp.append(li[i])
            i += 1
        else:
            ltmp.append(li[j])
            j += 1
    while i <= mid:
        ltmp.append(li[i])
        i += 1
    while j <= high:
        ltmp.append(li[j])
        j += 1
    li[low:high + 1] = ltmp
遞迴的呼叫歸併排序
def merge_sort(li, low, high):
    if low < high:
    mid = (low + high) // 2
    mergesort(li, low, mid)
    mergesort(li, mid + 1, high)
    merge(li, low, mid, high)

演算法分析:

時間複雜度:總的代價是 cnlg(n)+cn ,忽略常數項可以認為是 O(nlg(n))

空間複雜度:o(n)

6、堆排序

     為什麼我要將堆排序放在最後呢,相對來說,堆排序是高階排序演算法中相對不好理解的一中排序演算法,我個人的看法而已。

這涉及了二叉樹的理解,以及堆的理解,利用堆資料結構所設計的一種排序演算法,通過每次彈出堆頂元素實現排序。

動畫演示:

                

堆排序的過程:

  1. 建立堆
  2. 得到堆頂元素
  3. 去掉堆頂,將堆的最後一個元素放到堆頂,此時可通過一次調整重新使堆有序
  4. 堆頂元素為第二大元素
  5. 重複步驟3,直到堆變空

                                  

## 調整的過程
def sift(data, low, high):
    i = low          #當前父節點
    j = 2 * i + 1    #該父節點的左子節點
    tmp = data[i]
    while j <= high: #左節點在堆裡
        if j < high and data[j] < data[j + 1]:  ## 有右節點,且大於左子節點
            j += 1                    ##由於是先判斷了的,就是為了找出左右子節點中的較大者
        if tmp < data[j]:        ##判斷父節點是否小於子節點中的較大者
            data[i] = data[j]    ##小於的話,就相互交換
            i = j                ##交換之後,交換的子節點成為父節點,此時原子節點處是空著的,讓交換後的父節點成為新的子節點,在執行迴圈
            j = 2 * i + 1        ##到這裡,在判斷其左子節點是否在堆裡,在就繼續執行迴圈,不在就退出
         else:
             break
    data[i] = tmp      ##迴圈結束後,將最開始的父節點交換到最後迴圈結束時的那個父節點上
def heap_sort(data):
    n = len(data)
    for i in range(n // 2 - 1, -1, -1):  ##這裡的n//2是最後一個元素的父節點,從後往前的建堆
        sift(data, i, n - 1)    ### 迴圈結束時,一個堆就建好了
    for i in range(n - 1, -1, -1):    ##是為了出數,也可以新開一個列表,來儲存堆頂元素,出一個元素後,堆的個數同時減一
        data[0], data[i] = data[i], data[0]   
        sift(data, 0, i - 1)

本來還想記錄下希爾排序的,不過用的機會不多,這裡也不做介紹了。我覺得掌握前面的幾種排序演算法,就已經夠了。

堆排序、快速排序、歸併排序小結:

1)三種排序演算法的時間複雜度都是o(nlgn)

2)一般情況下,就執行時間而言:         快速<歸併<堆

3)三種排序的缺點:

  • 快速排序:極端情況下,排序效率低
  • 歸併排序:需要額外的記憶體開銷
  • 堆排序:在快的排序演算法中相對較慢

這裡截張圖作為總結,注這裡的穩定性主要是說比如我們遇到相同的數時,穩定性好的,能保持先前的狀態

               

參考資料 :