1. 程式人生 > >02.python實現排序算法

02.python實現排序算法

返回 height 越來越大 quick 區域 詳細 rand 左右子樹 pre

一、列表排序

  將無序列表變為有序列表

  應用場景: 榜單,表格, 給二分查找用,給其他算法用

二、python實現三種簡單排序算法

時間復雜度O(n^2), 空間O(1)

1、冒泡排序

思路:

  列表每兩個相鄰的數,如果前面的比後面的大,那麽交換這兩個數

代碼實現:

# 冒泡排序
@cal_time  # 測試執行時間
def bubble_sort(li):
    for i in range(len(li)-1): # i表示第i趟
        # 第i趟無序區位置【0,n-i-1】
        for j in range(len(li)-i-1):
            
if li[j] > li[j+1]: li[j], li[j+1] = li[j+1], li[j] # 最好情況 O(n^2) # 平均情況 O(n^2) # 最壞情況 O(n^2) # 優化改進>>> # 思路:如果冒泡排序中執行一趟而沒有交換,則列表已經是有序狀態,可以直接結束算法。 @cal_time def bubble_sort2(li): for i in range(len(li)): # 表示第i趟 exchange = 0 # 第i趟無序區的位置【0,n-i-1】 n是列表長度
for j in range(len(li)-i-1): if li[j] > li[j+1]: li[j], li[j+1] = li[j+1], li[j] exchange = 1 if not exchange: # 如果遍歷一遍沒有發生交換,則已經有序,直接返回 return # 最好情況 O(n) # 平均情況 O(n^2) # 最壞情況 O(n^2)

2、選擇排序

思路:

  一趟遍歷記錄最小的數,放到第一個位置;

  再一趟遍歷記錄剩余列表中最小的數,繼續放置;

  ...

問題:

  怎麽選出最小的數

# 找最小值
def find_min(li):
    min_num = li[0]
    for i in range(1,len(li)):
        if li[i] < min_num:
            min_num = li[i]
    return min_num

# 找最小值的下標
def find_min_pos(li):
    min_pos = 0
    for j in range(1,len(li)):
        if li[j] < li[min_pos]:
            min_pos = j
    return min_pos


li = [2,5,8,9,11,15,5,1]
print(find_min(li))  # 1
print(find_min_pos(li))  # 7

選擇排序:

def select_sort(li):
    for i in range(len(li)-1):  # 第i趟遍歷,從0開始
        # 第i趟 無序區【i, len(li)-1】
        # 找無序區最小數位置,和無序區第一個數交換
        min_pos = i
        for j in range(i+1, len(li)):  # 從無序區第二個開始找
            if li[j] < li[min_pos]:
                min_pos = j
        li[min_pos], li[i] = li[i], li[min_pos]

li = list(range(100))
random.shuffle(li)   # 打亂列表次序
print(li)
select_sort(li)
print(li)

比冒泡排序快。

3、插入排序

思路:

  列表被分為有序區和無序區兩個部分。最初有序區只有一個元素。每次從無序區選擇一個元素,插入到有序區的位置,直到無序區變空。

代碼:

def insert_sort(li):
    for i in range(1, len(li)):  # i表示第i趟,還表示無序區第一個數的位置
        tmp = li[i]
        j = i - 1  # j 從後往前遍歷有序區的指針
        while j >= 0 and li[j] > tmp:  # j遍歷結束或無序區第一位大於j指向的數時跳出循環
            li[j + 1] = li[j]  # 有序區的第j位往後挪一位
            j -= 1  # j 向前指一位
        li[j + 1] = tmp  # 將tmp插入到有序區j後面一位

三、python實現三種較復雜排序算法

1、快速排序

時間復雜度: O(nlogn)

思路:

  取一個元素p(第一個元素),使元素p歸位;

  列表被p分成兩部分,左邊都比p小,右邊都比p大;

  左右兩邊遞歸完成排序。

方法一(經典方法):

技術分享圖片

歸位思路:

  將要歸位的數p存起來,此時左遊標left指向空

  將遊標指向的數與p比較,大於放右邊,小於放左邊(詳細操作:

  先將右遊標right指向的數與p比較,大於p,位置不變,右遊標往左移一位,繼續比較;小於p,放到left指向的空位置,此時right指向空,然後移left

  再將left遊標往右移一位指向的數與p比較,小於p,位置不變,left往右移一位,繼續比較;大於p,放到right指向的空位置,此時left指向空,然後移right)

  當左遊標等於右遊標時,遊標指向的位置就是p要歸的位置

  註意處理最壞情況(列表倒序)導致遞歸達到最大深度,從列表中隨機取一個與第一個交換位置

代碼:

def quick_sort(li, left, right):
    if left < right:  # 遞歸區域至少有兩個元素
        mid = partition(li, left, right)  # 歸位
        quick_sort(li, left, mid-1)  # 左邊
        quick_sort(li, mid+1, right)  # 右邊


def partition(li, left, right): 
    i = random.randint(left, right)  # 防止最壞情況(列表有序或倒序)導致遞歸達到最大深度,從列表中隨機取一個與第一個交換位置 
    li[i], li[left] = li[left], li[i]
    tmp = li[left]  # 將要歸位的數存起來
    while left < right:
        while left < right and li[right] >= tmp:
            right -= 1   # 右邊的數大於等於tmp就不動,right遊標往左走
        li[left] = li[right]     # 右邊的數小於tmp就往左放
        while left < right and li[left] <= tmp:
            left += 1     # 左邊的數小於等於tmp就不動,left遊標往右走
        li[right] = li[left]       # 左邊的數大於tmp就往右放
    li[left] = tmp  # left=right 將tmp歸位
    return left

方法二(算法導論中的歸位方法):

歸位思路:

  取最後一個元素r歸位,

  分兩個區域,將小於r的數都放到區域一, 剩下的就是大於r的區域二

  然後將r與區域二的第一個數交換,就歸位成功了

  與方法1一樣也有python遞歸最大深度的問題

技術分享圖片

代碼實現:

def partition2(li, left, right):
    # 區域1:[left, i] 區域2:[i+1, j-1]
    i = left - 1  # 初始區域1和區域2都空,i指向區域1最後一個數
    for j in range(left, right):
        if li[j] < li[right]:  # 放到區域1,i往後移一位
           i += 1
           li[i], li[j] = li[j], li[i]  # 與區域2的第一個數(i+1)交換歸為區域1,
    li[right], li[i+1] = li[i+1], li[right]  # 歸位
    return i+1   # 返回mid

方法三(占用空間的方法):

思路:

  每次都取中間的數為歸位,盡可能的避免最壞情況導致python遞歸達最大深度的問題

  開三個列表,一個放大於歸位數的,一個放小於歸位數的,一個放等於歸位數的

  然後將三個列表拼起來

  遞歸結束條件,列表長度小於等於1, 

代碼:

def quick_sort3(li):
    if len(li) <= 1:
        return li
    m = li[len(li)//2]  # 防止列表本來就是有序或倒序的,導致遞歸達到最大深度,不取li[0]
    left = [item for item in li if item < m]
    right = [item for item in li if item > m]
    x = [i for i in li if i == m]
    return quick_sort3(left) + x + quick_sort3(right)

一行實現快速排序:

quick_sort4 = lambda li: li if len(li) <= 1 else quick_sort4([item for item in li[1:] if item <= li[0]]) + [li[0]] + quick_sort4([item for item in li[1:] if item > li[0]])

2、堆排序

堆的概念:

  堆是完全二叉樹,完全二叉樹可以用列表來存儲,通過規律可以從父親找到孩子或從孩子找到父親,堆中某個節點的值總是不大於或不小於其父節點的值

  大根堆:一棵完全二叉樹,滿足任一節點都比其孩子節點大

  小根堆:一棵完全二叉樹,滿足任一節點都比其孩子節點小

技術分享圖片

堆排序利用了堆向下調整的特征:節點的左右子樹都是堆,但自身不是堆。

向下調整,挨個出數;

通過父節點找子節點:父節點下標為i

  則:孩子節點為,2i+1 和 2i+2

通過孩子節點找父節點:孩子節點為j

  子節點為左節點,父節點為:(j-1) / 2

  子節點為右節點,父節點為:(j-2) / 2

  不知道為左子節點還是右子節點: (j-1) // 2

代碼:

def sift(li, low, high):
    ‘‘‘
    向下調整
    :param li:
    :param low: 堆頂下標
    :param high: 堆中最後一個元素下標
    :return:
    ‘‘‘
    tmp = li[low]
    i = low
    j = 2 * i + 1  # i, j 兩個遊標,初始i指向堆頂,j指向堆頂的左孩子
    while j <= high:   # 第二個結束循環的條件,沒有孩子和tmp競爭i這個位置
        if j+1 <= high and li[j+1] > li[j]:  # 如果右孩子存在並且比左孩子大 j指向右孩子
            j += 1
        if li[j] > tmp:
            li[i] = li[j]  # 大的數往上調整
            i = j  # i指向下一個要調整的堆的堆頂
            j = 2 * i + 1  # j指向調整堆的堆頂的左孩子
        else:
            break  # 第一種循環退出情況,tmp比目前兩個孩子都大
    li[i] = tmp  # i就是tmp要調整到的位置


def heap_sort(li):
    ‘‘‘
    堆排序
    :param li:
    :return:
    ‘‘‘
    # 1. 從列表構造堆,low的值和high的值
    n = len(li)
    # 子節點找父節點: low = (i-1)//2 --> (n-2)//2 --> n//2-1
    for low in range(n//2-1, -1, -1):
        sift(li, low, n-1)
    # 2. 挨個出數 利用原來的空間存儲下來的值,但是這些值不屬於堆
    for high in range(n-1, -1, -1):  # 或range(n-1, 0, -1)
        li[high], li[0] = li[0], li[high]  # 1.把最大的調下來 2.high對應的數調上去
        sift(li, 0, high - 1)  # 3.調整,high 往前移一個,low為0

3、歸並排序

思路:

  假設現在的列表分兩段有序,如何將其合成一個有序列表, 這個操作稱為一次歸並。

有了歸並之後怎麽用?

  分解 :將列表越分越小,直至分成一個元素

  終止條件:一個元素是有序的。

  合並:將兩個有序列表歸並,列表越來越大。

一次歸並:

def merge(li, low, mid, high):
    ‘‘‘
    歸並
    :param li:
    :param low:
    :param mid:
    :param high:
    :return:
    ‘‘‘
    i = low
    j = mid + 1
    li_tmp = []
    while i <= mid and j <= high:  # 兩邊都有數
        if li[i] <= li[j]:
            li_tmp.append(li[i])
            i += 1
        else:
            li_tmp.append(li[j])
            j += 1
    # i<=mid 和 j<=high 兩個條件 只能有一個滿足
    while i <= mid:
        li_tmp.append(li[i])
        i += 1
    while j <= high:
        li_tmp.append(li[j])
        j += 1
    # li_tmp 0~high-low 復制回li low~high
    for i in range(len(li_tmp)):
        li[low + i] = li_tmp[i]

分解合並:

def merge_sort(li, low, high):
    if low < high:  # 至少兩個元素
        # print(li[low:high+1], ‘->‘, end=‘ ‘)
        mid = (low + high) // 2  # 分解
        # print(li[low:mid+1], li[mid+1: high+1])
        merge_sort(li, low, mid)  # 遞歸排序左邊
        merge_sort(li, mid + 1, high)  # 遞歸排序右邊
        # print(li[low:mid+1], li[mid+1: high+1], ‘->‘, end=‘ ‘)
        merge(li, low, mid, high)  # 一次歸並 合並
        # print(li[low:high+1])

小結:

技術分享圖片

02.python實現排序算法