1. 程式人生 > 實用技巧 >常用排序演算法的Python實現

常用排序演算法的Python實現

使用python實現氣泡排序,插入排序,選擇排序,希爾排序,歸併排序,快速排序和堆排序。

氣泡排序

思路:通過交換相鄰的兩個數,使得更小的數在前較大的數在後(或反之),每次便利後,最大的數則被交換至最後。對n個數需要o(n^2)的比較次數,對於包含大量元素的數列排序是很沒有效率的。

基本的python實現:

def BubbleSort(nums):
    for i in range(len(nums)):
        for j in range(1, len(nums)-i):
            if nums[j] < nums[j-1]:
                nums[j], nums[j-1] = nums[j-1], nums[j]

    return nums

插入排序

思路:對於未排序資料,在已排序資料中從後向前掃描,找到相應位置並插入。需要用到o(1)的空間,平均演算法複雜度為o(n^2)(最好情況下是o(n)),同樣的也不適合資料量大的排序應用。

def InsertSort(nums):
    for i in range(len(nums)-1):
        key = nums[i+1]
        for j in range(i, -2, -1):
            if key < nums[j]:
                nums[j+1] = nums[j]
            else:
                break
        nums[j+1] = key
    return nums

選擇排序

思路:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然後,再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾。以此類推,直到所有元素均排序完畢(維基百科)。我寫氣泡排序時,總是會和選擇排序混在一起,與氣泡排序不同的是,每一次交換一對元素,至少有一個將會被移到最終位置上。

def SlectSort(nums):
    for i in range(len(nums)):
        min_idx = i
        for j in range(i+1, len(nums)):
            if nums[j] < nums[min_idx]:
                min_idx = j
        nums[i], nums[min_idx] = nums[min_idx], nums[i]
    return nums

雖然總共比較次數需要o(n^2),但是交換次數是o(n),因此會比氣泡排序要快。

希爾排序

思路:通過將比較的元素分為幾個區域從而提升插入排序的效能,然後逐漸遞減步長,到了最後一步,即是普通的插入排序了。時間複雜度根據陣列長度的變化而變化。

def ShellSort(nums):
    step = len(nums) / 2
    while step:
        for i in range(step):
            nums[i::step] = InsertSort(nums[i::step])
        step /= 2
    return nums

優化過後,不需要每次都重新分配空間:

def ShellSort1(nums):
    step = len(nums) / 2
    while step:
        for i in range(step, len(nums)):
            key, j = nums[i], i
            # 插入排序
            while j-step >= 0 and nums[j-step] > key:
                nums[j] = nums[j-step]
                j -= step
            nums[j] = key
        step /= 2
    return nums

歸併排序

思路:將陣列分成兩部分,分別對兩部分陣列排序,再進行合併。因此需要兩步:1.遞迴分解陣列,2. 再進行合併。平均/最壞/最優時間複雜度為o(nlogn)

def MergeSort(nums):
    def helpfunc(nums):
        start, end = 0, len(nums)-1
        if start < end:
            # 選擇mid偏向於end的原因是
            # 當start最終等於mid時
            # 會使得nums[:mid]永遠為空,nums[mid:]永遠為nums[:],從而進入死迴圈
            mid = end - (end - start) / 2
            nums1 = helpfunc(nums[:mid])
            nums2 = helpfunc(nums[mid:])
            return MergeArray(nums1, nums2)
        return nums
    nums = helpfunc(nums)
    return nums

def MergeArray(nums1, nums2):
    nums = []
    i, j = 0, 0
    while i < len(nums1) and j < len(nums2):
        if nums1[i] < nums2[j]:
            nums.append(nums1[i])
            i += 1
        else:
            nums.append(nums2[j])
            j += 1
    nums.extend(nums1[i:] or nums2[j:])
    return nums

快速排序

思路:每次選取一個基準數,分別從兩端開始遍歷,將比基準數小的值放在前面,比基準數大的值放在後面,遞迴對基準值兩端進行排序。平均/最優時間複雜度為o(nlogn),最壞時間為o(n^2)

def QuickSort(nums):
    def helpfunc(start, end):
        if start >= end:
            return
        left, right = start, end
        # 每次選取最左端的值為基準值
        key = nums[left]
        # left始終放置比key小的值,right始終放置比key大的值
        while left < right:
        		# 從後往前遍歷
            while right >= 0 and nums[right] > key:
                right -= 1
            if left < right:
                nums[left] = nums[right]
                left += 1
						# 從前往後遍歷
            while left < right and nums[left] < key:
                left += 1
            if left < right:
                nums[right] = nums[left]
                right -= 1
        # 直到left=right,找到該key的所在位置
        nums[right] = key
        helpfunc(start, right-1)
        helpfunc(right+1, end)
    helpfunc(0, len(nums)-1)
    return nums

堆排序

思路:通過構造一個最大堆或者最小堆,每次將最後一個元素與堆頂元素互換,再重新調整為堆,這樣每次取出的元素要麼是最大,要麼就是最小。 初始化建堆的時間複雜度為o(n),排序重建堆的時間複雜度為o(nlogn),所以總的時間複雜度為o(n+nlogn)=o(nlogn)

對於節點i來說,其左右節點分別為2*i+12*i+2,每次調整隻需要從非葉子節點開始。若陣列長度為n,則堆從第n/2-1個節點開始往下調整。

def HeapSort(nums):
    build_heap(nums)
    for i in range(len(nums)-1, -1, -1):
        nums[0], nums[i] = nums[i], nums[0]
        max_heapify(0, i)
    return nums

# 將較大元素下沉
def max_heapify(i, length):
    key = nums[i]
    left = 2*i+1
    while left < length:
        # 找出左右兩個節點更小的那個
        if left+1 < length and nums[left] > nums[left+1]:
            left += 1
        # 根節點比子節點都小,無需調整
        if key < nums[left]:
            break
        # 需要注意python的連續賦值
        nums[left], nums[i] = nums[i], nums[left]
        i = left
        left, key = 2*i+1, nums[i]

# 構造堆
def build_heap(nums):
    for i in range(len(nums)/2-1, -1, -1):
        max_heapify(i, len(nums))

References

白話經典算法系列之八 MoreWindows白話經典演算法之七大排序總結篇

堆排序時間複雜度分析