高階排序演算法【2】--快速排序、歸併排序、堆排序
阿新 • • 發佈:2018-12-09
4、快速排序
- 從數列中挑出一個元素,稱為基準;
- 重新排列數列,所有元素比基準小的擺放在基準前面,所有元素比基準大的擺在基準後面;
- 在這個分割槽結束之後,該基準就位於數列的中間位置;
- 遞迴地對基準左右兩邊的數列進行排序。
快速排序程式碼——第一步 def quick_sort(data, left, right): if left < right: mid = partition(data, left, right) quick_sort(data, left, mid - 1) quick_sort(data, mid + 1, right) 快速排序程式碼——第二步 def partition(data, left, right): left =0;right = len(data)-1 tmp = data[left] while left < right: while left < right and data[right] >= tmp: right -= 1 data[left] = data[right] while left < right and data[left] <= tmp: left += 1 data[right] = data[left] data[left] = tmp ###很顯然,當left ==right時,迴圈會終止,即實現了左邊的數都比tmp小,右邊的數都比tmp大 return left ##left==right,所以返回left和right是一樣的結果
5、歸併排序(Merge Sort)
分治法:很多有用的演算法結構上是遞迴的,為了解決一個特定問題,演算法一次或者多次遞迴呼叫其自身以解決若干子問題。 這些演算法典型地遵循分治法的思想:將原問題分解為幾個規模較小但是類似於原問題的子問題,遞迴求解這些子問題, 然後再合併這些問題的解來建立原問題的解。
現在我們就來看下歸併排序是是如何利用分治法解決問題的。
- 分解:將待排序的 n 個元素分成各包含 n/2 個元素的子序列
- 解決:使用歸併排序遞迴排序兩個子序列
- 合併:合併兩個已經排序的子序列以產生已排序的答案
當陣列被完全分隔成只有單個元素的陣列時,我們需要把它們合併回去,每次兩兩合併成一個有序的序列。
一次歸併程式碼 def merge(li, low, mid, high): i = low #這裡的low是左半段的第一個元素的索引 j = mid + 1 #這裡設定的mid是左半段的最後一個元素的索引,因此j就是右半段的第一個元素的索引 ltmp = [] #新增一個列表來儲存 while i <= mid and j <= high: if li[i] <= li[j]: ltmp.append(li[i]) i += 1 else: ltmp.append(li[j]) j += 1 while i <= mid: ltmp.append(li[i]) i += 1 while j <= high: ltmp.append(li[j]) j += 1 li[low:high + 1] = ltmp
遞迴的呼叫歸併排序
def merge_sort(li, low, high):
if low < high:
mid = (low + high) // 2
mergesort(li, low, mid)
mergesort(li, mid + 1, high)
merge(li, low, mid, high)
演算法分析:
時間複雜度:總的代價是 cnlg(n)+cn ,忽略常數項可以認為是 O(nlg(n))
空間複雜度:o(n)
6、堆排序
為什麼我要將堆排序放在最後呢,相對來說,堆排序是高階排序演算法中相對不好理解的一中排序演算法,我個人的看法而已。
這涉及了二叉樹的理解,以及堆的理解,利用堆資料結構所設計的一種排序演算法,通過每次彈出堆頂元素實現排序。
動畫演示:
堆排序的過程:
- 建立堆
- 得到堆頂元素
- 去掉堆頂,將堆的最後一個元素放到堆頂,此時可通過一次調整重新使堆有序
- 堆頂元素為第二大元素
- 重複步驟3,直到堆變空
## 調整的過程
def sift(data, low, high):
i = low #當前父節點
j = 2 * i + 1 #該父節點的左子節點
tmp = data[i]
while j <= high: #左節點在堆裡
if j < high and data[j] < data[j + 1]: ## 有右節點,且大於左子節點
j += 1 ##由於是先判斷了的,就是為了找出左右子節點中的較大者
if tmp < data[j]: ##判斷父節點是否小於子節點中的較大者
data[i] = data[j] ##小於的話,就相互交換
i = j ##交換之後,交換的子節點成為父節點,此時原子節點處是空著的,讓交換後的父節點成為新的子節點,在執行迴圈
j = 2 * i + 1 ##到這裡,在判斷其左子節點是否在堆裡,在就繼續執行迴圈,不在就退出
else:
break
data[i] = tmp ##迴圈結束後,將最開始的父節點交換到最後迴圈結束時的那個父節點上
def heap_sort(data):
n = len(data)
for i in range(n // 2 - 1, -1, -1): ##這裡的n//2是最後一個元素的父節點,從後往前的建堆
sift(data, i, n - 1) ### 迴圈結束時,一個堆就建好了
for i in range(n - 1, -1, -1): ##是為了出數,也可以新開一個列表,來儲存堆頂元素,出一個元素後,堆的個數同時減一
data[0], data[i] = data[i], data[0]
sift(data, 0, i - 1)
本來還想記錄下希爾排序的,不過用的機會不多,這裡也不做介紹了。我覺得掌握前面的幾種排序演算法,就已經夠了。
堆排序、快速排序、歸併排序小結:
1)三種排序演算法的時間複雜度都是o(nlgn)
2)一般情況下,就執行時間而言: 快速<歸併<堆
3)三種排序的缺點:
- 快速排序:極端情況下,排序效率低
- 歸併排序:需要額外的記憶體開銷
- 堆排序:在快的排序演算法中相對較慢
這裡截張圖作為總結,注這裡的穩定性主要是說比如我們遇到相同的數時,穩定性好的,能保持先前的狀態
參考資料 :