數據結構中排序方法2(附python代碼)

希爾排序

     希爾排序是D.L.shell於1959年提出來的排序算法,在這之前(見排序1),排序算法的時間複雜度基本都是O(n^2),希爾排序是突破這個時間複雜度的第一批算法之一。

   基本原理

     上面講的插入排序,在記錄本身比較有序或者當記錄較少時,比較高效。而希爾排序就是先使記錄基本有序然後再對全體記錄進行一次插入排序。所謂基本有序,就是小的關鍵字基本在前面,大的基本在後面,不大不小的基本在中間,像{2,1,3,6,4,7,5,8,9}這樣的就可以稱爲基本有序了。但像{1,5,9,3,7,8,2,4,6}這樣的9在第三位,2在倒數第三位就談不上基本有序

   python代碼

def shell_sort(arr):
    count = len(arr)
    step = 2
    group = int(count / step)
    while group > 0:
        for i in range(0,group):
            j = i + group
            while j < count:
                k = j - group
                key = arr[j]
                while k >= 0:
                    if arr[k] > key:
                        arr[k+group] = arr[k]
                        arr[k] = key
                    k -= group
                j += group
        group  = int(group/step)
    return arr

   希爾排序算法

    考慮到是記錄基本有序,可以採用增量比較法,arr[i]先不直接與arr[i+1]比較,而是與arr[i+increment]比較,這個increment就稱之爲增量,在python代碼中用group表示。假設初始記錄arr爲{9,1,5,8,3,7,4,6,2},初始增量group=4

      i = 0時,比較arr[0]=9與arr[4]=3的大小,因爲arr[0]>arr[4],所以交換9,3位置,記錄爲{3,1,5,8,9,7,4,6,2}

同時比較arr[4]>arr[8],故交換,arr[0]>arr[4],交換,得到記錄{2,1,5,8,3,7,4,6,9}

      i= 1時,比較arr[1]=1與arr[5]=7的大小,前者小於後者,不交換

      i= 2時,比較arr[2]=5與arr[6]=4的大小,前者大於後者,故交換,記錄爲{2,1,4,8,9,7,5,6,9}

      i= 3時,比較arr[3]=8與arr[7]=6的大小,前者大於後者,故交換,記錄爲{2,1,4,6,3,7,5,8,9}

     此時序列已經達到基本有序,接下來減少增量group=2,循環一輪後,再次縮小group=1,便可以使記錄達到有序

    希爾排序複雜度分析

      經過上面剖析,發現希爾排序是將相隔某個"增量"的記錄組成一個子序列,實現移動式的跳躍,使得效率升高,那麼增量的選擇就十分重要,當增量序列爲2^(t+k+1)-1時,可以獲得不錯效果,時間複雜度是O(n^(3/2)),優於直接排序,另外希爾排序並不是一種穩定的排序算法


     堆排序

    之前的簡單選擇排序,選取最小的記錄需要比較n-1次,可惜這樣的操作並沒有把每一趟的結果都保存下來,後面的比較中又執行了一次,因而記錄的比較次數較多。如果能做到在每次選擇最小記錄的同時,根據比較結果對其他記錄做出相應調整,那麼總體的效率就會非常高了。

      基本概念

    堆是具有下列性質的完全二叉樹:每個節點的值都大於或等於其左右孩子結點的值,稱爲大頂堆(下圖左);或每個節點的值都小於或等於其左右孩子結點的值,稱爲小頂堆(下圖右)


      堆排序算法

     將待排序的序列構成一個大頂堆。此時,整個序列的最大值就是堆頂的根節點。將它移走(其實就是將其與堆數組的末尾元素交換,此時末尾元素就是最大值),然後將剩餘的n-1個序列重新構造一個堆,這樣就會得到n個元素中次小值。如此反覆執行,便能得到一個有序序列了。

     python代碼

def heap_sort(lst):
    for start in range((len(lst) - 2) // 2, -1, -1):
        siftdown(lst, start, len(lst) - 1)
    for end in range(len(lst) - 1, 0, -1):
        lst[end], lst[0] = lst[0], lst[end]
        siftdown(lst, 0, end - 1)
    return lst


def siftdown(lst, start, end):
    root = start
    while True:
        child = 2 * root + 1
        if child > end: break
        if child + 1 <= end and lst[child] < lst[child + 1]:
            child += 1
        if lst[child] > lst[root]:
            lst[child], lst[root] = lst[root], lst[child]
            root = child
        else:
            break
heap_sort(l)


歸併排序(Merging Sort)

   基本思想: 利用歸併的思想實現的排序方法。它的原理是假設初始序列含有n個記錄,則可以看成n個有序的子序列,每個子序列的長度爲1,然後兩兩歸併,得到[n/2]個長度爲2或1的有序子序列;再兩兩歸併,......,如此重複,直至得到一個長度爲n的有序序列爲止,這種排序方法稱爲2路歸併排序。

   排序過程


                                                                                                                              圖來自《大話數據結構》

   python代碼

def merge_sort(arr):
    if len(arr) <= 1: #子序列
        return arr
    mid = (len(arr) // 2)
    left = merge_sort(arr[:mid])#遞歸的切片操作
    right = merge_sort(arr[mid:len(arr)])
    result = []

    while len(left) > 0 and len(right) > 0:
        if (left[0] <= right[0]):
            result.append(left.pop(0))
        else:
            result.append(right.pop(0))
            #j+= 1

    if (len(left) > 0):
        result.extend(merge_sort(left))
    else:
        result.extend(merge_sort(right))
    return result

merge_sort(a)

  複雜度分析

    一趟歸併需要將記錄中相鄰的長度爲h的有序序列進行兩兩歸併,這需要將待排序序列中的所有記錄掃描一遍,因此需要耗費

O(n)時間,而由完全二叉樹的深度可知,整個歸併排序需要進行logn次,因此,總的時間複雜度爲O(nlogn),而且這是歸併排序算法中最好、最壞、平均的時間性能。空間複雜度爲O(n+logn)

   歸併排序不存跳躍,因此算是一種穩定的排序算法

     

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