最近重新溫習資料結構與演算法,於是使用Python將各個排序演算法實現以下,以便做個總結
排序演算法分類
- 1:簡單排序
- 簡單插入排序
- 氣泡排序
- 2:複雜排序
- 快速排序
- 歸併排序
- 堆排序
簡單插入排序
- 原理:不斷地把一個個元素插入一個序列中,最終得到排序序列
- 注意:為減少輔助空間最合適的辦法就是把正在構造的排序序列嵌入到原來的表中
- 時間複雜度:最壞:O(n ** 2),平均:O(n ** 2),最好:O(n)
- 空間複雜度:O(1)
lst = [12,43,2,5,7,8]
print('排序前:',lst)
for i in range(1,len(lst)):
x = lst[i]
j = i
while j > 0 and lst[j - 1] > x:
lst[j] = lst[j - 1]
j -= 1
lst[j] = x
print('排序後:',lst)
複製程式碼
排序前: [12, 43, 2, 5, 7, 8]
排序後: [2, 5, 7, 8, 12, 43]
複製程式碼
選擇排序
- 原理:找出序列中最小的元素放在第0個位置,然後從其餘元素中找出最小元素放在第1個位置,依次類推,直到只有一個元素為止
- 注意:只需要遍歷len(lst) - 1次就可以
- 時間複雜度:任何情況下都是O(n ** 2),沒有適應性
- 空間複雜度:O(1)
#直接選擇排序
lst = [12,43,2,5,7,8]
print('排序前:',lst)
for i in range(len(lst) - 1):
j = i #記錄遍歷元素中最小元素的位置
for k in range(i,len(lst)):
if lst[k] < lst[j] : #發現後面比要替換位置元素小的資料
j = k
if j != i :#遍歷完成後如果j和初始位置i不相等,則交換j和i位置的元素
lst[j],lst[i] = lst[i],lst[j]
print('排序後:',lst)
複製程式碼
排序前: [12, 43, 2, 5, 7, 8]
排序後: [2, 5, 7, 8, 12, 43]
複製程式碼
氣泡排序
- 原理:氣泡排序是通過交換元素消除逆序實現排序方法,比較相鄰的元素,如果是逆序就交換
- 注意:如果某次排序沒有發生任何資料交換情況,說明排序已完成,就終止迴圈
- 時間複雜度:最好O(n)最壞O(n ** 2)平均O(n ** 2)
- 空間複雜度:O(1)
- 說明:氣泡排序效率較低,實際效果劣於複雜度相同的簡單插入排序演算法,主要原因1、反覆交換中做賦值操作比較多,累計起來代價比較大2、一些距離最終位置很遠的記錄可能拖累整個演算法
#直接選擇排序
lst = [12,43,2,5,7,8]
print('排序前:',lst)
for i in range(len(lst)):
found = False #記錄此次是否找到交換的元素,如果沒有則表示排序已完成
for j in range(1,len(lst) - i):
#如果發現前一個元素比後一個元素,就將兩個元素交換位置
if lst[j - 1] > lst[j] :
lst[j - 1],lst[j] = lst[j],lst[j - 1]
found = True
if not found :
break
print('排序前:',lst)
複製程式碼
排序前: [12, 43, 2, 5, 7, 8]
排序前: [2, 5, 7, 8, 12, 43]
複製程式碼
快速排序
- 原理:選擇一個標準,把排序序列中的記錄按照這個標準分為大小兩組,較小的一組排在前面,較大的一組排在後面,然後使用遞迴對大組小組重複以上操作,直到組裡只有一個元素為止
- 時間複雜度:最壞時間複雜度O(n**2),平均時間複雜度O(n log n),最好時間複雜度O(n log n)
- 空間複雜度:O(log n)
- 說明:在各種基於關鍵碼比較的內排序演算法中,快速排序是實踐中平均速度最快的演算法之一
#快速排序
lst = [12,43,2,5,23,51,7,8]
print(lst,':排序前')
def ks_sort(lst,begin,end):
if begin >= end :
return None
piovt = lst[begin]
i = begin
for j in range(begin + 1,end + 1):
if lst[j] < piovt:#發現比begin為止小的元素就用i+1為止的元素和它交換
i += 1
lst[i],lst[j] = lst[j],lst[i]
lst[begin],lst[i] = lst[i],lst[begin]#遍歷一次後將begin移動到大小組中間的位置
print(lst)
#使用遞迴方式處理大小組,現第i個位置的元素是原來的begin位置的元素,不需要再考慮
ks_sort(lst,begin,i - 1)
ks_sort(lst,i + 1,end)
ks_sort(lst,0,len(lst) - 1)
複製程式碼
[12, 43, 2, 5, 23, 51, 7, 8] :排序前
[8, 2, 5, 7, 12, 51, 43, 23]
[7, 2, 5, 8, 12, 51, 43, 23]
[5, 2, 7, 8, 12, 51, 43, 23]
[2, 5, 7, 8, 12, 51, 43, 23]
[2, 5, 7, 8, 12, 23, 43, 51]
[2, 5, 7, 8, 12, 23, 43, 51]
複製程式碼
歸併排序
-
歸併排序原理:
-
1、初始時,將待排序序列中的n個記錄看做是n個有序子序列
-
2、把當時序列組裡的有序子序列兩兩歸併,完成一遍序列組裡的排序序列個數減半,每個序列的長度加倍
-
3、對加長的有序序列重複上面的操作,最終得到一個長度為n的有序序列
-
這種歸併方法為二路歸併排序,還有三路歸併排序和多路歸併排序
-
歸併排序操作過程中對資料的訪問具有區域性性,適合外存資料交換的特點,特別適合處理一組記錄形成的資料塊
-
由於這些情況,歸併排序適合於處理儲存在外存的大量資料
-
-
歸併排序分為三層,每層負責不同的工作
- 1、最下層:實現表中相鄰的一對有序序列的歸併工作,將歸併的結果存入另一個順序表裡相同的位置
- 2、中間層:基於操作一(一對序列的歸併操作),實現對整個表裡順序各對有序序列的歸併,完成一遍歸併,
- 對序列的歸併結果順序存入另一個順序表裡的同位置分段
- 3、最高層:在兩個順序表之間往復執行操作2,完成一遍歸併後交換兩個表的地位,然後重複2的工作,
- 直到整個表中只有一個有序序列
-
時間複雜度:最壞情況O(n log n) 平均O(n log n) 最好 O(n log n)
-
空間複雜度:O(n)
def merge(lfrom,lto,low,mid,high):
'''
功能:完成表中連續排放的兩個有序序列的歸併工作
:param lfrom:將lfrom中的資料歸併到lto中,
:param lto:將歸併的結果放到lto中
:param low:需要歸併的序列分別是lfrom[low:mid],lfrom[mid:high]
:param mid:
:param high:
:return:None
'''
i,j,k = low,mid,low
while i < mid and j < high: #反覆複製分段首記錄中較小的
if lfrom[i] <= lfrom[j]:
lto[k] = lfrom[i]
i += 1
else :
lto[k] = lfrom[j]
j += 1
k += 1
while i < mid: #複製第一段剩餘記錄
lto[k] = lfrom[i]
i += 1
k += 1
while j < high: #複製第二段剩餘記錄
lto[k] = lfrom[j]
j += 1
k += 1
def merge_pass(lfrom,lto,llen,slen):
'''
功能:歸併長為slen的兩段序列
:param lfrom: 要歸併的序列
:param lto: 歸併後存放的序列
:param llen: 開始歸併時分段長度,
:param slen: 整個表的長度
:return:None
'''
i = 0
while i + 2 * slen < llen: #歸併長slen的兩段
merge(lfrom , lto , i , i + slen , i + 2 * slen)
i += 2 * slen
if i + slen < llen: #剩下兩段,後段長度小於slen
merge(lfrom,lto,i,i + slen,llen)
else : #只剩下一段,複製到lto
for j in range(i,llen):
lto[j] = lfrom[j]
def merge_sort(lst):
'''
功能:建立另一個長度相同的表,然後在兩個表中往復做一遍遍的歸併
:param lst: 要排序的列表
:return:None
'''
slen,llen = 1,len(lst)
#建立另一個長度相同的列表
templst = [None]*llen
while slen < llen :
merge_pass(lst,templst,llen,slen)
slen *= 2
merge_pass(templst,lst,llen,slen)#結果存回原位
slen *= 2
lst = [21,43,6,3,7,31,43,23]
print('開始:',lst)
merge_sort(lst)
print('結束:',lst)
複製程式碼
開始: [21, 43, 6, 3, 7, 31, 43, 23]
結束: [3, 6, 7, 21, 23, 31, 43, 43]
複製程式碼
堆排序
-
什麼是堆
- 在這裡首先要先解釋一下什麼是堆,堆疊是計算機的兩種最基本的資料結構。
- 堆的特點就是FIFO(first in first out)先進先出,
- 堆在接收資料的時候先接收的資料會被先彈出。
- 棧的特性正好與堆相反,是屬於FILO(first in/last out)先進後出的型別。
- 棧處於一級快取而堆處於二級快取中。
-
堆節點的訪問
- 通常堆是通過一維陣列來實現的。在陣列起始位置為0的情況中
- (1)父節點i的左子節點在位置(2*i+1);
- (2)父節點i的右子節點在位置(2*i+2);
- (3)子節點i的父節點在位置floor((i-1)/2);
-
堆操作
- 堆可以分為大根堆和小根堆,這裡用最大堆的情況來定義操作:
- (1)最大堆調整(MAX_Heapify):將堆的末端子節點作調整,使得子節點永遠小於父節點。
- 這是核心步驟,在建堆和堆排序都會用到。比較i的根節點和與其所對應i的孩子節點的值。
- 當i根節點的值比左孩子節點的值要小的時候,就把i根節點和左孩子節點所對應的值交換,
- 當i根節點的值比右孩子的節點所對應的值要小的時候,就把i根節點和右孩子節點所對應的值交換。
- 然後再呼叫堆調整這個過程,可見這是一個遞迴的過程。
- (2)建立最大堆(Build_Max_Heap):將堆所有資料重新排序。
- 建堆的過程其實就是不斷做最大堆調整的過程,
- 從len/2出開始調整,一直比到第一個節點。
- (3)堆排序(HeapSort):移除位在第一個資料的根節點,並做最大堆調整的遞迴運算。
- 堆排序是利用建堆和堆調整來進行的。首先先建堆,然後將堆的根節點選出與最後一個節點進行
- 交換,然後將前面len-1個節點繼續做堆調整的過程。直到將所有的節點取出,
- 對於n個數我們只需要做n-1次操作。
-
時間複雜度:O(nlgn)
-
空間複雜度:O(1)
def MAX_Heapify(heap,HeapSize,root):#在堆中做結構調整使得父節點的值大於子節點
left = 2*root + 1
right = left + 1
larger = root
if left < HeapSize and heap[larger] < heap[left]:
larger = left
if right < HeapSize and heap[larger] < heap[right]:
larger = right
if larger != root:#如果做了堆調整則larger的值等於左節點或者右節點的,這個時候做對調值操作
heap[larger],heap[root] = heap[root],heap[larger]
MAX_Heapify(heap, HeapSize, larger)
def Build_MAX_Heap(heap):#構造一個堆,將堆中所有資料重新排序
HeapSize = len(heap)#將堆的長度當獨拿出來方便
for i in range((HeapSize -2)//2,-1,-1):#從後往前出數
MAX_Heapify(heap,HeapSize,i)
def HeapSort(heap):#將根節點取出與最後一位做對調,對前面len-1個節點繼續進行對調整過程。
Build_MAX_Heap(heap)
for i in range(len(heap)-1,-1,-1):
heap[0],heap[i] = heap[i],heap[0]
MAX_Heapify(heap, i, 0)
return heap
lst = [21,43,6,3,7,31,43,23]
print('排序前:',lst)
HeapSort(lst)
print('排序後:',lst)
複製程式碼
排序前: [21, 43, 6, 3, 7, 31, 43, 23]
排序後: [3, 6, 7, 21, 23, 31, 43, 43]
複製程式碼