八大排序演算法總結Python實現
排序演算法
直接插入排序
直接插入排序是一種簡單的排序演算法,基本操作是將一個記錄插入到一個有序結果集中,並且在插入資料過程中將前面大於或者小於(取決於升序或者降序)當前要插入的資料向後移動。
def InsertSort(array):
j = 0
length = len(array)
for i in range(1, length):
if array[i] < array[i - 1]:
j = i
while j > 0:
if(array[j] < array[j - 1 ]):
array[j], array[j - 1] = array[j - 1], array[j]
j = j - 1
return array
- 時間複雜度O(n*n)
- 額外空間複雜度O(1)
- 穩定
希爾排序
1.插入排序在待排序列有序時可將插入排序的時間複雜度降至O(n),基於此如果待排記錄在進行插入排序之前基本有序則可以降低時間複雜度
2.此外直接插入排序演算法比較簡單,在n值很小時效率也比較高
基於上述2點改進插入排序->希爾排序。
希爾排序的增量dk取法:
1.Shell 增量序列的遞推公式為:
ht=⌊N2⌋,hk=⌊hk+12⌋
2.Hibbard 增量序列的通項公式為:
hi=2i−1hi=2i−1
Hibbard 增量序列的遞推公式為:
h1=1,hi=2∗hi−1+1h1=1,hi=2∗hi−1+1
Hibbard 增量序列的最壞時間複雜度為 Θ(N3/2)Θ(N3/2);平均時間複雜度約為 O(N5/4)O(N5/4)。
3.Knuth 增量序列
Knuth 增量序列的通項公式為:
hi=12(3i−1)hi=12(3i−1)
增量序列的遞推公式為:
h1=1,hi=3∗hi−1+1
4.Gonnet 增量序列
Gonnet 增量序列的遞推公式為:
ht=⌊N2.2⌋,hk=⌊hk+12.2⌋(若h2=2則h1=1)
5.Sedgewick 增量序列
Sedgewick 增量序列的通項公式為: hi=max(9∗4j−9∗2j+1,4k−3∗2k+1)hi=max(9∗4j−9∗2j+1,4k−3∗2k+1)
Sedgewick 增量序列的最壞時間複雜度為 O(N4/3)O(N4/3);
平均時間複雜度約為 O(N7/6)O(N7/6)。
以Knuth 增量序列為基礎的希爾排序演算法如下:
def ShellSort(array):
dk, length, j = 0, len(array), 0
while dk * 3 < length:
dk = dk * 3 + 1
while dk > 0:
for i in range(dk, length):
if (array[i] < array[i - dk]):
tmp = array[i]
j = i - dk
while j >= 0 and array[j] > tmp:
array[j + dk] = array[j]
j -= dk
array[j + dk] = tmp
dk //= 3
return array
增量:dk
待排序列長度:length
j:用來處理當前資料對應減去dk的資料
- 時間複雜度比較複雜在此不做討論
- 額外空間複雜度O(1)
- 不穩定
### 氣泡排序
氣泡排序基本過程,比較相鄰元素之間的大小關係,在一趟氣泡排序中可以從n個數據中選取最大或最小值(取決於升序還是降序),在迴圈過程中也就是從剩餘的資料中依次選出其中最大或者最小值的過程。
氣泡排序非遞迴程式碼如下:
def BubbleSort(array):
length = len(array)
for i in range(length - 1):
for j in range(length - i - 1):
if array[j] > array[j + 1]:
array[j], array[j + 1] = array[j + 1], array[j]
return array
length:陣列長度
i:需要迴圈的次數
j:需要比較的次數
時間複雜度:O(n*n)
額外空間複雜度:O(1)
穩定的排序演算法
氣泡排序遞迴程式碼如下:
def BubbleSortRecur(array, end):
if end == 0:
return
for i in range(end - 1):
if array[i] > array[i + 1]:
array[i], array[i + 1] = array[i + 1], array[i]
BubbleSortRecur(array, end - 1)
end:表示迴圈需要比較到的最後一個位置
快速排序
快速排序是對氣泡排序的改進,通過一次排序將一個有序序列分成獨立的兩個部分,一部分比關鍵字小一部分比關鍵字大,再分別對其進行排序使得整個記錄有序。
快排遞迴程式碼:
def QuickSortRecur(array, start, end):
if start >= end:
return
low, high, pivotKey = start, end, array[start]
while low < high:
while low < high and array[high] >= pivotKey:
high -= 1
array[low], array[high] = array[high], array[low]
while low < high and array[low] <= pivotKey:
low += 1
array[low], array[high] = array[high], array[low]
QuickSortRecur(array, start, low - 1)
QuickSortRecur(array, high + 1, end)
lambda版快排程式碼:
qsortlam = lambda arr: len(arr) > 1 and qsort(list(filter(lambda x: x <= arr[0], arr[1:]))) + arr[0:1] + qsort(list(filter(lambda x: x > arr[0], arr[1:]))) or arr
短程式碼版:
def quickSort(arr):
return [] if arr==[] else quickSort([y for y in arr[1:] if y<arr[0]]) + [arr[0]]+ quickSort([y for y in arr[1:] if y>=arr[0]])
非遞迴版本:
def partation(array, low, high):
if low >= high:
return
pivotKey = array[low]
while low < high:
while array[high] >= pivotKey and low < high:
high -= 1
array[high], array[low] = array[low], array[high]
while array[low] <= pivotKey and low < high:
low += 1
array[high], array[low] = array[low], array[high]
return low
def QuickSortNonRecur(array):
stack = []
position = partation(array, 0, len(array) - 1)
if position - 1 > 0:
stack.append((0, position - 1))
if position + 1 < len(array) - 1:
stack.append((position + 1, len(array) - 1))
while len(stack):
low, high = stack[-1]
stack.remove(stack[-1])
mid = partation(array, low, high)
if mid - 1 > low:
stack.append((low, mid - 1))
if mid + 1 < high:
stack.append((mid + 1, high))
return array
快排屬於不穩定排序
平均時間複雜度為O(nlogn)
最差情況複雜度為O(n*n)
額外空間複雜度:O(n)
選擇排序
選擇排序就是在每次比較過程中拿一個值去和該值後邊的所有值去比較,選擇大的或者小的進行交換的過程。
選擇排序程式碼:
def SelectSort(array):
if len(array) <= 1:
return
length = len(array)
for i in range(length):
for j in range(i + 1, length):
if array[i] > array[j]:
array[i], array[j] = array[j], array[i]
return array
選擇排序屬於不穩定排序演算法
時間複雜度O(n*n)
額外空間複雜度為O(1)
堆排序
堆定義:簡單講堆是一個完全二叉樹,滿足根節點大於或小於左子樹和右子樹,根節點大的叫做大頂堆,反之叫做小頂堆。
堆排序是利用堆的性質,將堆頂作為要排序列的最後一個元素,因為堆頂是極大值。之後調整堆的結構,將後續的每個堆頂作為剩餘序列的最後一個元素,直到整個序列只剩一個元素,排序結束。至於調整堆的具體過程在此並不過多描述,請參考演算法導論堆排序相關問題。
根據上述想法堆排序可以編寫遞迴版本的堆排序以及非遞迴版本的堆排序。
非遞迴版本的堆排序:
def heapAdjast(array, start, leng):
length, lchild = leng, 2 * start + 1
while lchild <= length - 1:
if lchild + 1 > length - 1:
if array[lchild // 2] < array[lchild]:
array[lchild], array[lchild // 2] = array[lchild // 2], array[lchild]
return
else:
tmp = max(array[lchild], array[lchild + 1])
if tmp <= array[lchild // 2]:
return
if tmp == array[lchild]:
array[lchild // 2], array[lchild] = array[lchild], array[lchild // 2]
lchild = lchild * 2 + 1
elif tmp == array[lchild + 1]:
array[lchild // 2], array[lchild + 1] = array[lchild + 1], array[lchild // 2]
lchild = (lchild + 1) * 2 + 1
def HeapSort(array):
length = len(array)
for i in range(length//2, 0, -1):
heapAdjast(array, i - 1, len(array))
for i in range(length - 1, 0, -1):
array[i], array[0] = array[0], array[i]
heapAdjast(array, 0, i)
*註釋:***length需要調整的序列長度, length - 1需要調整的列表最後一個 ,lchild // 2指向根節點
遞迴版本堆排序:
def adjust_heap(lists, i, size):
lchild = 2 * i + 1
rchild = 2 * i + 2
max = i
if i < size / 2:
if lchild < size and lists[lchild] > lists[max]:
max = lchild
if rchild < size and lists[rchild] > lists[max]:
max = rchild
if max != i:
lists[max], lists[i] = lists[i], lists[max]
adjust_heap(lists, max, size)
def heap_sort(lists):
size = len(lists)
for i in range(0, (size/2))[::-1]:
adjust_heap(lists, i, size)
for i in range(0, size)[::-1]:
lists[0], lists[i] = lists[i], lists[0]
adjust_heap(lists, 0, i)
遞迴版本相對比較容易理解
堆排序時間複雜度,O(nlogn)
空間複雜度:O(1)
不穩定排序
歸併排序
歸併排序是將一個序列拆分成一個一個元素,之後將兩兩相鄰的元素放入一個新的列表中,依次類推最終將兩個有序的列表歸併成為一個新的列表,返回。
遞迴歸併排序:
def mergeSort(array):
length = len(array)
if length < 2:
return array
mid = length // 2
left = mergeSort(array[:mid])
right = mergeSort(array[mid:])
result = []
lPoint, rPoint = 0, 0
while lPoint < len(left) and rPoint < len(right):
if left[lPoint] < right[rPoint]:
result.append(left[lPoint])
lPoint += 1
else:
result.append(right[rPoint])
rPoint += 1
result += left[lPoint:] + right[rPoint:]
return result
歸併排序平均時間複雜度:O(nlogn)
額外空間複雜度:O(n)
穩定的排序演算法
基數排序
基數排序是在桶排序的基礎上,首先建立0-9的10個桶,之後分別對需要排序的序列的每個數,將該數的第i位取出,之後將整個數字放進對應的桶中,當每位數字都排列之後整個序列有序。排序方式可以從高位開始也可以從低位開始。
import math
def RadixSort(array, radix=10):
k = int(math.ceil(math.log(max(array), radix)))
bucket = [[] for i in range(radix)]
for i in range(k):
for j in array:
bucket[int(j / (radix ** i) % radix)].append(j)
del array[:]
for z in bucket:
array += z
del z[:]
return array
八大排序總結