作為一個計算機專業相關的人員(程序猿),無論你從事什么方向(前端、后端、機器學習等),最最基礎的就是對排序和查找的算法原理理解與實現。如果連這個還沒有爛熟於心,隨手就來的話,只能說明你的發展比較堪憂,因為這個是最最初級但也是顯示該專業的最最扎實基礎的部分,所以本人專門詳細整理了十大排序算法及七大查找算法。本人打算用兩篇博客分別進行探討,本篇主要和大家分享一下十大排序算法。下一篇將和大家分享七大查找算法~接下來我會用自以為條理最清楚、語言最簡潔的方式與大家進行分享~歡迎大家批評指正~
ps:筆者一直在努力修正更新中!~
閱讀目錄
0. 干貨總結
0.1 整體分類
首先讓我們宏觀的分類一下幾種排序算法:我們平常所說的排序算法大部分是指內部排序算法,其實還有三種常見的外部排序算法~
那么什么是內部排序和外部排序呢?
所謂的內排序是指所有的數據已經讀入內存,在內存中進行排序的算法。排序過程中不需要對磁盤進行讀寫。同時,內排序也一般假定所有用到的輔助空間也可以直接存在於內存中。與之對應地,另一類排序稱作外排序,即內存中無法保存全部數據,需要進行磁盤訪問,每次讀入部分數據到內存進行排序。
下面給出它們的具體分類:
-
內部排序算法:冒泡排序、快速排序、直接擇排序、直接插入排序、希爾排序、歸並排序、堆排序
- 其中冒泡排序和快速排序屬於交換排序,直接插入排序和希爾排序屬於插入排序
-
外部排序算法:計數排序、基數排序、桶排序等
是不是有點亂,沒關系,看一看我下面畫的圖表清醒一下!

0.2 概念講解
-
穩定性:如果i=j,排序前i在j的前面,排序后i仍然在j的前面,即相等的兩個數字的相對位置在排序前后不變,則該算法是穩定的,否則不穩定。為了方便大家理解,舉個形象點的例子: 用某一算法對[1,3,2,4,2]進行排序后的結果為[1,2,2,3,4],我們可以看到排序前粗體2在細體2之前,排序之后仍然是,則該算法為穩定的。
大家一定會有疑問,穩定性有什么用呢,是這樣的,排序算法如果是穩定的,那么從一個鍵上排序,然后再從另一個鍵上排序,前一個鍵排序的結果可以為后一個鍵排序所用。可能比較難理解,這里再舉個例子方便理解,比如在基數排序中,先將低位排序,再逐次按高位排序,穩定的話就可以保證排序后低位元素的順序在高位相同時是不會改變的,這里大家可以參考我下一篇的基數排序的詳細講解,這樣會理解的更透徹一些~ -
時間復雜度:指執行算法所需要的工作量,即對待排序數據的總操作次數,我們用它來描述算法的運行時間。這里不能再多說了,不然會把筆者累死。。。大家想深入理解的話自行搜索學習吧~
-
空間復雜度:指執行算法所需的內存空間。
在這里我想特別囑咐大家兩點:
-
本人用的是python來實現代碼,其實無論哪種語言,只要你懂得了算法其中的原理,稍微學習一點相應的不同語言基礎語法,你就應該能夠用相應的語言實現出來的~所以筆者認為,編程語言的不同並不會阻礙我們交流~如何你還在為實現代碼所用的語言不同而苦惱,那你可能需要加一把勁兒嘍~畢竟語言只是一門工具,你的大腦思想(算法)才是王道!這是本喳喳個人的理解,可能有誤,忘大家見諒~
-
對於算法的時間復雜度、空間復雜度、穩定性等千萬不要死記硬背(這也是我為什么不填寫上面表格數據的原因,大家別罵我。。如果你是一個稍微不那么慵懶的程序猿,你就可以在我下面的詳細講解中找到全部答案,並深刻理解),深入理解了算法的原理,自然而然就可以得出來。大家千萬不要死記硬背、照本宣科,舉個例子,每個算法的穩定性並不是絕對的,比如冒泡算法若把判斷條件由原來的if L(j) > L(j+1)改成if L(j) >= L(j+1),那么冒泡算法的穩定性就由原來的穩定變為不穩定(后面會詳細講解,這里只是舉個例子),所以死記硬背完全沒有意義,理解算法融於腦中才是王道~
廢話不多說,下面一一進行介紹這十大排序算法~
1. 冒泡排序(Bubble Sort)
1.0 性質總結
-
時間復雜度:
- 平均情況:O(n^2)
- 最好情況:O(n)
- 最壞情況:O(n^2)
-
空間復雜度:O(1)
-
穩定性:穩定 (僅限於本博客代碼,前面說過若把判斷條件由原來的if L(j) > L(j+1)改成if L(j) >= L(j+1),那么穩定性就由原來的穩定變為不穩定!),之后的算法穩定性都是這個道理,之后也不會在贅述了,大家一定要注意啊~!
1.1 基本思想
冒泡排序可以算是最簡單、最基礎的排序算法了,它的基本思想是:重復的遍歷(走過)待排序的一組數字(通常是列表形式),依次比較兩個相鄰的元素(數字),若它們的順序錯誤則將它們調換一下位置,直至沒有元素再需要交換為止。因為每遍歷一次列表,最大(或最小)的元素會經過交換一點點”浮“到列表的一端(頂端),所以形象的稱這個算法為冒泡算法
1.2 具體步驟
- 比較兩個相鄰元素,如果前一個比后一個大,則交換這兩個相鄰元素
- 從頭至尾對每一對相鄰元素進行步驟1的操作,完成1次對整個待排序數字列表的遍歷后,最大的元素就放在了該列表的最后一個位置上了
- 對除最后一個元素的所有元素重復上述步驟,這第二次遍歷后第二大的元素就也放在了正確的位置(整個列表的倒數第二位置上)
- 不斷重復上述步驟,每次遍歷都會將一個元素放在正確的位置上,從而下次遍歷的元素也會隨之減少一個,直至沒有任何一對數字需要比較
1.3 代碼實現
#冒泡排序(python3實現)
def Bubble_Sort(L):
for i in range(len(L)):
for j in range(len(L)-i-1):
if L[j] > L[j+1]:
temp = L[j]
L[j] = L[j+1]
L[j+1] = temp
return L
那么問題來了,上面代碼的時間復雜度無論如何都是O(n^2),那么冒泡算法的最好狀況時間復雜度O(n)是從何而來的呢,這個其實是針對下面的改進的冒泡算法的!
1.4 改進的冒泡排序算法
細心的同學會發現上面的冒泡排序有些笨拙,如果一組數字一開始就完全有序的話,上述算法還是會萌萌的一遍一遍的遍歷,其實完全不必要,所以我們就稍微改進一下嘞~
我們每次遍歷一遍列表,同時記住交換元素的位置,若此次遍歷無元素交換,則直接停止。
下面給出這種改進的冒泡算法的代碼實現
# 改進的冒泡排序(python3實現)
def Imroved_Bubble_Sort(L):
lenght = len(L)
lastswap = lenght - 1
for i in range(lenght):
sign = lastswap
for j in range(lastswap):
if L[j] > L[j+1]:
temp = L[j]
L[j] = L[j+1]
L[j+1] = temp
lastswap = j
if sign == lastswap:
break
return L
2. 快速排序(Quick Sort)
2.0 性質總結
-
時間復雜度: (關於快排復雜度的詳細分析大家可以參考這篇博客,講的很清楚,這里我就不再耗費精力贅述嘞~)
- 平均情況:O(nlogn)
- 最好情況:O(nlong)
- 最壞情況:O(n^2)
其實不難理解,快排的最壞情況就已經退化為冒泡排序了!所以大家深入理解就會發現各個排序算法是相通的,學習時間久了你就會發現它們的內在聯系!是不是很神奇哈~
-
空間復雜度:
- 平均情況:O(logn)
- 最好情況:O(logn)
- 最壞情況:O(n)
-
穩定性:不穩定 (由於關鍵字的比較和交換是跳躍進行的,所以快速排序是一種不穩定的排序方法~)
2.1 基本思想
快速排序顧名思義,就是算法比較快嘞~當然,這個“快”是根據其平均情況O(nlogn)而言的,畢竟我們的世界里平均情況最普遍嘛,如果抬杠人士非要用各個算法的最壞情況來比教,那我也真沒啥辦法哈~快排的基本思想是:通過一趟排序將待排序列表分割成獨立的兩部分,其中一部分的所有元素都比另一部分小,然后再按此方法將獨立的兩部分分別繼續重復進行此操作,這個過程我們可以通過遞歸實現,從而達到最終將整個列表排序的目的。
2.2 具體步驟
- 從待排序列表(數組)中選擇一個元素作為基准(pivot),這里我們選擇最后一個元素元素
- 遍歷列表,將所有小於基准的元素放在其前面,這樣就可以將待排序列表分成兩部分了
- 遞歸地對每個部分進行1、2操作,這里遞歸結束的條件是序列的大小為0或1,此時遞歸結束,排序就已經完成了
2.3 代碼實現
# 快速排序(python3實現)
def Swap(L,i,j):
temp = L[i]
L[i] = L[j]
L[j] = temp
def Partition(L,left,right):
pivot = L[right]
tail = left - 1
# 將所有小於基准的數依次堆到前面
for i in range(left,right):
if L[i] <= pivot:
tail += 1
Swap(L,i,tail)
Swap(L,tail+1,right)
return tail + 1
def Quick_Sort(L,left,right):
if left >= right:
return
pivot = Partition(L,left,right)
QuickSort_wujingqiao(L,left,pivot-1)
QuickSort_wujingqiao(L,pivot+1,right)
return L
另附一個專屬於python的代碼(python的初衷就是用最少的代碼完成功能!),且看且珍惜:
def quick_sort(L):
if len(L) < 2: # 基線條件(停止遞歸的條件)
return L
else:
base_value = L[0] # 選擇基准值
less = [m for m in L[1:] if m < base_value] # 由所有小於基准值的元素組成的子數組(python獨有的切片,生成器等特性)
greater = [m for m in L[1:] if m >= base_value] # 由所有大於基准值的元素組成的子數組
return quick_sort(less) + [base_value] + quick_sort(greater)
非遞歸實現快排
# 快排非遞歸實現,即將需要排序的首尾下標存入棧中,依次出棧
class Solution:
def quickSortNoRecur(self, L, left, right):
stack = []
stack.append(left)
stack.append(right)
while stack:
r = stack.pop()
l = stack.pop()
index = self.partSort(L, l, r)
if l <= index - 1:
stack.append(l)
stack.append(index - 1)
if r >= index + 1:
stack.append(index + 1)
stack.append(r)
def partSort(self, L, left, right):
tail = left
for i in range(left, right):
if L[i] < L[right]:
self.swap(L, i, tail)
tail += 1
self.swap(L, tail, right)
return tail
def swap(self, L, i, j):
temp = L[i]
L[i] = L[j]
L[j] = temp
3. 直接選擇排序(Straight Select Sort)
3.0 性質總結
-
時間復雜度:
- 平均情況:O(n^2)
- 最好情況:O(n^2)
- 最壞情況:O(n^2)
由此可見這排序可以號稱最沒效率的排序了。。。
-
空間復雜度:O(1)
-
穩定性:不穩定
3.1 基本思想
選擇排序也很簡單直觀(其實這幾種排序算法都很簡單直觀~。~),它的基本思想是:先在待排序列表中找到最小(大)的元素,把它放在起始位置作為已排序序列;然后,再從剩余待排序序列中找到最小(大)的元素放在已排序序列的末尾,以此類推,直至完畢。
這個有點像暴力解決的意思,所以它的效率不高(最壞、最好、平均都一樣很差。。。)
3.2 具體步驟
- 初始狀態整個待排序序列為無序序列,有序序列為空
- 每次遍歷無序序列將最小元素交換到有序序列之后
- n-1趟遍歷后排序完成
3.3 代碼實現
# 直接選擇排序(python3實現)
def Straight_Select_Sort(L):
for i in range(len(L)):
min = i
for j in range(i,len(L)):
if L[j] < L[min]:
min = j
if min != i:
temp = L[i]
L[i] = L[min]
L[min] = temp
print(L)
return L
4. 堆排序(Heap Sort)
4.0 性質總結
-
時間復雜度:初始化堆O(n)因為循環內部需要比較的次數是O(n),交換元素后調整堆O(nlogn)因為做n-1次交換,每次交換后的調整為3(n-1)h, h=logn
- 平均情況:O(nlogn)
- 最好情況:O(nlogn)
- 最壞情況:O(nlogn)
可以看出,堆排序效率很好,所以大家一定要會熟練將堆排序應用到各種場景中~
-
空間復雜度:O(1)
-
穩定性:不穩定
4.1 基本思想
堆排序比之前幾種排序稍微難理解一些,理解此算法,大家需要具備一些關於堆這種數據結構的知識儲備。堆的結構相當於一個完全二叉樹,最大堆滿足下面的性質:父結點的值總大於它的孩子結點的值。
4.2 具體步驟
- 將待排序列表構造成一個最大堆,作為初始無序堆(即初始無序列表)
- 將堆頂元素(最大值)與堆尾元素互換
- 將該堆(無序區)尺寸縮小1,並對縮小后的堆重新調整為最大堆形式
- 重復上述步驟,直至堆(無序區)的尺寸變為1,此時排序完成
4.3 代碼實現
# 堆排序(python3實現)
def Swap(L,i,j):
temp = L[i]
L[i] = L[j]
L[j] = temp
def Heap_adj(L, i, size): # 從L[i]向下進行堆調整
left_child = 2 * i + 1 # 左孩子索引
right_child = 2 * i + 2 # 右孩子索引
max = i # 選出當前結點與其左右孩子結點中的最大值
if left_child < size and L[left_child] > L[max]:
max = left_child
if right_child < size and L[right_child] > L[max]:
max = right_child
if max != i:
Swap(L, i, max) # 將當前結點和最大子結點交換
Heap_adj(L, max, size) # 遞歸向下進行堆調整(每次子結點與父結點交換后,需將以子結點為根的完全二叉樹調整為大頂堆)
def Heap_Build(L): # 建堆、初始堆時,從下往上調整;交換堆頂與堆尾后,從上往下調整
heap_size = len(L)
for i in range(heap_size // 2 - 1, -1, -1): # 從每一個非葉結點開始向上進行堆調整
Heap_adj(L, i, heap_size)
def Heap_Sort(L):
heap_size = len(L) # 先建立最大堆
Heap_Build(L)
while heap_size > 1: # 直至堆尺寸縮減為1,排序結束
heap_size -= 1
Swap(L, 0, heap_size)
Heap_adj(L, 0, heap_size)
return L
5.0 直接插入排序(Straight Insertion Sort)
5.0 性質總結
-
時間復雜度:
- 平均情況:O(n^2)
- 最好情況:O(n)
- 最壞情況:O(n^2)
-
空間復雜度:O(1)
-
穩定性:穩定
5.1 基本思想
顧名思義,直接插入排序就是將未排序元素一個個的插入到已排序列表中,它的基本思想是:對於未排序元素,在已排序序列中從后向前掃描,找到相應位置把它插入進去;在從后向前掃描過程中,需要反復把已排序元素逐步向后挪位,為新元素提供插入空間。
5.2 具體步驟
- 從第一個元素開始,默認該元素已被排好序
- 取出下一個元素,在已經排序的元素序列中從后向前掃描
- 如果該元素(已排序)大於新元素,將該元素移到下一位置
- 重復步驟3,直到找到已排序的元素小於或者等於新元素的位置
- 將新元素插入到該位置后
- 重復步驟2~5
5.3 代碼實現
# 直接插入排序(python3實現)
def Insert_Sort(L):
for i in range(1,len(L)):
temp = L[i]
j = i-1
while L[j] > temp and j >= 0:
L[j+1] = L[j]
j -= 1
L[j+1] = temp
return L
6.0 希爾排序(Shell Sort)
6.0 性質總結
-
時間復雜度:
- 平均情況:O(nlogn~n^2)
- 最好情況:O(n^1.3)
- 最壞情況:O(n^2)
-
空間復雜度:O(1)
-
穩定性:不穩定
6.1 基本思想
我們還可以將希爾排序叫做縮小增量排序,它是插入排序的一種更高效的改進版本。它與簡單插入排序不同的是,它優先比較距離較遠的元素。希爾排序通過將比較的全部元素分為幾個區域來提升插入排序的性能。
它的基本思想是:將待排序列表按下標的一定增量分組(比如增量為2時,下標1,3,5,7為一組,下標2,4,6,8為另一組),各組內進行直接插入排序;隨着增量的越來越小,每組所包含的數字序列越來越多,當增量減至1時,整個序列被分成一個組,排序就完成了了。
6.2 具體步驟
- 選擇一個增量序列(定義增量的遞減狀況,直至最后為1)
- 按增量序列的個數k,對序列進行k趟排序
- 每趟排序,對各分組進行直接插入排序
6.3 代碼實現
# 希爾排序(python3實現)
def Shell_Sort(L):
n = len(L)
h = 0
while h <= n: # 生成初始增量
h = 3 * h + 1
while h >= 1: # 增量縮小到1時完成排序
for i in range(h, n): # 對每組進行直接插入排序
j = i - h
temp = L[i]
while j >= 0 and L[j] > temp:
L[j + h] = L[j]
j -= h
L[j + h] = temp
h = (h - 1) // 3 # 遞減增量
return L
7. 歸並排序(Merge Sort)
7.0 性質總結
-
時間復雜度:
- 平均情況:O(nlogn)
- 最好情況:O(nlogn)
- 最壞情況:O(nlogn)
-
空間復雜度:O(n)
-
穩定性:穩定
可以看出,歸並排序的效率很好(與堆排序一樣),只是它的空間復雜度較高(需要占用一定的內存空間)
7.1 基本思想
歸並排序的遞歸實現是算法設計中分治策略的典型應用,它的基本思想是:遞歸的將兩個已排序的序列合並成一個序列。
7.2 具體步驟
- 申請空間,其大小為兩個已經排序序列之和,該空間用來存放合並后的序列
- 設定兩個指針,最初位置分別為兩個已經排序序列的起始位置
- 比較兩個指針所指向的元素,選擇相對小的元素放入到合並空間,並移動指針到下一位置
7.3 代碼實現
# 歸並排序(python3實現)
def Merge(left, right):
l, r = 0, 0
result = []
while l < len(left) and r < len(right): # 比較兩個指針所指向的元素,選擇相對小的元素放入到合並空間,並移動指針到下一位置
if left[l] < right[r]:
result.append(left[l])
l += 1
else:
result.append(right[r])
r += 1
result += left[l:] # 若最后left列表剩余,則將其剩余部分加入到result后面
result += right[r:] # 若最后right列表剩余,則將其剩余部分加入到result后面
return result
def Merge_Sort(L):
if len(L) <= 1:
return L
mid = len(L) // 2 # 這里的//是python3中除以后取整的用法,大家不要以為我打錯了~
left = Merge_Sort(L[:mid])
right = Merge_Sort(L[mid:])
return Merge(left, right)
上面和大家分享的7種排序算法是屬於內部排序算法的,下面和大家分享3種外部排序算法~
8. 計數排序(Counting Sort)
8.0 性質總結
當輸入的元素是 n 個 0到 k 之間的整數時,時間復雜度是O(n+k),空間復雜度也是O(n+k),且計數排序是穩定的。
8.1 基本思想
計數排序的優勢在於在對一定范圍內的整數排序時,它的復雜度為Ο(n+k)(其中k是整數的范圍),快於任何排序算法(平均情況)!當然這是一種犧牲空間換取時間的做法,而且當O(k)>O(n*log(n))的時候其效率反而不如某些排序算法(堆排序、歸並排序)
它的基本思想是:對於給定的輸入序列中的每一個元素x,確定該序列中值小於x的元素的個數(此處並非比較各元素的大小,而是通過對元素值的計數和計數值的累加來確定)。一旦有了這個信息,就可以將x直接存放到最終的輸出序列的正確位置上。
例如,如果輸入序列中只有17個元素的值小於x的值,則x可以直接存放在輸出序列的第18個位置上。當然,如果有多個元素具有相同的值時,我們不能將這些元素放在輸出序列的同一個位置上,因此,上述方案還要作適當的修改。
這里需要注意兩點:
-
計數排序算法沒有用到元素間的比較,它利用元素的實際值來確定它們在輸出數組中的位置。因此,計數排序算法不是一個基於比較的排序算法,從而它的計算時間下界不再是Ω(nlogn)。
-
計數排序算法之所以能取得線性計算時間的上界是因為對元素的取值范圍作了一定限制,即k=O(n)。如果k=n2,n3,..,就得不到線性時間的上界。
8.2 具體步驟
- 統計列表L中每個值L[i]出現的次數,存入count[L[i]]
- 從前向后,使列表count中的每個值等於其與前一項相加,這樣列表count[L[i]]就變成了代表列表L中小於等於L[i]的元素個數
- 反向填充目標列表target:將列表元素L[i]放在列表targrt的第count[L[i]]個位置(下標為count[L[i]] - 1),每放一個元素就將count[L[i]]遞減
8.3 代碼實現
# 計數排序(python3實現)
def Counting_Sort(L):
n = len(L)
k = 100 # 這里暫時定基數為100,即排序[0,99]內的整數
count = [0 for i in range(k)]
target = [0 for i in range(n)]
for i in range(n): # 讓C[i]保存等於L[i]的元素個數
count[L[i]] += 1
for i in range(1, k): # 讓C[i]保存小於等於L[i]的元素個數,排序后元素L[i]就放在第C[i]個輸出位置上
count[i] = count[i] + count[i - 1]
for i in range(n-1, -1, -1): # 反向填充目標列表
count[L[i]] -= 1
target[count[L[i]]] = L[i]
for i in range(n): # 將目標列表拷貝回原列表
L[i] = target[i]
return L
9. 桶排序(Bucket Sort)
9.0 性質總結
-
時間復雜度:
- 平均情況:O(n)
- 最好情況:O(n)
- 最壞情況:O(nlogn)或O(n^2)
最壞情況是只有一個桶的情況,所以當然是取決於這個桶內你所采用的排序算法嘞~,在本片博客中,我在桶內用的是直接插入算法,所以最壞情況為O(n^2)。
-
空間復雜度:O(n + bucket_num)
-
穩定性:穩定
9.1 基本思想
桶排序又叫箱排序,它是計數排序的升級版,利用了函數的映射關系將數據分到有限數量的桶里,每個桶再分別進行排序(使用別的排序方法或者遞歸的繼續使用桶方法排序)。
9.2 具體步驟
- 設置定量列表(數組)當作空桶
- 遍歷待排序序列,將數據一個一個放到對應的桶里面
- 對每個非空桶進行排序(本片博客桶內采用直接插入排序,當然大家也可以用其他排序試一試)
- 把排好序的非空桶里的序列拼接在一起
9.3 代碼實現
# 桶排序(python3實現)
bucket_num = 5 # 這里暫定將待排序序列分為5個桶,大家也可以根據輸入動態的定義此變量
bound = [0 for i in range(bucket_num)] # 計數列表,存放桶的邊界信息
def Insertsort(L, left, right): # 直接插入排序(應用於桶內排序中)
for i in range(left + 1,right + 1):
get = L[i]
j = i - 1
while j >= left and L[j] > get:
L[j + 1] = L[j]
j -= 1
L[j + 1] = get
return L
def Map(x): # 映射函數,將整個待排序序列分割成不同的桶
return x//10
def Countingsort(L): # 計數排序(應用於桶邊界的確認)
n = len(L)
for i in range(n):
bound[Map(L[i])] += 1
for i in range(1, bucket_num):
bound[i] = bound[i] + bound[i - 1]
temp = [0 for i in range(n)]
for i in range(n-1, -1, -1):
bound[Map(L[i])] -= 1
temp[bound[Map(L[i])]] = L[i]
for i in range(n):
L[i] = temp[i]
return L
def Bucket_Sort(L):
n = len(L)
Countingsort(L) # 利用計數排序確定各個桶的邊界
for i in range(bucket_num): # 每個桶內采用直接插入排序
left = bound[i] # bound[i]為桶的左邊界
if i == bucket_num - 1:
right = n - 1 # bound[i + 1] - 1為桶的右邊界
else:
right = bound[i + 1] - 1
if left < right: # 對元素個數大於1個桶進行直接插入排序
Insertsort(L, left, right)
return L
10. 基數排序(Radix Sort)
10.0 性質總結
-
時間復雜度:
- 平均情況:O(n*bucket_num)
- 最好情況:O(n*bucket_num)
- 最壞情況:O(n*bucket_num)
-
空間復雜度:O(n*bucket_num)
-
穩定性:穩定
10.1 基本思想
基數排序的基本思想是:將所有待比較正整數統一為同樣的數位長度,數位較短的數前面補零。然后,從最低位開始進行基數為10的計數排序,一直到最高位計數排序完后,數列就變成一個有序序列(利用了計數排序的穩定性)。由此可知,基數排序、桶排序、計數排序三者關系很緊密,尤其計數排序!
10.2 具體步驟
- 確定位數基數
- 從低位到高位,每位看作一個桶,每個桶內進行計數排序
- 最高位桶排序完成后,整個排序便完成了
10.3 代碼實現
# 基數排序(python3實現)
bucket_num = 3 # 這里暫定待排序元素為三位數及以下,大家也可以動態的定義此變量
k = 10 # 基數為10,即每一位數字范圍為[0,9]
def Get_Digit(x, d): # 獲取元素x的第d位數字(由低到高)
redix = [1, 1, 10, 100] # 最大為三位數,所以這里只要到百位就夠了
return (x // redix[d]) % 10
def Counting_Sort(L, d): # 計數排序,應用於每位對應的桶內排序
n = len(L)
count = [0 for i in range(k)]
temp = [0 for i in range(n)]
for i in range(n):
count[Get_Digit(L[i], d)] += 1
for i in range(k):
count[i] = count[i] + count[i - 1]
for i in range(n-1, -1, -1):
count[Get_Digit(L[i], d)] -= 1
temp[count[Get_Digit(L[i], d)]] = L[i]
for i in range(n):
L[i] = temp[i]
return L
def Radix_Sort(L):
for d in range(bucket_num + 1): # 從低位到高位,依次依據第d位數字對L進行計數排序
Counting_Sort(L, d)
return L