本篇主要實現九(八)大排序演算法,分別是氣泡排序,插入排序,選擇排序,希爾排序,歸併排序,快速排序,堆排序,計數排序。希望大家回顧知識的時候也能從我的這篇文章得到幫助。
為了防止誤導讀者,本文所有概念性內容均擷取自對應Wiki
氣泡排序
原理
氣泡排序(Bubble Sort)是一種簡單的排序演算法。它重複地走訪過要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把他們交換過來。走訪數列的工作是重複地進行直到沒有再需要交換,也就是說該數列已經排序完成。這個演算法的名字由來是因為越小的元素會經由交換慢慢“浮”到數列的頂端。
步驟
氣泡排序演算法的運作如下:
- 比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
- 對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對。這步做完後,最後的元素會是最大的數。
- 針對所有的元素重複以上的步驟,除了最後一個。
- 持續每次對越來越少的元素重複上面的步驟,直到沒有任何一對數字需要比較。
程式碼
1 2 3 4 5 6 7 8 9 10 |
def bubble_sort(list): length = len(list) # 第一級遍歷 for index in range(length): # 第二級遍歷 for j in range(1, length - index): if list[j - 1] > list[j]: # 交換兩者資料,這裡沒用temp是因為python 特性元組。 list[j - 1], list[j] = list[j], list[j - 1] return list |
這種排序其實還可以稍微優化一下,新增一個標記,在排序已完成時,停止排序。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def bubble_sort_flag(list): length = len(list) for index in range(length): # 標誌位 flag = True for j in range(1, length - index): if list[j - 1] > list[j]: list[j - 1], list[j] = list[j], list[j - 1] flag = False if flag: # 沒有發生交換,直接返回list return list return list |
選擇排序
原理
選擇排序(Selection sort)是一種簡單直觀的排序演算法。它的工作原理大致是將後面的元素最小元素一個個取出然後按順序放置。
步驟
- 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,
- 再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾。
- 重複第二步,直到所有元素均排序完畢。
程式碼
1 2 3 4 5 6 7 8 9 |
def selection_sort(list): n=len(list) for i in range (0,n): min = i for j in range(i+1,n): if list[j]<list[min]: min=j list[min],list[i]=list[i],list[min] return list |
插入排序
原理
插入排序(Insertion Sort)是一種簡單直觀的排序演算法。它的工作原理是通過構建有序序列,對於未排序資料,在已排序序列中從後向前掃描,找到相應位置並插入。
步驟
- 從第一個元素開始,該元素可以認為已經被排序
- 取出下一個元素,在已經排序的元素序列中從後向前掃描
- 如果該元素(已排序)大於新元素,將該元素移到下一位置
- 重複步驟3,直到找到已排序的元素小於或者等於新元素的位置
- 將新元素插入到該位置後
- 重複步驟2~5
程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def insert_sort(list): n = len(list) for i in range(1, n): # 後一個元素和前一個元素比較 # 如果比前一個小 if list[i] temp: list[j + 1] = list[j] index = j else: break # 插入元素 list[index] = temp return list |
希爾排序
原理
希爾排序,也稱遞減增量排序演算法,是插入排序的一種更高效的改進版本。希爾排序是非穩定排序演算法。
希爾排序是基於插入排序的以下兩點性質而提出改進方法的:
插入排序在對幾乎已經排好序的資料操作時,效率高,即可以達到線性排序的效率
但插入排序一般來說是低效的,因為插入排序每次只能將資料移動一位。
步驟
每次以一定步長(就是跳過等距的數)進行排序,直至步長為1.
程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
def shell_sort(list): n = len(list) # 初始步長 gap = round(n / 2) while gap > 0: for i in range(gap, n): # 每個步長進行插入排序 temp = list[i] j = i # 插入排序 while j >= gap and list[j - gap] > temp: list[j] = list[j - gap] j -= gap list[j] = temp # 得到新的步長 gap = round(gap / 2) return list |
步長使用的是Donald Shell的建議,另外步長還可以使用Sedgewick提出的(1, 5, 19, 41, 109,…)。
也可以使用斐波那契數列除去0和1將剩餘的數以黃金分割槽比的兩倍的冪進行運算得到的數列。
歸併排序
原理
歸併操作(歸併演算法),指的是將兩個已經排序的序列合併成一個序列的操作。歸併排序演算法依賴歸併操作。
步驟
迭代法
- 申請空間,使其大小為兩個已經排序序列之和,該空間用來存放合併後的序列
- 設定兩個指標,最初位置分別為兩個已經排序序列的起始位置
- 比較兩個指標所指向的元素,選擇相對小的元素放入到合併空間,並移動指標到下一位置
- 重複步驟3直到某一指標到達序列尾
- 將另一序列剩下的所有元素直接複製到合併序列尾
遞迴法
假設序列共有n個元素:
- 將序列每相鄰兩個數字進行歸併操作,形成 {displaystyle floor(n/2)} floor(n/2)個序列,排序後每個序列包含兩個元素
- 將上述序列再次歸併,形成 {displaystyle floor(n/4)} floor(n/4)個序列,每個序列包含四個元素
- 重複步驟2,直到所有元素排序完畢
程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# 遞迴法 def merge_sort(list): # 認為長度不大於1的數列是有序的 if len(list) <= 1: return list # 二分列表 middle = len(list) // 2 left = merge_sort(list[:middle]) right = merge_sort(list[middle:]) # 最後一次合併 return merge(left, right) # 合併 def merge(left, right): l,r=0,0 result=[] while l<len(left) and r<len(right): if left[l] <right[r]: result.append(left[l]) l+=1 else: result.append(right[r]) r +=1 reslut +=left[l:] result+=right[r:] return result |
鄙人不才,不知歸併排序的迭代法如何用Python實現,望指教。
快速排序
原理
快速排序使用分治法(Divide and conquer)策略來把一個序列(list)分為兩個子序列(sub-lists)。
步驟
- 從數列中挑出一個元素,稱為”基準”(pivot),
- 重新排序數列,所有元素比基準值小的擺放在基準前面,所有元素比基準值大的擺在基準的後面(相同的數可以到任一邊)。在這個分割槽結束之後,該基準就處於數列的中間位置。這個稱為分割槽(partition)操作。
- 遞迴地(recursive)把小於基準值元素的子數列和大於基準值元素的子數列排序。
程式碼
普通版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
def quick_sort(list): less = [] pivotList = [] more = [] # 遞迴出口 if len(list) <= 1: return list else: # 將第一個值做為基準 pivot = list[0] for i in list: # 將比急轉小的值放到less數列 if i < pivot: less.append(i) # 將比基準打的值放到more數列 elif i > pivot: more.append(i) # 將和基準相同的值儲存在基準數列 else: pivotList.append(i) # 對less數列和more數列繼續進行排序 less = quick_sort(less) more = quick_sort(more) return less + pivotList + more |
咳咳,下面這段程式碼出自《Python cookbook 第二版》傳說中的三行實現python快速排序。
1 2 3 4 5 6 7 8 |
def qsort(arr): if len(arr) <= 1: return arr else: pivot = arr[0] return qsort([x for x in arr[1:] if x < pivot]) + \ [pivot] + \ qsort([x for x in arr[1:] if x >= pivot]) |
當然還有一行語法糖版本:
1 |
qs = lambda xs : ( (len(xs) <= 1 and [xs]) or [ qs( [x for x in xs[1:] if x < xs[0]] ) + [xs[0]] + qs( [x for x in xs[1:] if x >= xs[0]] ) ] )[0] |
是不是感受到了Python的魅力?
堆排序
原理
堆排序(Heapsort)是指利用堆這種資料結構所設計的一種排序演算法。堆積是一個近似完全二叉樹的結構,並同時滿足堆積的性質:即子結點的鍵值或索引總是小於(或者大於)它的父節點。
步驟
- 建立最大堆:將堆所有資料重新排序,使其成為最大堆
- 最大堆調整:作用是保持最大堆的性質,是建立最大堆的核心子程式
- 堆排序:移除位在第一個資料的根節點,並做最大堆調整的遞迴運算
程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
def heap_sort(list): # 建立最大堆 for start in range((len(list) - 2) // 2, -1, -1): sift_down(list, start, len(list) - 1) # 堆排序 for end in range(len(list) - 1, 0, -1): list[0], list[end] = list[end], list[0] sift_down(list, 0, end - 1) return list # 最大堆調整 def sift_down(lst, start, end): root = start while True: child = 2 * root + 1 if child > end: break if child + 1 <= end and lst[child] < lst[child + 1]: child += 1 if lst[root] < lst[child]: lst[root], lst[child] = lst[child], lst[root] root = child else: break |
計數排序
原理
當輸入的元素是n個0到k之間的整數時,它的執行時間是Θ(n + k)。計數排序不是比較排序,排序的速度快於任何比較排序演算法。
由於用來計數的陣列C的長度取決於待排序陣列中資料的範圍(等於待排序陣列的最大值與最小值的差加上1),這使得計數排序對於資料範圍很大的陣列,需要大量時間和記憶體。例如:計數排序是用來排序0到100之間的數字的最好的演算法,但是它不適合按字母順序排序人名。但是,計數排序可以用在基數排序演算法中,能夠更有效的排序資料範圍很大的陣列。
步驟
- 找出待排序的陣列中最大和最小的元素
- 統計陣列中每個值為i的元素出現的次數,存入陣列 C 的第 i 項
- 對所有的計數累加(從C中的第一個元素開始,每一項和前一項相加)
- 反向填充目標陣列:將每個元素i放在新陣列的第C(i)項,每放一個元素就將C(i)減去1
程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
def count_sort(list): min = 2147483647 max = 0 # 取得最大值和最小值 for x in list: if x max: max = x # 建立陣列C count = [0] * (max - min +1) for index in list: count[index - min] += 1 index = 0 # 填值 for a in range(max - min+1): for c in range(count[a]): list[index] = a + min index += 1 return list |
第九種排序
None?
當然不會
自然就是系統自帶的
1 |
list.sort() |
以上所有原始碼均在Github共享希望與大家共同進步!