引言
所謂排序,就是使一串記錄,按照其中的某個或某些關鍵字的大小,遞增或遞減的排列起來的操作。排序算法,就是如何使得記錄按照要求排列的方法。排序算法在很多領域得到相當地重視,尤其是在大量數據的處理方面。一個優秀的算法可以節省大量的資源。在各個領域中考慮到數據的各種限制和規范,要得到一個符合實際的優秀算法,得經過大量的推理和分析。
兩年前,我曾在博客園發布過一篇《十大經典排序算法最強總結(含JAVA代碼實現)》博文,簡要介紹了比較經典的十大排序算法,不過在之前的博文中,僅給出了Java版本的代碼實現,並且有一些細節上的錯誤。所以,今天重新寫一篇文章,深入了解下十大經典排序算法的原理及實現。
簡介
排序算法可以分為內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部的排序記錄,在排序過程中需要訪問外存。常見的內部排序算法有:插入排序、希爾排序、選擇排序、冒泡排序、歸並排序、快速排序、堆排序、基數排序等,本文只講解內部排序算法。用一張圖概括:
圖片名詞解釋:
- n:數據規模
- k:“桶”的個數
- In-place:占用常數內存,不占用額外內存
- Out-place:占用額外內存
術語說明
- 穩定:如果A原本在B前面,而A=B,排序之后A仍然在B的前面。
- 不穩定:如果A原本在B的前面,而A=B,排序之后A可能會出現在B的后面。
- 內排序:所有排序操作都在內存中完成。
- 外排序:由於數據太大,因此把數據放在磁盤中,而排序通過磁盤和內存的數據傳輸才能進行。
- 時間復雜度: 定性描述一個算法執行所耗費的時間。
- 空間復雜度:定性描述一個算法執行所需內存的大小。
算法分類
十種常見排序算法可以分類兩大類別:比較類排序和非比較類排序。

常見的快速排序、歸並排序、堆排序以及冒泡排序等都屬於比較類排序算法。比較類排序是通過比較來決定元素間的相對次序,由於其時間復雜度不能突破O(nlogn)
,因此也稱為非線性時間比較類排序。在冒泡排序之類的排序中,問題規模為n
,又因為需要比較n
次,所以平均時間復雜度為O(n²)
。在歸並排序、快速排序之類的排序中,問題規模通過分治法消減為logn
次,所以時間復雜度平均O(nlogn)
。
比較類排序的優勢是,適用於各種規模的數據,也不在乎數據的分布,都能進行排序。可以說,比較排序適用於一切需要排序的情況。
而計數排序、基數排序、桶排序則屬於非比較類排序算法。非比較排序不通過比較來決定元素間的相對次序,而是通過確定每個元素之前,應該有多少個元素來排序。由於它可以突破基於比較排序的時間下界,以線性時間運行,因此稱為線性時間非比較類排序。 非比較排序只要確定每個元素之前的已有的元素個數即可,所有一次遍歷即可解決。算法時間復雜度O(n)
。
非比較排序時間復雜度底,但由於非比較排序需要占用空間來確定唯一位置。所以對數據規模和數據分布有一定的要求。
冒泡排序(Bubble Sort)
冒泡排序是一種簡單的排序算法。它重復地遍歷要排序的序列,依次比較兩個元素,如果它們的順序錯誤就把它們交換過來。遍歷序列的工作是重復地進行直到沒有再需要交換為止,此時說明該序列已經排序完成。這個算法的名字由來是因為越小的元素會經由交換慢慢“浮”到數列的頂端。
算法步驟
- 比較相鄰的元素。如果第一個比第二個大,就交換它們兩個;
- 對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最后一對,這樣在最后的元素應該會是最大的數;
- 針對所有的元素重復以上的步驟,除了最后一個;
- 重復步驟1~3,直到排序完成。
圖解算法
代碼實現
JAVA
/** * 冒泡排序 * @param arr * @return arr */
public static int[] BubbleSort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
// Set a flag, if true, that means the loop has not been swapped,
// that is, the sequence has been ordered, the sorting has been completed.
boolean flag = true;
for (int j = 0; j < arr.length - i; j++) {
if (arr[j] > arr[j + 1]) {
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
// Change flag
flag = false;
}
}
if (flag) {
break;
}
}
return arr;
}
Python
def BubbleSort(arr):
for i in range(len(arr)):
is_sorted = True
for j in range(len(arr)-i-1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
is_sorted = False
if is_sorted:
break
return arr
此處對代碼做了一個小優化,加入了is_sorted
Flag,目的是將算法的最佳時間復雜度優化為O(n)
,即當原輸入序列就是排序好的情況下,該算法的時間復雜度就是O(n)
。
選擇排序(Selection Sort)
選擇排序是一種簡單直觀的排序算法,無論什么數據進去都是O(n²)
的時間復雜度。所以用到它的時候,數據規模越小越好。唯一的好處可能就是不占用額外的內存空間了吧。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再從剩余未排序元素中繼續尋找最小(大)元素,然后放到已排序序列的末尾。以此類推,直到所有元素均排序完畢。
算法步驟
- 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
- 再從剩余未排序元素中繼續尋找最小(大)元素,然后放到已排序序列的末尾。
- 重復第2步,直到所有元素均排序完畢。
圖解算法
代碼實現
JAVA
/** * 選擇排序 * @param arr * @return arr */
public static int[] SelectionSort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
if (minIndex != i) {
int tmp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = tmp;
}
}
return arr;
}
Python
def SelectionSort(arr):
for i in range(len(arr)-1):
# 記錄最小值的索引
minIndex = i
for j in range(i+1, len(arr)):
if arr[j] < arr[minIndex]:
minIndex = j
if minIndex != i:
arr[i], arr[minIndex] = arr[minIndex], arr[i]
return arr
插入排序(Insertion Sort)
插入排序是一種簡單直觀的排序算法。它的工作原理是通過構建有序序列,對於未排序數據,在已排序序列中從后向前掃描,找到相應位置並插入。插入排序在實現上,通常采用in-place排序(即只需用到O(1)
的額外空間的排序),因而在從后向前掃描過程中,需要反復把已排序元素逐步向后挪位,為最新元素提供插入空間。
插入排序的代碼實現雖然沒有冒泡排序和選擇排序那么簡單粗暴,但它的原理應該是最容易理解的了,因為只要打過撲克牌的人都應該能夠秒懂。插入排序是一種最簡單直觀的排序算法,它的工作原理是通過構建有序序列,對於未排序數據,在已排序序列中從后向前掃描,找到相應位置並插入。
插入排序和冒泡排序一樣,也有一種優化算法,叫做拆半插入。
算法步驟
- 從第一個元素開始,該元素可以認為已經被排序;
- 取出下一個元素,在已經排序的元素序列中從后向前掃描;
- 如果該元素(已排序)大於新元素,將該元素移到下一位置;
- 重復步驟3,直到找到已排序的元素小於或者等於新元素的位置;
- 將新元素插入到該位置后;
- 重復步驟2~5。
圖解算法
代碼實現
JAVA
/** * 插入排序 * @param arr * @return arr */
public static int[] InsertionSort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
int preIndex = i - 1;
int current = arr[i];
while (preIndex >= 0 && current < arr[preIndex]) {
arr[preIndex + 1] = arr[preIndex];
preIndex -= 1;
}
arr[preIndex + 1] = current;
}
return arr;
}
Python
def InsertionSort(arr):
for i in range(1, len(arr)):
preIndex = i-1
current = arr[i]
while preIndex >= 0 and current < arr[preIndex]:
arr[preIndex+1] = arr[preIndex]
preIndex -= 1
arr[preIndex+1] = current
return arr
希爾排序(Shell Sort)
希爾排序是希爾(Donald Shell)於1959年提出的一種排序算法。希爾排序也是一種插入排序,它是簡單插入排序經過改進之后的一個更高效的版本,也稱為遞減增量排序算法,同時該算法是沖破O(n²)
的第一批算法之一。
希爾排序的基本思想是:先將整個待排序的記錄序列分割成為若干子序列分別進行直接插入排序,待整個序列中的記錄“基本有序”時,再對全體記錄進行依次直接插入排序。
算法步驟
我們來看下希爾排序的基本步驟,在此我們選擇增量gap=length/2
,縮小增量繼續以gap = gap/2
的方式,這種增量選擇我們可以用一個序列來表示,{n/2, (n/2)/2, ..., 1}
,稱為增量序列。希爾排序的增量序列的選擇與證明是個數學難題,我們選擇的這個增量序列是比較常用的,也是希爾建議的增量,稱為希爾增量,但其實這個增量序列不是最優的。此處我們做示例使用希爾增量。
先將整個待排序的記錄序列分割成為若干子序列分別進行直接插入排序,具體算法描述:
- 選擇一個增量序列
{t1, t2, …, tk}
,其中(ti>tj, i<j, tk=1)
; - 按增量序列個數k,對序列進行k趟排序;
- 每趟排序,根據對應的增量
t
,將待排序列分割成若干長度為m
的子序列,分別對各子表進行直接插入排序。僅增量因子為1時,整個序列作為一個表來處理,表長度即為整個序列的長度。
圖解算法

代碼實現
JAVA
/** * 希爾排序 * * @param arr * @return arr */
public static int[] ShellSort(int[] arr) {
int n = arr.length;
int gap = n / 2;
while (gap > 0) {
for (int i = gap; i < n; i++) {
int current = arr[i];
int preIndex = i - gap;
// Insertion sort
while (preIndex >= 0 && arr[preIndex] > current) {
arr[preIndex + gap] = arr[preIndex];
preIndex -= gap;
}
arr[preIndex + gap] = current;
}
gap /= 2;
}
return arr;
}
Python
def ShellSort(arr):
n = len(arr)
# 初始步長
gap = n // 2
while gap > 0:
for i in range(gap, n):
# 根據步長進行插入排序
current = arr[i]
preIndex = i - gap
# 插入排序
while preIndex >= 0 and arr[preIndex] > current:
arr[preIndex+gap] = arr[preIndex]
preIndex -= gap
arr[preIndex+gap] = current
# 得到新的步長
gap = gap // 2
return arr
歸並排序(Merge Sort)
歸並排序是建立在歸並操作上的一種有效的排序算法。該算法是采用分治法(Divide and Conquer)的一個非常典型的應用。歸並排序是一種穩定的排序方法。將已有序的子序列合並,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合並成一個有序表,稱為2-路歸並。
和選擇排序一樣,歸並排序的性能不受輸入數據的影響,但表現比選擇排序好的多,因為始終都是O(nlogn)
的時間復雜度。代價是需要額外的內存空間。
算法步驟
歸並排序算法是一個遞歸過程,邊界條件為當輸入序列僅有一個元素時,直接返回,具體過程如下:
- 如果輸入內只有一個元素,則直接返回,否則將長度為
n
的輸入序列分成兩個長度為n/2
的子序列; - 分別對這兩個子序列進行歸並排序,使子序列變為有序狀態;
- 設定兩個指針,分別指向兩個已經排序子序列的起始位置;
- 比較兩個指針所指向的元素,選擇相對小的元素放入到合並空間(用於存放排序結果),並移動指針到下一位置;
- 重復步驟3 ~4直到某一指針達到序列尾;
- 將另一序列剩下的所有元素直接復制到合並序列尾。
圖解算法
代碼實現
JAVA
/** * 歸並排序 * * @param arr * @return arr */
public static int[] MergeSort(int[] arr) {
if (arr.length <= 1) {
return arr;
}
int middle = arr.length / 2;
int[] arr_1 = Arrays.copyOfRange(arr, 0, middle);
int[] arr_2 = Arrays.copyOfRange(arr, middle, arr.length);
return Merge(MergeSort(arr_1), MergeSort(arr_2));
}
/** * Merge two sorted arrays * * @param arr_1 * @param arr_2 * @return sorted_arr */
public static int[] Merge(int[] arr_1, int[] arr_2) {
int[] sorted_arr = new int[arr_1.length + arr_2.length];
int idx = 0, idx_1 = 0, idx_2 = 0;
while (idx_1 < arr_1.length && idx_2 < arr_2.length) {
if (arr_1[idx_1] < arr_2[idx_2]) {
sorted_arr[idx] = arr_1[idx_1];
idx_1 += 1;
} else {
sorted_arr[idx] = arr_2[idx_2];
idx_2 += 1;
}
idx += 1;
}
if (idx_1 < arr_1.length) {
while (idx_1 < arr_1.length) {
sorted_arr[idx] = arr_1[idx_1];
idx_1 += 1;
idx += 1;
}
} else {
while (idx_2 < arr_2.length) {
sorted_arr[idx] = arr_2[idx_2];
idx_2 += 1;
idx += 1;
}
}
return sorted_arr;
}
Python
def Merge(arr_1, arr_2):
sorted_arr = []
idx_1 = 0
idx_2 = 0
while idx_1 < len(arr_1) and idx_2 < len(arr_2):
if arr_1[idx_1] < arr_2[idx_2]:
sorted_arr.append(arr_1[idx_1])
idx_1 += 1
else:
sorted_arr.append(arr_2[idx_2])
idx_2 += 1
if idx_1 < len(arr_1):
while idx_1 < len(arr_1):
sorted_arr.append(arr_1[idx_1])
idx_1 += 1
else:
while idx_2 < len(arr_2):
sorted_arr.append(arr_2[idx_2])
idx_2 += 1
return sorted_arr
def MergeSort(arr):
if len(arr) <= 1:
return arr
mid = int(len(arr)/2)
arr_1, arr_2 = arr[:mid], arr[mid:]
return Merge(MergeSort(arr_1), MergeSort(arr_2))
算法分析
穩定性:穩定
時間復雜度:最佳:O(nlogn)
最差:O(nlogn)
平均:O(nlogn)
空間復雜度:O(n)
快速排序(Quick Sort)
快速排序用到了分治思想,同樣的還有歸並排序。乍看起來快速排序和歸並排序非常相似,都是將問題變小,先排序子串,最后合並。不同的是快速排序在划分子問題的時候經過多一步處理,將划分的兩組數據划分為一大一小,這樣在最后合並的時候就不必像歸並排序那樣再進行比較。但也正因為如此,划分的不定性使得快速排序的時間復雜度並不穩定。
快速排序的基本思想:通過一趟排序將待排序列分隔成獨立的兩部分,其中一部分記錄的元素均比另一部分的元素小,則可分別對這兩部分子序列繼續進行排序,以達到整個序列有序。
算法步驟
快速排序使用分治法(Divide and conquer)策略來把一個序列分為較小和較大的2個子序列,然后遞回地排序兩個子序列。具體算法描述如下:
- 從序列中隨機挑出一個元素,做為 “基准”(
pivot
); - 重新排列序列,將所有比基准值小的元素擺放在基准前面,所有比基准值大的擺在基准的后面(相同的數可以到任一邊)。在這個操作結束之后,該基准就處於數列的中間位置。這個稱為分區(partition)操作;
- 遞歸地把小於基准值元素的子序列和大於基准值元素的子序列進行快速排序。
圖解算法
代碼實現
JAVA
/** * Swap the two elements of an array * @param array * @param i * @param j */
private static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
/** * Partition function * @param arr * @param left * @param right * @return small_idx */
private static int Partition(int[] arr, int left, int right) {
if (left == right) {
return left;
}
// random pivot
int pivot = (int) (left + Math.random() * (right - left + 1));
swap(arr, pivot, right);
int small_idx = left;
for (int i = small_idx; i < right; i++) {
if (arr[i] < arr[right]) {
swap(arr, i, small_idx);
small_idx++;
}
}
swap(arr, small_idx, right);
return small_idx;
}
/** * Quick sort function * @param arr * @param left * @param right * @return arr */
public static int[] QuickSort(int[] arr, int left, int right) {
if (left < right) {
int pivotIndex = Partition(arr, left, right);
sort(arr, left, pivotIndex - 1);
sort(arr, pivotIndex + 1, right);
}
return arr;
}
Python
def QuickSort(arr, left=None, right=None):
left = 0 if left == None else left
right = len(arr)-1 if right == None else right
if left < right:
pivotIndex = Partition(arr, left, right)
QuickSort(arr, left, pivotIndex-1)
QuickSort(arr, pivotIndex+1, right)
return arr
def Partition(arr, left, right):
if left == right:
return left
# 隨機pivot
swap(arr, random.randint(left, right), right)
pivot = right
small_idx = left
for i in range(small_idx, pivot):
if arr[i] < arr[pivot]:
swap(arr, i, small_idx)
small_idx += 1
swap(arr, small_idx, pivot)
return small_idx
def swap(arr, i, j):
arr[i], arr[j] = arr[j], arr[i]
算法分析
穩定性:不穩定
時間復雜度:最佳:O(nlogn)
最差:O(nlogn)
平均:O(nlogn)
空間復雜度:O(nlogn)
堆排序(Heap Sort)
堆排序是指利用堆這種數據結構所設計的一種排序算法。堆是一個近似完全二叉樹的結構,並同時滿足堆的性質:即子結點的值總是小於(或者大於)它的父節點。
算法步驟
- 將初始待排序列
(R1, R2, ……, Rn)
構建成大頂堆,此堆為初始的無序區; - 將堆頂元素
R[1]
與最后一個元素R[n]
交換,此時得到新的無序區(R1, R2, ……, Rn-1)
和新的有序區(Rn),且滿足R[1, 2, ……, n-1]<=R[n]
; - 由於交換后新的堆頂
R[1]
可能違反堆的性質,因此需要對當前無序區(R1, R2, ……, Rn-1)
調整為新堆,然后再次將R[1]與無序區最后一個元素交換,得到新的無序區(R1, R2, ……, Rn-2)
和新的有序區(Rn-1, Rn)
。不斷重復此過程直到有序區的元素個數為n-1
,則整個排序過程完成。
圖解算法
代碼實現
JAVA
// Global variable that records the length of an array;
static int heapLen;
/** * Swap the two elements of an array * @param arr * @param i * @param j */
private static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
/** * Build Max Heap * @param arr */
private static void buildMaxHeap(int[] arr) {
for (int i = arr.length / 2 - 1; i >= 0; i--) {
heapify(arr, i);
}
}
/** * Adjust it to the maximum heap * @param arr * @param i */
private static void heapify(int[] arr, int i) {
int left = 2 * i + 1;
int right = 2 * i + 2;
int largest = i;
if (right < heapLen && arr[right] > arr[largest]) {
largest = right;
}
if (left < heapLen && arr[left] > arr[largest]) {
largest = left;
}
if (largest != i) {
swap(arr, largest, i);
heapify(arr, largest);
}
}
/** * Heap Sort * @param arr * @return */
public static int[] HeapSort(int[] arr) {
// index at the end of the heap
heapLen = arr.length;
// build MaxHeap
buildMaxHeap(arr);
for (int i = arr.length - 1; i > 0; i--) {
// Move the top of the heap to the tail of the heap in turn
swap(arr, 0, i);
heapLen -= 1;
heapify(arr, 0);
}
return arr;
}
Python
def HeapSort(arr):
global heapLen
# 用於標記堆尾部的索引
heapLen = len(arr)
# 構建最大堆
buildMaxHeap(arr)
for i in range(len(arr)-1, 0, -1):
# 依次將堆頂移至堆尾
swap(arr, 0, i)
heapLen -= 1
heapify(arr, 0)
return arr
def buildMaxHeap(arr):
for i in range(len(arr)//2-1, -1, -1):
heapify(arr, i)
def heapify(arr, i):
left = 2*i + 1
right = 2*i + 2
largest = i
if right < heapLen and arr[right] > arr[largest]:
largest = right
if left < heapLen and arr[left] > arr[largest]:
largest = left
if largest != i:
swap(arr, largest, i)
heapify(arr, largest)
def swap(arr, i, j):
arr[i], arr[j] = arr[j], arr[i]
算法分析
穩定性:不穩定
時間復雜度:最佳:O(nlogn)
最差:O(nlogn)
平均:O(nlogn)
空間復雜度:O(1)
計數排序(Counting Sort)
計數排序的核心在於將輸入的數據值轉化為鍵存儲在額外開辟的數組空間中。 作為一種線性時間復雜度的排序,計數排序要求輸入的數據必須是有確定范圍的整數。
計數排序(Counting sort)是一種穩定的排序算法。計數排序使用一個額外的數組C
,其中第i
個元素是待排序數組A
中值等於i
的元素的個數。然后根據數組C
來將A
中的元素排到正確的位置。它只能對整數進行排序。
算法步驟
- 找出數組中的最大值
max
、最小值min
; - 創建一個新數組
C
,其長度是max-min+1
,其元素默認值都為0; - 遍歷原數組
A
中的元素A[i]
,以A[i]-min
作為C
數組的索引,以A[i]
的值在A
中元素出現次數作為C[A[i]-min]
的值; - 對
C
數組變形,新元素的值是該元素與前一個元素值的和,即當i>1
時C[i] = C[i] + C[i-1]
; - 創建結果數組
R
,長度和原始數組一樣。 - 從后向前遍歷原始數組
A
中的元素A[i]
,使用A[i]
減去最小值min
作為索引,在計數數組C
中找到對應的值C[A[i]-min]
,C[A[i]-min]-1
就是A[i]
在結果數組R
中的位置,做完上述這些操作,將count[A[i]-min]
減小1。
圖解算法
代碼實現
JAVA
/** * Gets the maximum and minimum values in the array * * @param arr * @return */
private static int[] getMinAndMax(int[] arr) {
int maxValue = arr[0];
int minValue = arr[0];
for (int i = 0; i < arr.length; i++) {
if (arr[i] > maxValue) {
maxValue = arr[i];
} else if (arr[i] < minValue) {
minValue = arr[i];
}
}
return new int[] { minValue, maxValue };
}
/** * Counting Sort * * @param arr * @return */
public static int[] CountingSort(int[] arr) {
if (arr.length < 2) {
return arr;
}
int[] extremum = getMinAndMax(arr);
int minValue = extremum[0];
int maxValue = extremum[1];
int[] countArr = new int[maxValue - minValue + 1];
int[] result = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
countArr[arr[i] - minValue] += 1;
}
for (int i = 1; i < countArr.length; i++) {
countArr[i] += countArr[i - 1];
}
for (int i = arr.length - 1; i >= 0; i--) {
int idx = countArr[arr[i] - minValue] - 1;
result[idx] = arr[i];
countArr[arr[i] - minValue] -= 1;
}
return result;
}
Python
def CountingSort(arr):
if arr.size < 2:
return arr
minValue, maxValue = getMinAndMax(arr)
countArr = [0]*(maxValue-minValue+1)
resultArr = [0]*len(arr)
for i in range(len(arr)):
countArr[arr[i]-minValue] += 1
for i in range(1, len(countArr)):
countArr[i] += countArr[i-1]
for i in range(len(arr)-1, -1, -1):
idx = countArr[arr[i]-minValue]-1
resultArr[idx] = arr[i]
countArr[arr[i]-minValue] -= 1
return resultArr
def getMinAndMax(arr):
maxValue = arr[0]
minValue = arr[0]
for i in range(len(arr)):
if arr[i] > maxValue:
maxValue = arr[i]
elif arr[i] < minValue:
minValue = arr[i]
return minValue, maxValue
算法分析
當輸入的元素是n
個0
到k
之間的整數時,它的運行時間是 O(n+k)
。計數排序不是比較排序,排序的速度快於任何比較排序算法。由於用來計數的數組C
的長度取決於待排序數組中數據的范圍(等於待排序數組的最大值與最小值的差加上1),這使得計數排序對於數據范圍很大的數組,需要大量額外內存空間。
穩定性:穩定
時間復雜度:最佳:O(n+k)
最差:O(n+k)
平均:O(n+k)
空間復雜度:O(k)
桶排序(Bucket Sort)
桶排序是計數排序的升級版。它利用了函數的映射關系,高效與否的關鍵就在於這個映射函數的確定。為了使桶排序更加高效,我們需要做到這兩點:
- 在額外空間充足的情況下,盡量增大桶的數量
- 使用的映射函數能夠將輸入的 N 個數據均勻的分配到 K 個桶中
桶排序的工作的原理:假設輸入數據服從均勻分布,將數據分到有限數量的桶里,每個桶再分別排序(有可能再使用別的排序算法或是以遞歸方式繼續使用桶排序進行。
算法步驟
- 設置一個BucketSize,作為每個桶所能放置多少個不同數值;
- 遍歷輸入數據,並且把數據依次映射到對應的桶里去;
- 對每個非空的桶進行排序,可以使用其它排序方法,也可以遞歸使用桶排序;
- 從非空桶里把排好序的數據拼接起來。
圖解算法
代碼實現
JAVA
/** * Gets the maximum and minimum values in the array * @param arr * @return */
private static int[] getMinAndMax(List<Integer> arr) {
int maxValue = arr.get(0);
int minValue = arr.get(0);
for (int i : arr) {
if (i > maxValue) {
maxValue = i;
} else if (i < minValue) {
minValue = i;
}
}
return new int[] { minValue, maxValue };
}
/** * Bucket Sort * @param arr * @return */
public static List<Integer> BucketSort(List<Integer> arr, int bucket_size) {
if (arr.size() < 2 || bucket_size == 0) {
return arr;
}
int[] extremum = getMinAndMax(arr);
int minValue = extremum[0];
int maxValue = extremum[1];
int bucket_cnt = (maxValue - minValue) / bucket_size + 1;
List<List<Integer>> buckets = new ArrayList<>();
for (int i = 0; i < bucket_cnt; i++) {
buckets.add(new ArrayList<Integer>());
}
for (int element : arr) {
int idx = (element - minValue) / bucket_size;
buckets.get(idx).add(element);
}
for (int i = 0; i < buckets.size(); i++) {
if (buckets.get(i).size() > 1) {
buckets.set(i, sort(buckets.get(i), bucket_size / 2));
}
}
ArrayList<Integer> result = new ArrayList<>();
for (List<Integer> bucket : buckets) {
for (int element : bucket) {
result.add(element);
}
}
return result;
}
Python
def BucketSort(arr, bucket_size):
if len(arr) < 2 or bucket_size == 0:
return arr
minValue, maxValue = getMinAndMax(arr)
bucket_cnt = (maxValue-minValue)//bucket_size+1
bucket = [[] for i in range(bucket_cnt)]
for i in range(len(arr)):
idx = (arr[i]-minValue) // bucket_size
bucket[idx].append(arr[i])
for i in range(len(bucket)):
if len(bucket[i]) > 1:
# 遞歸使用桶排序,也可使用其它排序算法
bucket[i] = BucketSort(bucket[i], bucket_size//2)
result = []
for i in range(len(bucket)):
for j in range(len(bucket[i])):
result.append(bucket[i][j])
return result
def getMinAndMax(arr):
maxValue = arr[0]
minValue = arr[0]
for i in range(len(arr)):
if arr[i] > maxValue:
maxValue = arr[i]
elif arr[i] < minValue:
minValue = arr[i]
return minValue, maxValue
算法分析
穩定性:穩定
時間復雜度:最佳:O(n+k)
最差:O(n²)
平均:O(n+k)
空間復雜度:O(k)
基數排序(Radix Sort)
基數排序也是非比較的排序算法,對元素中的每一位數字進行排序,從最低位開始排序,復雜度為O(n×k)
,n
為數組長度,k
為數組中元素的最大的位數;
基數排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次類推,直到最高位。有時候有些屬性是有優先級順序的,先按低優先級排序,再按高優先級排序。最后的次序就是高優先級高的在前,高優先級相同的低優先級高的在前。基數排序基於分別排序,分別收集,所以是穩定的。
算法步驟
- 取得數組中的最大數,並取得位數,即為迭代次數
N
(例如:數組中最大數值為1000,則N=4
); A
為原始數組,從最低位開始取每個位組成radix
數組;- 對
radix
進行計數排序(利用計數排序適用於小范圍數的特點); - 將
radix
依次賦值給原數組; - 重復2~4步驟
N
次
圖解算法
代碼實現
JAVA
/** * Radix Sort * * @param arr * @return */
public static int[] RadixSort(int[] arr) {
if (arr.length < 2) {
return arr;
}
int N = 1;
int maxValue = arr[0];
for (int element : arr) {
if (element > maxValue) {
maxValue = element;
}
}
while (maxValue / 10 != 0) {
maxValue = maxValue / 10;
N += 1;
}
for (int i = 0; i < N; i++) {
List<List<Integer>> radix = new ArrayList<>();
for (int k = 0; k < 10; k++) {
radix.add(new ArrayList<Integer>());
}
for (int element : arr) {
int idx = (element / (int) Math.pow(10, i)) % 10;
radix.get(idx).add(element);
}
int idx = 0;
for (List<Integer> l : radix) {
for (int n : l) {
arr[idx++] = n;
}
}
}
return arr;
}
Python
def RadixSort(arr):
if len(arr) < 2:
return arr
maxValue = arr[0]
N = 1
for i in range(len(arr)):
if arr[i] > maxValue:
maxValue = arr[i]
while maxValue // 10 != 0:
maxValue = maxValue//10
N += 1
for n in range(N):
radix = [[] for i in range(10)]
for i in range(len(arr)):
idx = arr[i]//(10**n) % 10
radix[idx].append(arr[i])
idx = 0
for j in range(len(radix)):
for k in range(len(radix[j])):
arr[idx] = radix[j][k]
idx += 1
return arr
算法分析
穩定性:穩定
時間復雜度:最佳:O(n×k)
最差:O(n×k)
平均:O(n×k)
空間復雜度:O(n+k)
基數排序 vs 計數排序 vs 桶排序
這三種排序算法都利用了桶的概念,但對桶的使用方法上有明顯差異:
- 基數排序:根據鍵值的每位數字來分配桶
- 計數排序:每個桶只存儲單一鍵值
- 桶排序:每個桶存儲一定范圍的數值
參考文章
https://www.cnblogs.com/guoyaohua/p/8600214.html
出處: http://www.guoyaohua.com
微信:guoyaohua167
郵箱:guo.yaohua@foxmail.com
本文版權歸作者和博客園所有,歡迎轉載,轉載請標明出處。
【如果你覺得本文還不錯,對你的學習帶來了些許幫助,請幫忙點擊右下角的推薦】