10種經典排序算法包括冒泡排序、選擇排序、快速排序、歸并排序、堆排序、插入排序、希爾排序、計數排序、桶排序、基數排序等。
當然,還有一些其他的排序算法,大家可以繼續去研究下。
01冒泡排序
冒泡排序(Bubble Sort)是一種比較簡單的排序算法,它重復地走訪過要排序的元素,依次比較相鄰兩個元素,如果它們的順序錯誤就把他們調換過來,直到沒有元素再需要交換,排序完成。
注:上圖中,數字表示的是數據序列原始的索引號。
算法過程
- 比較相鄰的元素,如果前一個比后一個大,就把它們兩個對調位置。
- 對排序數組中每一對相鄰元素做同樣的工作,直到全部完成,此時最后的元素將會是本輪排序中最大的數。
- 對剩下的元素繼續重復以上的步驟,直到沒有任何一個元素需要比較。
冒泡排序每次找出一個最大的元素,因此需要遍歷 n-1 次 (n為數據序列的長度)。
算法特點
什么時候最快(Best Cases):當輸入的數據已經是正序時。
什么時候最慢(Worst Cases):當輸入的數據是反序時。
Python/ target=_blank class=infotextkey>Python代碼
def bubble_sort(lst):
n = len(lst)
for i in range(n):
for j in range(1, n - i):
if lst[j - 1] > lst[j]:
lst[j - 1], lst[j] = lst[j], lst[j - 1]
return lst
02選擇排序
選擇排序原理
選擇排序(Selection Sort)的原理,每一輪從待排序的記錄中選出最小的元素,存放在序列的起始位置,然后再從剩余的未排序元素中尋找到最小元素,然后放到已排序的序列的末尾。以此類推,直到全部待排序的數據元素的個數為零。得到數值從小到達排序的數據序列。
也可以每一輪找出數值最大的元素,這樣的話,排序完畢后的數組最終是從大到小排列。
選擇排序每次選出最小(最大)的元素,因此需要遍歷 n-1 次。
Python代碼
def selection_sort(lst):
for i in range(len(lst) - 1):
min_index = i
for j in range(i + 1, len(lst)):
if lst[j] < lst[min_index]:
min_index = j
lst[i], lst[min_index] = lst[min_index], lst[i]
return lst
03快速排序
快速排序(Quick Sort),是在上世紀60年代,由美國人東尼·霍爾提出的一種排序方法。這種排序方式,在當時已經是非常快的一種排序了。因此在命名上,才將之稱為“快速排序”。
算法過程
- 先從數據序列中取出一個數作為基準數(baseline,習慣取第一個數)。
- 分區過程,將比基準數小的數全放到它的左邊,大于或等于它的數全放到它的右邊。
- 再對左右區間遞歸(recursive)重復第二步,直到各區間只有一個數。
因為數據序列之間的順序都是固定的。最后將這些子序列一次組合起來,整體的排序就完成了。
如下圖,對于數據序列,先取第一個數據 15為基準數,將比 15 小的數放在左邊,比 15 大(大于或等于)的數放在右邊
接下來,對于左邊部分,重復上面的步驟,如下圖,取左邊序列的第一個數據 11 為基準數,將比 11 小的數放在左邊,比 11 大(大于或等于)的數放在右邊。
繼續遞歸重復上述過程,直到每個區間只有一個數。這樣就會完成排序
Python代碼
def quick_sort(lst):
n = len(lst)
if n <= 1:
return lst
baseline = lst[0]
left = [lst[i] for i in range(1, len(lst)) if lst[i] < baseline]
right = [lst[i] for i in range(1, len(lst)) if lst[i] >= baseline]
return quick_sort(left) + [baseline] + quick_sort(right)
04歸并排序
算法思想
歸并排序(Merge Sort)是建立在歸并操作上的一種有效的排序算法。該算法是采用分治法的一個非常典型的應用,歸并排序將兩個已經有序的子序列合并成一個有序的序列。
算法流程
主要兩步(拆分,合并)
- 步驟1:進行序列拆分,一直拆分到只有一個元素;
- 步驟2:拆分完成后,開始遞歸合并。
思路:假設我們有一個沒有排好序的序列,那么我們首先使用拆分的方法將這個序列分割成一個個已經排好序的子序列(直到剩下一個元素)。然后再利用歸并方法將一個個有序的子序列合并成排好序的序列。
圖解算法
拆分
對于數據序列 [15,11,13,18,10] ,我們從首先從數據序列的中間位置開始拆分,中間位置的設置為
首次拆分如下:
第一次拆分后,依次對子序列進行拆分,拆分過程如下:
合并
合并過程中,對于左右分區以及其子區間,遞歸使用合并方法。先從左邊最小的子區間開始,對于每個區間,依次將最小的數據放在最左邊,然后對右邊區間也執行同樣的操作。
合并過程的完整圖示如下:
Python代碼
def merge_sort(lst):
def merge(left,right):
i = 0
j = 0
result = []
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.Append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result = result + left[i:] + right[j:]
return result
n = len(lst)
if n <= 1:
return lst
mid = n // 2
left = merge_sort(lst[:mid])
right = merge_sort(lst[mid:])
return merge(left,right)
05堆排序
要理解堆排序(Heap Sort)算法,首先要知道什么是“堆”。
堆的定義
對于 n 個元素的數據序列
,當且僅當滿足下列情形之一時,才稱之為 堆:
情形1:
情形2:
若序列
是堆,則堆頂元素必為序列中n個元素的最小值或最大值。
小頂堆如下圖所示:
小頂堆
大頂堆如下圖所示:
大頂堆
若在輸出堆頂的最小值(或最大值)之后,使得剩余n-1個元素的序列重又建成一個堆,則得到n個元素的次小值(或次大值)。如此反復執行,便能得到一個有序序列,這個過程稱之為 堆排序。
堆的存儲
一般用數組來表示堆,若根結點存在序號 0 處, i 結點的父結點下標就為 (i-1)/2。i 結點的左右子結點下標分別為 2*i+1和 2*i+2 。
對于上面提到的小頂堆和大頂堆,其數據存儲情況如下:
小頂堆
大頂堆
每幅圖的右邊為其數據存儲結構,左邊為其邏輯結構。
堆排序
實現堆排序需要解決兩個問題:
- 如何由一個無序序列建成一個堆?
- 如何在輸出堆頂元素之后,調整剩余元素成為一個新的堆?
堆的初始化
第一個問題實際上就是堆的初始化,下面來闡述下如何構造初始堆,假設初始的數據序列如下:
咱們首先需要將其以樹形結構來展示,如下:
初始化堆的時候是對所有的非葉子結點進行篩選。
最后一個非終端元素的下標是 [n/2] 向下取整,所以篩選只需要從第 [n/2] 向下取整個元素開始,從后往前進行調整。
從最后一個非葉子結點開始,每次都是從父結點、左邊子節點、右邊子節點中進行比較交換,交換可能會引起子結點不滿足堆的性質,所以每次交換之后需要重新對被交換的子結點進行調整。
以小頂堆為例,構造初始堆的過程如下:
進行堆排序
有了初始堆之后就可以進行排序了。
堆排序是一種選擇排序。建立的初始堆為初始的無序區。
排序開始,首先輸出堆頂元素(因為它是最值),將堆頂元素和最后一個元素交換,這樣,第n個位置(即最后一個位置)作為有序區,前n-1個位置仍是無序區,對無序區進行調整,得到堆之后,再交換堆頂和最后一個元素,這樣有序區長度變為2。。。
大頂堆
交換堆頂元素和最后的元素
無序區-1,有序區+1
不斷進行此操作,將剩下的元素重新調整為堆,然后輸出堆頂元素到有序區。每次交換都導致無序區-1,有序區+1。不斷重復此過程直到有序區長度增長為n-1,排序完成。
Python代碼
def heap_sort(lst):
def adjust_heap(lst, i, size):
left_index = 2 * i + 1
right_index = 2 * i + 2
largest_index = i
if left_index < size and lst[left_index] > lst[largest_index]:
largest_index = left_index
if right_index < size and lst[right_index] > lst[largest_index]:
largest_index = right_index
if largest_index != i:
lst[largest_index], lst[i] = lst[i], lst[largest_index]
adjust_heap(lst, largest_index, size)
def built_heap(lst, size):
for i in range(len(lst)//2)[::-1]:
adjust_heap(lst, i, size)
size = len(lst)
built_heap(lst, size)
for i in range(len(lst))[::-1]:
lst[0], lst[i] = lst[i], lst[0]
adjust_heap(lst, 0, i)
return lst
06插入排序
插入排序(Insertion Sort)就是每一步都將一個需要排序的數據按其大小插入到已經排序的數據序列中的適當位置,直到全部插入完畢。
插入排序如同打撲克牌一樣,每次將后面的牌插到前面已經排好序的牌中。
Python代碼
def insertion_sort(lst):
for i in range(len(lst) - 1):
cur_num, pre_index = lst[i+1], i
while pre_index >= 0 and cur_num < lst[pre_index]:
lst[pre_index + 1] = lst[pre_index]
pre_index -= 1
lst[pre_index + 1] = cur_num
return lst
07希爾排序
基本原理
希爾排序(Shell Sort)是插入排序的一種更高效率的實現。
希爾排序的核心在于間隔序列的設定。既可以提前設定好間隔序列,也可以動態的定義間隔序列。
這里以動態間隔序列為例來描述。初始間隔(gap值)為數據序列長度除以2取整,后續間隔以 前一個間隔數值除以2取整為循環,直到最后一個間隔值為 1 。
對于下面這個數據序列,初始間隔數值為5
先將數據序列按間隔進行子序列分組,第一個子序列的索引為[0,5,10],這里分成了5組。
為方便大家區分不同的子序列,對同一個子序列標注相同的顏色,分組情況如下:
分組結束后,子序列內部進行插入排序,gap為5的子序列內部排序后如下:
注:紅色箭頭標注的地方,是子序列內部排序后的狀態
接下來選取第二個間隔值,按照間隔值進行子序列分組,同樣地,子序列內部分別進行插入排序;
如果數據序列比較長,則會選取第3個、第4個或者更多個間隔值,重復上述的步驟。
gap為2的排序情況前后對照如下:
最后一個間隔值為1,這一次相當于簡單的插入排序。但是經過前幾次排序,序列已經基本有序,因此最后一次排序時間效率就提高了很多。
Python代碼
def shell_sort(lst):
n = len(lst)
gap = n // 2
while gap > 0:
for i in range(gap, n):
for j in range(i, gap - 1, -gap):
if lst[j] < lst[j - gap]:
lst[j], lst[j - gap] = lst[j - gap], lst[j]
else:
break
gap //= 2
return lst
08計數排序
基本原理
計數排序(Counting Sort)的核心在于將輸入的數據值轉化為鍵,存儲在額外開辟的數組空間中。計數排序要求輸入的數據必須是有確定范圍的整數。
算法的步驟如下:
先找出待排序的數組中最大和最小的元素,新開辟一個長度為 最大值-最小值+1 的數組;
然后,統計原數組中每個元素出現的次數,存入到新開辟的數組中;
接下來,根據每個元素出現的次數,按照新開辟數組中從小到大的秩序,依次填充到原來待排序的數組中,完成排序。
Python代碼
def counting_sort(lst):
nums_min = min(lst)
bucket = [0] * (max(lst) + 1 - nums_min)
for num in lst:
bucket[num - nums_min] += 1
i = 0
for j in range(len(bucket)):
while bucket[j] > 0:
lst[i] = j + nums_min
bucket[j] -= 1
i += 1
return lst
09桶排序
基本思想
簡單來說,桶排序(Bucket Sort)就是把數據分組,放在一個個的桶中,對每個桶里面的數據進行排序,然后將桶進行數據合并,完成桶排序。
該算法分為四步,包括劃分桶、數據入桶、桶內排序、數據合并。
桶的劃分過程
這里詳細介紹下桶的劃分過程。
對于一個數值范圍在10到 49范圍內的數組,我們取桶的大小為10 (defaultBucketSize = 10),則第一個桶的范圍為 10到20,第二個桶的數據范圍是20到30,依次類推。最后,我們一共需要4個桶來放入數據。
排序過程
對于下面這個數據序列,初始設定桶的大小為 20 (defaultBucketSize = 20),經計算,一共需要4個桶來放入數據。
然后將原始數組按數值大小放入到對應的桶中,完成數據分組。
對于桶內的數據序列,這時可以用冒泡排序、選擇排序等多種排序算法來對數據進行排序。這些算法,在之前的視頻里已有介紹,大家可以去了解下。
這里,我選用 冒泡排序 來對桶內數據進行排序。
桶內排序完成后,將數據按桶的順序進行合并,這樣就得到所有數值排好序的數據序列了
Python代碼
def bucket_sort(lst, defaultBucketSize=4):
maxVal, minVal = max(lst), min(lst)
bucketSize = defaultBucketSize
bucketCount = (maxVal - minVal) // bucketSize + 1
buckets = [[] for i in range(bucketCount)]
for num in lst:
buckets[(num - minVal) // bucketSize].append(num)
lst.clear()
for bucket in buckets:
bubble_sort(bucket)
lst.extend(bucket)
return lst
10基數排序
基數排序(radix sort)屬于“分配式排序”(distribution sort),它是透過鍵值的部份信息,將要排序的元素分配至某些“桶”中,以達到排序的作用。
基數排序適用于所有元素均為正整數的數組。
基本思想
排序過程分為“分配”和“收集”。
排序過程中,將元素分層為多個關鍵碼進行排序(一般按照數值的個位、十位、百位、…… 進行區分),多關鍵碼排序按照從最主位關鍵碼到最次位關鍵碼或從最次位到最主位關鍵碼的順序逐次排序。
基數排序的方式可以采用最低位優先LSD(Least sgnificant digital)法或最高位優先MSD(Most sgnificant digital)法,LSD的排序方式由鍵值的最右邊開始,而MSD則相反,由鍵值的最左邊開始。
LSD的基數排序適用于位數小的數列,如果位數多的話,使用MSD的效率會比較好,MSD的方式恰與LSD相反,是由高位數為基底開始進行分配,其他的演算方式則都相同。
算法流程
這里以最低位優先LSD為例。
先根據個位數的數值,在掃描數值時將它們分配至編號0到9的桶中,然后將桶子中的數值串接起來。
將這些桶子中的數值重新串接起來,成為新的序列,接著再進行一次分配,這次是根據十位數來分配。
如果排序的對象有三位數以上,則持續進行以上的動作直至最高位數為止。
Python代碼
# LSD Radix Sort
def radix_sort(lst):
mod = 10
div = 1
mostBit = len(str(max(lst)))
buckets = [[] for row in range(mod)]
while mostBit:
for num in lst:
buckets[num // div % mod].append(num)
i = 0
for bucket in buckets:
while bucket:
lst[i] = bucket.pop(0)
i += 1
div *= 10
mostBit -= 1
return lst
11小結
以上就是用 Python 來實現10種經典排序算法的相關內容。
對于這些排序算法的實現,代碼其實并不是最主要的,重要的是需要去理解各種算法的基本思想、基本原理以及其內部的實現過程。
對于每種算法,用其他編程語言同樣是可以去實現的。
并且,對于同一種算法,即使只用 Python 語言,也有多種不同的代碼方式可以來實現,但其基本原理是一致的。