數據結構和算法: 歸併排序/快速排序

快速排序和歸併排序都使用了分治思想. 分治算法通常都用遞歸來實現python

分治: 分而治之, 將一個大問題不斷的分解爲小問題來解決, 小的問題解決了, 大的問題也就解決了.算法

歸併排序

思想: 將原數組不斷分解爲先後兩部分, 直到每一個數組內只有一個元素, 而後不斷進行排序合併, 最後合併爲一個有序數組數組

  • 時間複雜度:
    能夠理解爲不斷的從中間分解須要O(logn)次, 每次都須要對n個元素排序, 因此須要O(nlogn)
    • 最好: O(nlogn)
    • 最壞: O(nlogn)
    • 平均: O(nlogn)
  • 空間複雜度: O(n)
    雖然每次排序都須要申請內存, 可是使用完畢後都釋放了, 最多的一次使用內存是O(n)
# coding:utf-8

def merge(left, right):
    res = []
    while left and right:
        # 此處決定了排序是否穩定. 須要保證針對相等的元素排序後按照出現的前後順序進行排列
        if left[0] < right[0]:
            res.append(left.pop(0))
        else:
            res.append(right.pop(0))
    if left:
        res.extend(left)
    if right:
        res.extend(right)
    return res


def merge_sort(nums):
    length = len(nums)
    if length <= 1:
        return nums

    middle = int(length / 2)
    left = merge_sort(nums[:middle])
    right = merge_sort(nums[middle:])
    return merge(left, right)


if __name__ == "__main__":
    nums = [4, 3, 6, 9, 7, 0, 1, 9, 3]

    assert merge_sort(nums) == [0, 1, 3, 3, 4, 6, 7, 9, 9]

快速排序

使用了分治思想. 以數組中的一個數key爲基準, 把小於key的數放到左邊, 把大於key的數放到右邊, 而後使用一樣的方法做用於key兩邊的區間數據結構

  • 時間複雜度:
    • 最壞: O(n**2)
      好比原始數組就是有序的, 那麼當尾端元素爲key時, 分區致使一個區域爲空. 因此須要分區n次, 每次平均對n/2個元素排序, 因此是O(n**2)
    • 最好: O(nlogn)
      分區很是均衡
    • 平均: O(nlogn)
  • 空間複雜度: O(n)/O(1)
  • 是否穩定: 不穩定
方案一
# coding:utf-8

"""
空間複雜度: O(n)
"""

def quick_sort(nums):
    if len(nums) <= 1:
        return nums
    key = nums.pop()
    # 不考慮空間消耗
    less, over = [], []
    for i in nums:
        if i < key:
            less.append(i)
        else:
            over.append(i)
    return quick_sort(less) + [key] + quick_sort(over)


if __name__ == "__main__":
    nums_1 = [4, 3, 6, 9, 7, 0, 1, 9, 3]

    assert quick_sort(nums_1) == [0, 1, 3, 3, 4, 6, 7, 9, 9]
方案二
# coding:utf-8

"""
空間複雜度: O(1)
"""

def partition(nums, low, high):
    key_index = high
    key = nums[key_index]

    while low < high:
        while low < high and nums[low] <= key:
            low += 1
        while low < high and nums[high] >= key:
            high -= 1
        nums[low], nums[high] = nums[high], nums[low]
    nums[low], nums[key_index] = nums[key_index], nums[low]
    return low


def interval(nums, low, high):
    if low < high:
        new_index = partition(nums, low, high)
        interval(nums, 0, new_index - 1)
        interval(nums, new_index + 1, high)
    return nums


def quick_sort(nums):
    res = interval(nums, 0, len(nums) - 1)
    return res


if __name__ == "__main__":
    nums_2 = [4, 3, 6, 9, 7, 0, 1, 9, 3]

    assert quick_sort(nums_2) == [0, 1, 3, 3, 4, 6, 7, 9, 9]

區別

  • 歸併排序:
    排序順序是從下到上, 先解決子問題, 再合併分區. 缺點: 不是原地排序, 合併須要佔用額外空間
  • 快速排序:
    排序順序是從上到下, 先分區, 再解決子問題. 能夠經過合理的選擇key來避免時間複雜度爲最壞的O(n**2)

優化key的選擇

快速排序中最壞狀況是分區後一個分區是空, 另外一個分區全滿, 這種通常是key的選擇不當致使的, 好比一個有序數組選擇了第一個或最後一個元素爲key, 能夠採用如下方法優化app

  • 三位數取中
    取頭部, 尾部, 中間的元素, 將3個數的中間值做爲分界線
  • 隨機法
    從數組中隨機取一個數做爲分界線

資料

  • < <大話數據結構> >
  • < <漫畫算法> >
  • < <數據結構和算法-極客時間> >
相關文章
相關標籤/搜索