python實現幾種排序算法

洛基準備把一些python 3實現的算法題總結到這兒(python大法真香)。其實洛基覺得,不管是C++,java,python,各有各的妙處,只要玩的好了,都可以寫出優雅、高效的代碼,實現各種各樣酷炫的功能。大家也沒必要爭吵到底誰纔是第一語言。Anyway,語言只是工具,最重要的是我們能用工具完成什麼樣的任務,創造什麼樣的價值。

於是乎立下此博,把一些常見的算法存放於此,閒時看一看,以期鞏固提高。書讀百遍其意自現,算法看百遍內功up up up!

——各種排序算法。

1.冒泡。和名字一樣的有趣且簡潔的算法。複雜度 Time(n2) 、Space(1) ,是穩定的。

# py3
def sortBubble(nums):
    n = len(nums)
    # 1 to n-1
    for i in range(1, n):
        # range(0, n-1) to range(0, 1)
        for j in range(0, n - i): 
            if nums[j] > nums[j + 1]:
                nums[j], nums[j + 1] = nums[j + 1], nums[j]
    return nums

2.選擇排序,從第一個索引開始,每趟在後續的序列中找到最小的,與當前位置交換。複雜度Time(n2)、Space(1), 不穩定,比如 3 3 2 1,選擇排序後第一個3到末尾去了,第二個3跑到倒數第二個位置了。

def sortSelect(nums):
    length = len(nums)
    for cur in range(length - 1):
        min_idx = cur
        for i in range(cur + 1, length):
            if nums[i] < nums[min_idx]:
                min_idx = i 
        nums[cur], nums[min_idx] = nums[min_idx], nums[cur]
    return nums

 3.希爾排序,又稱爲“縮小增量排序”。 第一趟選定step=length/2:從step開始,每次以一定步長和前面的元素進行比較,一直循環到數組的第一個元素;接着從step+1開始繼續執行...此爲一趟。第二趟則把step繼續縮小一半,重複之前的步驟。 複雜度:Time(nlogn)、 Space(1)。不穩定。

def sortShell(nums):
    step = len(nums) // 2
    while step > 0:
        for i in range(step, len(nums)):
            # 因爲前面的已經兩兩比較過了,所以當後面沒有因新的更小元素而交換時,不需要再遍歷i-step之前的元素
            while i >= step and nums[i] < nums[i - step]:
                nums[i], nums[i - step] = nums[i - step], nums[i]
                i -= step
        step = step // 2
    return nums

 4.歸併排序,分治思想。首先將序列二分成兩個子序列,然後對子序列再進行二分...直到所有子序列都只包含單個元素。然後將相鄰兩個序列進行歸併操作,並保證歸併後的序列內部有序;接着再對相鄰序列進行歸併,一直到只剩下一個序列爲止。Time(nlogn) 、Space(1) , 穩定的。

def sortMerge(nums):
    if len(nums) <= 1:
        return nums
    # 要拆分,必須要找到中點mid
    mid = len(nums) // 2
    # 遞歸拆分
    left = sortMerge(nums[:mid])
    right = sortMerge(nums[mid:])

    # 歸併子序列得到有序序列
    return merge(left, right)

def merge(left, right):
    result = []
    while left and right:
        if left[0] <= right[0]: # 爲了排序的穩定性
            result.append(left.pop(0))
        else:
            result.append(right.pop(0))
    return result + left + right

5.快速排序(請叫它面試小常客)。T(nlogn) ,空間複雜度有兩種情況,如果原地交換元素,那麼空間複雜度是遞歸消耗的Space(logn),如果採用額外輔助數組(會很好理解),那麼空間也可以達到Space(n)(誇我)。不穩定。

# 藉助輔助數組的話,空間複雜度O(n)
def sortQuick(nums):
    if len(nums) <= 1:
        return nums
    small, equal, big = [], [], []
    p = nums[0]
    for num in nums:
        if num < p:
            small.append(num)
        elif num > p:
            big.append(num)
        else:
            equal.append(num)
    # 對剩餘的small和big數組繼續快排
    small = sortQuick(small)
    big = sortQuick(big)
    return small + equal + big


# 快速排序方法二,就地排序,空間複雜度:S(logn),是遞歸消耗的複雜度
# 時間仍然是T(nlogn)
# 思路:定義一個區間內快排的函數,對一個區間進行一趟快排,取pivat爲區間末尾元素,
#       先取index爲頭元素保存最終的pivat應去的位置;掃描區間內前面n-1個元素,交換...;函數返回中間idx。
#       定義一個遞歸函數,先對整個數組進行一趟快排,得到中間idx,再遞歸對劃分出來的兩個子區間進行快排。
def sortQuick(nums, start, end):
    '''一趟快排'''
    # index指示小的那個值應該放到當前哪個位置
    index = start
    pivot = nums[end]
    # 在指定區間內進行一趟快排
    for i in range(start, end): # 沒有檢查最後一個元素,因爲最後一個元素是pivat,最終index會指引它應該去的位置
        if nums[i] < pivot:
            nums[index], nums[i] = nums[i], nums[index]
            index += 1
    # 最後,把mid值放到它應放的位置上,index之前的數都小於mid,index之後的數都大於mid
    nums[index], nums[end] = nums[end], nums[index]
    return index

def mysort(nums, start, end):
    if start < end: # 如果start和end相等或者start大於end,那麼不需要在這個區間快排了
        m_index = sortQuick(nums, start, end)
        mysort(nums, start, m_index - 1)
        mysort(nums, m_index + 1, end)

 6.計數排序。當輸入的元素是n個0到k之間的整數時,運行時間是Time(n + k), 空間Space(k)。穩定。 當數據範圍k比數組長度小很多時,此方法比較好。

def sortCount(nums):
    small, big = nums[0], nums[0]
    for x in nums:
        if x < small:
            small = x
        elif x > big:
            big = x
    # 計數數組
    count = [0] * (big - small + 1)
    for x in nums:
        count[x - small] += 1
    
    # 填值
    # index是當前填入nums的位置,每填一個值index就加一,如此遍歷到nums的末尾
    index = 0
    for i, freq in enumerate(count):
        # 原數組中出現的某個值=i+small, 出現次數=freq
        x = i + small
        while freq > 0:
            nums[index] = x
            index += 1
            freq -= 1
    return nums

 7.堆排序。後面會補充。之所以暫時每做,是因爲python自帶的heapq包已經繼承了小根堆的功能,堆排序僅僅幾行代碼不到就可以搞定,感覺很trick(trick是對於面試官來說的。自己用的話,heapq有多麼舒適,誰用誰知道=。=)。之後等洛基手擼一個堆出來再把這裏補上咯。

8.topK問題:一億個整數,求topk(假設k=100),分析時間複雜度和空間複雜度。

第一種思路,直接做快速排序,然後取最大k個,時間複雜度Time(nlogn);個人認爲,由於大數據下的快速排序不可能使用遞歸解決,必須用額外的輔助數組,故空間複雜度應該是Space(n)。

第二種思路,建立一個小根堆,並把這個堆的元素個數限制爲k(此例中k=100),然後先從全部數據中拿出100個數裝到堆裏面,調整堆之後得到一個小根堆,此時堆頂元素就是整個堆中最小的了(小根堆的性質)。接着,每次從全部數據中讀取一部分至內存,遍歷這一小部分元素,每次都與堆頂元素進行比較,如果新的元素大於堆頂元素,那麼新的元素替換掉堆頂元素,調整一下堆。如此循環,即可以在O(n)的時間內,以O(k)的額外空間(建堆的空間)完成任務了,很酷不是嗎?What's more,用python的heapq包真的能很優雅的處理這個問題。朋友你覺得用python不香嗎~

import heapq

class topkHeap:
    def __init__(self, k):
        self.heap = []
        self.k = k

    def push(self, num):
        if len(self.heap) < self.k:
            heapq.heappush(self.heap, num)
        else:
            heapq.heappushpop(self.heap, num)

    def topk(self):
        return sorted(self.heap, reverse=True)

# 最小的k個值,與topk異曲同工,只是會先把原數據的逐個元素取反。
class minkHeap:
    def __init__(self, k):
        self.heap = []
        self.k = k

    def push(self, num):
        if len(self.heap) < self.k:
            heapq.heappush(self.heap, -num) # 取反
        else:
            heapq.heappushpop(self.heap, -num)

    def mink(self):
        return sorted([-num for num in self.heap])

 

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