1. 程式人生 > >快速排序之python

快速排序之python

插入 價值 時間復雜度 選擇 reverse 版本 elf time 獨立

快速排序( Quick sort)

快速排序的基本思想:通過一趟排序將待排記錄分隔成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分的關鍵字小,則可分別對這兩部分記錄繼續進行遞歸排序,以達到整個序列有序。

1.算法描述:

  • 另一個分而治之
  • 將數組劃分為兩個部分,然後獨立地對部分進行排序
    • 首先選擇一個數據透視,並從列表中刪除(隱藏在最後)
    • 然後這些元素被分成兩部分.一個小於樞軸,另一個大於樞軸. 這種分區是通過交換價值來實現的
    • 然後在中間恢復樞軸,並且這兩個部分遞歸地快速排序

技術分享圖片

示例:

pivot:中間樞紐 ( 5)

portitiom:分區

Two points:兩個指針 ( i:左->右 j:左<-右)

技術分享圖片

2.算法屬性:

  • 不穩定
  • 在一般情況下,通過數學歸納法可證明,快速排序的時間復雜度為O(nlog(n))

3.代碼實現

def _quick_sorted(nums: list) -> list:
    if len(nums) <= 1:
        return nums

    pivot = nums[0]
    #pivot左右邊分別調用quick_sort
    left_nums = _quick_sorted([x for x in nums[1:] if x < pivot])  #找左半邊比樞紐小的
    right_nums = _quick_sorted([x for
x in nums[1:] if x >= pivot]) #找右半邊比樞紐大的 return left_nums + [pivot] + right_nums def quick_sorted(nums: list, reverse=False)-> list: """Quick Sort""" start = time.time() nums = _quick_sorted(nums) if reverse: nums = nums[::-1] t = time.time() - start return
nums, len(nums), t lis = [1, 3, 5, 7, 9, 2, 4, 6, 8, 0] lis = quick_sorted(lis, reverse=False) print(lis) #輸出結果 ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 10, 0.0)

另一個版本

def quick_sort(nums):
    # 封裝一層的目的是方便用戶調用
    def qsort(lst, begin, end):
        if begin >= end:
            return
        i = begin
        key = lst[begin]
        for j in range(begin+1, end+1):
            if lst[j] < key:
                i += 1
                lst[i], lst[j] = lst[j], lst[i]
        lst[begin], lst[i] = lst[i], lst[begin]
        qsort(lst, begin, i-1)
        qsort(lst,i+1,end)
    qsort(nums, 0, len(nums)-1)

4.基本的快速排序還有可以優化的地方:

1)優化選取的pivot_key

前面我們每次選取pivot_key的都是子序列的第一個元素,也就是lis[low],這就比較看運氣。運氣好時,該值處於整個序列的靠近中間值,則構造的樹比較平衡,運氣比較差,處於最大或最小位置附近則構造的樹接近斜樹。
為了保證pivot_key選取的盡可能適中,采取選取序列左中右三個特殊位置的值中,處於中間值的那個數為pivot_key,通常會比直接用lis[low]要好一點。在代碼中,在原來的pivot_key = lis[low]這一行前面增加下面的代碼:

m = low + int((high-low)/2)
if lis[low] > lis[high]:
    self.swap(low, high)
if lis[m] > lis[high]:
    self.swap(high, m)
if lis[m] > lis[low]:
    self.swap(m, low)

如果覺得這樣還不夠好,還可以將整個序列先劃分為3部分,每一部分求出個pivot_key,再對3個pivot_key再做一次上面的比較得出最終的pivot_key。這時的pivot_key應該很大概率是一個比較靠譜的值。

2)減少不必要的交換

原來的代碼中pivot_key這個記錄總是再不斷的交換中,其實這是沒必要的,完全可以將它暫存在某個臨時變量中,如下所示:

def partition(self, low, high):
        
        lis = self.r

        m = low + int((high-low)/2)
        if lis[low] > lis[high]:
            self.swap(low, high)
        if lis[m] > lis[high]:
            self.swap(high, m)
        if lis[m] > lis[low]:
            self.swap(m, low)

        pivot_key = lis[low]
        # temp暫存pivot_key的值
        temp = pivot_key
        while low < high:
            while low < high and lis[high] >= pivot_key:
                high -= 1
            # 直接替換,而不交換了
            lis[low] = lis[high]
            while low < high and lis[low] <= pivot_key:
                low += 1
            lis[high] = lis[low]
            lis[low] = temp
        return low

3)優化小數組時的排序

快速排序算法的遞歸操作在進行大量數據排序時,其開銷能被接受,速度較快。但進行小數組排序時則不如直接插入排序來得快,也就是殺雞用牛刀,未必就比菜刀來得快。
因此,一種很樸素的做法就是根據數據的多少,做個使用哪種算法的選擇而已,如下改寫qsort方法:

def qsort(self, low, high):
    """根據序列長短,選擇使用快速排序還是簡單插入排序"""
    # 7是一個經驗值,可根據實際情況自行決定該數值。
    MAX_LENGTH = 7
    if high-low < MAX_LENGTH:
        if low < high:
            pivot = self.partition(low, high)
            self.qsort(low, pivot - 1)
            self.qsort(pivot + 1, high)
    else:
        # insert_sort方法是我們前面寫過的簡單插入排序算法
        self.insert_sort()

4)優化遞歸操作

可以采用尾遞歸的方式對整個算法的遞歸操作進行優化,改寫qsort方法如下:

def qsort(self, low, high):
    """根據序列長短,選擇使用快速排序還是簡單插入排序"""
    # 7是一個經驗值,可根據實際情況自行決定該數值。
    MAX_LENGTH = 7
    if high-low < MAX_LENGTH:
        # 改用while循環
        while low < high:
            pivot = self.partition(low, high)
            self.qsort(low, pivot - 1)
            # 采用了尾遞歸的方式
            low = pivot + 1
    else:
        # insert_sort方法是我們前面寫過的簡單插入排序算法
        self.insert_sort()

快速排序之python