一、冒泡排序
-
冒泡排序的思想:
- 循環把
相鄰的元素進行比較
, 當一個元素大於右側相鄰元素時, 交換它們的位置 - 當一個元素小於或等於右側相鄰元素時, 位置不變
- 由於該排序算法的每一輪都要遍歷所有元素, 總共遍歷(n-1) 輪, 所以平均時間複雜度是
O(n2)
- 循環把
-
冒泡排序示意圖:
-
冒泡排序優化:
-
利用布爾變量
is_sorted
作爲標記,如果在本輪排序中, 元素有交換, 則說明數列無序; 如果沒有元素交換, 則說明數列已然有序, 然後直接跳出大循環
-
在每一輪排序後, 記錄下來最後一次元素交換的位置, 該位置即爲無序數列的邊界, 再往後就是有序區了
-
雞尾酒排序:
- 排序過程就像鐘擺一樣,奇數輪從左到右排序, 偶數輪從右到左排序
- 針對大部分元素已經有序情況的優化
-
-
代碼實踐:
def bubble_sort(alist):
# 總共需要遍歷 n-1 輪
for pair_num in range(len(alist) - 1, 0, -1):
# 每一輪需要 pair_num 對相鄰數據
is_sorted = True # 已經有序
for i in range(pair_num):
if alist[i] > alist[i + 1]:
"""
temp = alist[i]
alist[i] = alist[i + 1]
alist[i + 1] = temp
"""
alist[i], alist[i + 1] = alist[i + 1], alist[i] # python 支持直接交換
is_sorted = False
if is_sorted:
break
alist = [20, 30, 40, 90, 50, 60, 70, 80, 100, 110] # 加上 is_sorted 只需要二輪即可結束排序
bubble_sort(alist)
print(alist) # [20, 30, 40, 50, 60, 70, 80, 90, 100, 110]
二、快速排序
- 快速排序的思想:
- 在每一輪挑選一個基準元素, 並讓其他比它大的元素移動到右邊, 比它小的元素移動到左邊, 從而把數據表拆解成兩個部分
- 分治法:原數據表在每一輪都被拆分成兩部分, 每一部分在下一輪又分別被拆分成兩部分, 直到不可再分爲止,排序的時間複雜度爲
O(nlogn)
,如果只是單純的二分查找,時間複雜度爲O(logn)
- 基準元素(
pivot
)的選擇:一般會隨機選擇一個,並且讓基準元素和數列首元素交換位置(亦可直接選擇第一個元素)
- 元素的交換:雙邊循環法
- 分裂數據表的目標:找到基準元素應處的位置,比它大的元素移動到右邊, 比它小的元素移動到左邊, 從而把數據表拆解成兩個部分
- 分裂數據表的手段:設置左右指針(
left_idx/right_idx
),左指針向右移動,右指針向左移動- 左指針一直向右移動,碰到比基準元素大的就停止
- 右指針一直向左移動,碰到比基準元素小的就停止
- 然後把左右指針所指的數據項交換
- 重複上述過程,直到左指針移動到右指針的右側(
cross
),停止移動,將基準元素和右指針
所指元素進行交換
- 代碼實踐:
# 基於遞歸的雙邊循環快速排序法
def quick_sort(lst, start_idx, end_idx):
# 遞歸結束條件:start_idx 大等於 end_idx 的時候
if start_idx >= end_idx:
return
# 得到基準元素應處的位置
pivot_idx = partition(lst, start_idx, end_idx)
# 根據基準元素,分成兩部分遞歸排序
quick_sort(lst, start_idx, pivot_idx - 1) # left quick sort
quick_sort(lst, pivot_idx + 1, end_idx) # right quick sort
def partition(lst, start_idx, end_idx):
"""找到基準元素應處的位置,比它大的元素移動到右邊,
比它小的元素移動到左邊"""
# 取第一個位置的元素作爲基準元素(也可以選擇隨機位置)
pivot_value = lst[start_idx]
# 左右指針初始索引值
left_idx = start_idx + 1
right_idx = end_idx
split_point_founded = False
while not split_point_founded:
# 向右移動左指針
while left_idx <= right_idx and lst[left_idx] <= pivot_value:
left_idx += 1
# 向左移動右指針
while left_idx <= right_idx and lst[right_idx] >= pivot_value:
right_idx -= 1
# 兩指針相互交錯則結束移動,否則交換左右指針所指向的元素
if right_idx < left_idx:
split_point_founded = True
else:
temp = lst[left_idx]
lst[left_idx] = lst[right_idx]
lst[right_idx] = temp
# 將基準元素和右指針所指元素進行交換
temp = lst[start_idx]
lst[start_idx] = lst[right_idx]
lst[right_idx] = temp
return right_idx # 返回基準元素點,也就是分裂點
alist = [54, 26, 93, 17, 77, 31, 44, 55, 20]
quick_sort(alist, 0, len(alist) - 1)
print(alist) # [17, 20, 26, 31, 44, 54, 55, 77, 93]
三、堆排序
- 把無序數組構建成二叉堆。 需要從小到大排序, 則構建成最大堆; 需要從大到小排序, 則構建成最小堆。
- 循環刪除堆頂元素, 替換到二叉堆的末尾, 調整堆產生新的堆頂
四、計數排序
- 計數排序的思想:
- 直接遍歷數組, 輸出數組元素的下標值, 元素的值是幾, 就輸出幾次
- 它適用於一定範圍內的整數排序
- 數組長度優化方法:
- 以數列(最大值 - 最小值 + 1)作爲統計數組的長度
- 數列的最小值作爲一個偏移量, 用於計算整數在統計數組中的下標
五、桶排序