常用排序算法
排序算法可以分為內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部的排序記錄,在排序過程中需要訪問外存。常見的內部排序算法有:插入排序、希爾排序、選擇排序、冒泡排序、歸並排序、快速排序、堆排序、基數排序等。用一張圖概括:
- 穩定性:排序后 2 個相等鍵值的順序和排序之前它們的順序相同
- 排序方式:In-place:占用常數內存,不占用額外內存;Out-place:占用額外內存
- n:數據規模
1. 冒泡排序
- 效率:O(n2)
- 原理:
- 1.每一趟比較相鄰的元素,如果逆序(前大后小),就交換他們兩個;每一趟都把大的元素往后調。
- n個元素最多n-1趟排序
def bubble_sort1(origin_items,comp=lambda x, y: x > y):
items = origin_items[:]
for i in range(len(items) - 1):
for j in range(i, len(items) - 1 - i):
if comp(items[j], items[j + 1]):
items[j], items[j + 1] = items[j + 1], items[j]
# 改進1
def bubble_sort1(origin_items,comp=lambda x, y: x > y):
items = origin_items[:]
for i in range(len(items) - 1):
swapped = False # 交換標志
for j in range(i, len(items) - 1 - i):
if comp(items[j], items[j + 1]):
items[j], items[j + 1] = items[j + 1], items[j]
if not swapped: # 如果一趟排序中沒有交換,則說明整體有序,結束排序
break
# 改進版2
def bubble_sort2(origin_items, comp=lambda x, y: x > y):
"""高質量冒泡排序(攪拌排序)"""
items = origin_items[:]
for i in range(len(items) - 1):
swapped = False
for j in range(i, len(items) - 1 - i): # 從左向右排序
if comp(items[j], items[j + 1]):
items[j], items[j + 1] = items[j + 1], items[j]
swapped = True
if swapped:
swapped = False
for j in range(len(items) - 2 - i, i, -1): # 從右向左排序
if comp(items[j - 1], items[j]):
items[j], items[j - 1] = items[j - 1], items[j]
swapped = True
if not swapped:
break
return items
2. 選擇排序
- 效率:O(n2)
- 原理:從頭至尾掃描序列,找出最小的一個元素,和第一個元素交換,接着從剩下的元素中繼續這種選擇和交換方式,最終得到一個有序序列。
def select_sort(origin_items, comp=lambda x, y: x < y):
"""簡單選擇排序"""
items = origin_items[:]
for i in range(len(items) - 1):
min_index = i
for j in range(i + 1, len(items)):
if comp(items[j], items[min_index]):
min_index = j
items[i], items[min_index] = items[min_index], items[i]
return items
3. 插入排序
- 效率:
- 原理
- 將第一待排序序列第一個元素看做一個有序序列,把第二個元素到最后一個元素當成是未排序序列。
- 從頭到尾依次掃描未排序序列,將掃描到的每個元素插入有序序列的適當位置。(如果待插入的元素與有序序列中的某個元素相等,則將待插入元素插入到相等元素的后面。)
def insertionSort(arr):
for i in range(len(arr)):
preIndex = i-1
current = arr[i]
while preIndex >= 0 and arr[preIndex] > current:
arr[preIndex+1] = arr[preIndex]
preIndex-=1
arr[preIndex+1] = current
return arr
4. 歸並排序
歸並排序(Merge sort)是建立在歸並操作上的一種有效的排序算法。該算法是采用分治法(Divide and Conquer)的一個非常典型的應用。
作為一種典型的分而治之思想的算法應用,歸並排序的實現由兩種方法:
- 自上而下的遞歸(所有遞歸的方法都可以用迭代重寫,所以就有了第 2 種方法);
- 自下而上的迭代;
分治法:
- 分割:遞歸地把當前序列平均分割成兩半。
- 集成:在保持元素順序的同時將上一步得到的子序列集成到一起(歸並)。

def merge_sort(items, comp=lambda x, y: x <= y):
"""歸並排序(分治法)"""
if len(items) < 2:
return items[:]
mid = len(items) // 2
left = merge_sort(items[:mid], comp)
right = merge_sort(items[mid:], comp)
return merge(left, right, comp)
def merge(items1, items2, comp):
"""合並(將兩個有序的列表合並成一個有序的列表)"""
items = []
index1, index2 = 0, 0
while index1 < len(items1) and index2 < len(items2):
if comp(items1[index1], items2[index2]):
items.append(items1[index1])
index1 += 1
else:
items.append(items2[index2])
index2 += 1
items += items1[index1:]
items += items2[index2:]
return items
5. 快速排序
快速排序使用分治法(Divide and conquer)策略來把一個序列(list)分為較小和較大的2個子序列,然后遞歸地排序兩個子序列。步驟為:
- 挑選基准值:從數列中挑出一個元素,稱為"基准"(pivot);
- 分割:重新排序數列,所有比基准值小的元素擺放在基准前面,所有比基准值大的元素擺在基准后面(與基准值相等的數可以到任何一邊)。在這個分割結束之后,對基准值的排序就已經完成;
- 遞歸排序子序列:遞歸地將小於基准值元素的子序列和大於基准值元素的子序列排序。
# 快速排序 - 選擇樞軸對元素進行划分,左邊都比樞軸小右邊都比樞軸大
def quick_sort(origin_items, comp=lambda x, y: x <= y):
items = origin_items[:]
_quick_sort(items, 0, len(items) - 1, comp)
return items
def _quick_sort(items, start, end, comp):
if start < end:
pos = _partition(items, start, end, comp)
_quick_sort(items, start, pos - 1, comp)
_quick_sort(items, pos + 1, end, comp)
def _partition(items, start, end, comp):
pivot = items[end]
i = start - 1
for j in range(start, end):
if comp(items[j], pivot):
i += 1
items[i], items[j] = items[j], items[i]
items[i + 1], items[end] = items[end], items[i + 1]
return i + 1