一篇夯實一個知識點系列--python實現十大排序演算法
阿新 • • 發佈:2020-08-09
#### 寫在前面
> 排序是查詢是演算法中最重要的兩個概念,我們大多數情況下都在進行查詢和排序。科學家們窮盡努力,想使得排序和查詢能夠更加快速。本篇文章用Python實現十大排序演算法。
#### 乾貨兒
> 排序演算法從不同維度可以分為好多類別,從其排序思想(排序思想一般決定了其時間複雜度的量級)來看,主要可以分為四類:
>
> - 雙層迴圈比較排序:平方級排序
> - 分治策略比較排序:對數級排序
> - 另闢蹊徑的非比較方式排序:線性級排序
> - 笑死人不償命的其它排序:有著天馬行空的時間複雜度,難以描述。
##### 平方級排序
- 氣泡排序
> 1. 從陣列的第一個元素開始,比較當前元素和下一個元素,如果當前元素大於下一個元素,交換兩元素位置。
> 2. 接著從第二個元素開始,重複第一步,直到當前元素為最後一個元素。此時最後一個元素為最大元素。未排序陣列為除最後一個元素之外的其它元素。
> 3. 對未排序陣列不斷重複以上步驟,直到未排序陣列為空。
```python
def bubble_sort(arr):
length = len(arr)
for i in range(length):
for j in range(length-i-1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
return arr
```
- 選擇排序
> 1. 選取陣列中的最小元素,和陣列中的第一個元素交換位置
> 2. 選取陣列中除第一個元素外剩餘元素的最小元素,和陣列中的第二個元素交換位置。
> 3. 不斷重複以上步驟,直到當前選取的元素為陣列中最後一個元素。
```python
def select_sort(arr):
length = len(arr)
for i in range(length):
min_ix = i
for j in range(i, length):
if arr[j] < arr[min_ix]:
min_ix = j
arr[min_ix], arr[i] = arr[i], arr[min_ix]
return arr
```
- 插入排序
> 1. 從陣列的第一個元素開始,不斷比較當前元素和前一個元素。如果當前元素比前一個元素小,那麼就將當前元素插入到前一個元素的前面(即兩者交換位置)
> 2. 從第二個元素開始,不斷重複以上步驟,直到所有元素全部經歷上述步驟。
```python
def insert_sort(arr):
length = len(arr)
for i in range(length):
for j in range(i, 0, -1):
if arr[j] < arr[j-1]:
arr[j], arr[j-1] = arr[j-1], arr[j]
return arr
```
##### 對數級排序
- 希爾排序
> 1. 選擇一個增量值k,分別將陣列中索引以k為間隔的元素放在同一個陣列中。
> 2. 將增量值縮小為原增量值的1/2,然後重複步驟1。
> 3. 直到增量值為1,使用插入排序對已經部分有序的陣列進行排序。
```python
def shell_sort(arr):
n = len(arr)
gap = int(n/2)
while gap > 0:
for i in range(gap,n):
temp = arr[i]
j = i
while j >= gap and arr[j-gap] >temp:
arr[j] = arr[j-gap]
j -= gap
arr[j] = temp
gap = int(gap/2)
return arr
```
- 歸併排序
> 1. 以陣列中間元素為界,將陣列分為等長的兩個陣列(可能不等長,和陣列長度的奇偶性有關)。
> 2. 對所有陣列執行步驟1
> 3. 不斷重複以上步驟,直到將陣列分割為多個包含單個元素的陣列。
> 4. 將以上陣列兩兩合併,並排序,此時為多個包含有序的兩個元素的陣列(可能包含單個元素,跟陣列長度的奇偶性有關)。
> 5. 重複步驟4,直到將所有數組合併為一個數組
```python
def merge(left, right):
i = j = 0
res = []
while i < len(left) and j < len(right):
if left[i] < right[j]:
res.append(left[i])
i += 1
else:
res.append(right[j])
j += 1
if i == len(left):
res.extend(right[j:])
else:
res.extend(left[i:])
return res
def merge_sort(arr):
if len(arr) <= 1:
return arr
length = len(arr)
i = int(length / 2)
left = merge_sort(arr[:i])
right = merge_sort(arr[i:])
return merge(left, right)
```
- 快速排序
> 1. 挑選一個元素為基準
> 2. 比基準大的元素作為一個數組,比基準小或者等於基準的元素作為一個數組。
> 3. 對新分割的陣列,不斷重複以上步驟,直到分割後的陣列只含有1個或者0個元素
> 4. 遞迴地合併以上陣列為有序陣列,合併方式為:[小於等於基準的元素]+[基準]+[大於基準的元素]
```python
def fast_sort(arr):
if len(arr) <= 1:
return arr
pivot = arr.pop()
left = [i for i in arr if i <= pivot]
right = [i for i in arr if i > pivot]
return fast_sort(left) + [pivot] + fast_sort(right)
```
以上演算法需要額外的空間,如果我們將小於等於基準的元素不斷置於基準元素之前,大於基準的元素置於基準元素之後,那麼就可以實現不需要額外空間的就地排序。
```python
def fast_sort_on_extra_spacing(arr):
l = 0
h = len(arr)-1
def partition(arr, l, h):
pivot = arr[h]
for i in range(l, h):
if arr[i] <= pivot:
arr[l], arr[i] = arr[i], arr[l]
l += 1
arr[h], arr[l] = arr[l], arr[h]
return l
def fast_sort(arr, l, h):
if l < h:
pivot = partition(arr, l, h)
fast_sort(arr, l, pivot-1)
fast_sort(arr, pivot+1, h)
return arr
return fast_sort(arr, l, h)
```
- 堆排序
> 1. 先對待排序陣列構造大根堆
> 2. 將大根堆第一個元素和最後一個元素交換位置。此時最後一個元素為最大元素,待排序陣列為除最後一個元素之外的所有元素。
> 3. 對待排序陣列不斷重複以上步驟,直到待排序陣列中只有一個元素。
```python
def heapify(arr, n, i):
# build a max root heap
max_ix = i
left_i = 2 * i + 1
right_i = 2 * i + 2
if left_i < n and arr[max_ix] < arr[left_i]:
max_ix = left_i
if right_i < n and arr[max_ix] < arr[right_i]:
max_ix = right_i
if max_ix != i:
arr[max_ix], arr[i] = arr[i], arr[max_ix]
heapify(arr, n, max_ix)
def heap_sort(arr):
for i in range(n-1, -1, -1):
heapify(arr, n, i)
for i in range(n-1, 0, -1):
arr[i], arr[0] = arr[0], arr[i]
heapify(arr, i, 0)
return arr
```
##### 線性級排序
> 此排序方法只適用於陣列元素全部為整數的情景。
- 計數排序
> 1. 找出待排序陣列中最大的元素,構造一個長度為此元素值的計數陣列。
> 2. 遍歷待排序陣列元素,以當前元素為索引,將計數陣列中的對應值加1.
> 3. 此時計數陣列中的索引為待排序陣列中的元素,值為出現的次數。將計數陣列中所有值非0的元素索引根據其出現次數串聯起來。
```python
def count_sort(arr):
min_ix, max_ix = min(arr), max(arr)
bucket = [0 for _ in range(max_ix+1)]
for i in arr:
bucket[i] += 1
return sum([[i] * bucket[i] for i in range(len(bucket)) if bucket[i] != 0], [])
```
- 桶排序
> 1. 設定固定數量的桶(這是個技術活兒).
> 2. 將待排序陣列中的元素放入對應的桶中(對應關係也是個技術活兒,下面的例子中採用整除)
> 3. 將非空桶中的元素串聯起來。
```python
def bucket_sort(arr):
min_ix, max_ix = min(arr), max(arr)
bucket_range = (max_ix - min_ix) / len(arr)
# +1 avoid for that max_ix - min_ix will raise a IndexError
temp_bucket = [[] for i in range(len(arr) + 1)]
for i in arr:
temp_bucket[int((i-min_ix)//bucket_range)].append(i)
return sum(temp_bucket, [])
```
- 基數排序
> 1. 找出待排序陣列中最大元素的位數。將所有元素補足此位數,補足方式為前面補0。
> 2. 從最低位到最高位,進行多輪陣列排序。
```python
def radix_sort(arr):
max_value = max(arr)
num_digits = len(str(max_value))
for i in range(num_digits):
bucket = [[] for _ in range(10)]
for j in arr:
bucket[j//(10**i)%10].append(j)
arr = [j for i in bucket for j in i]
return arr
```
##### 笑死人不償命排序
- 睡排序
> 讓多個程序(執行緒)分別睡眠待排序陣列中的元素時長,先睡醒的程序(執行緒),對應元素追加到結果陣列中。
- 猴子排序
> 不停隨機排序,然後檢查是否元素全部有序。如果你是歐皇,那麼你可以嘗試用這個排序演算法,很可能一次搞定。
##### 排序演算法複雜度、穩定性及通用性總結
| 演算法 | 平均時間複雜度 | 最優時間複雜度 | 最壞時間複雜度 | 空間複雜度 | 是否原地排序 | 是否穩定 | 是否通用 |
| -------- | ---------------- | --------------------- | --------------------- | ---------- | ------------ | -------- | -------- |
| 氣泡排序 | O(n2) | O(n) | O(n2) | O(1) | 是 | 是 | 是 |
| 選擇排序 | O(n2) | O(n2) | O(n2) | O(1) | 是 | 否 | 是 |
| 插入排序 | O(n2) | O(n) | O(n2) | O(1) | 是 | 是 | 是 |
| 希爾排序 | O(n logn) | O(n log2 n) | O(n log2n) | O(1) | 是 | 否 | 是 |
| 歸併排序 | O(n logn) | O(n logn) | O(n logn) | O(n) | 否 | 是 | 是 |
| 快速排序 | O(n logn) | O(n logn) | O(n2) | O(n logn) | 是 | 否 | 是 |
| 堆排序 | O(n logn) | O(n logn) | O(n logn) | O(1) | 是 | 否 | 是 |
| 計數排序 | O(n+k) | O(n+k) | O(n+k) | O(k) | 否 | 是 | 否 |
| 桶排序 | O(n+k) | O(n+k) | O(n2 ) | O(n+k) | 否 | 是 | 否 |
| 基數排序 | O(n*k) | O(n*k) | O(n*k) | O(n+k) | 否 | 是 | 否 |
##### 寫在最後
排序演算法是演算法學習中的核心。掌握排序演算法及其思想是學習其它演算法的基礎。希望大家可以熟練掌握。歡迎關注個人部落格:[藥少敏的部落格](https://shaominyao.github