This commit is contained in:
krahets
2024-04-06 03:02:20 +08:00
parent 0a9daa8b9f
commit 8d37c215c8
148 changed files with 70398 additions and 408 deletions
+623
View File
@@ -0,0 +1,623 @@
---
comments: true
---
# 11.3   泡沫排序
<u>泡沫排序(bubble sort</u>透過連續地比較與交換相鄰元素實現排序。這個過程就像氣泡從底部升到頂部一樣,因此得名泡沫排序。
如圖 11-4 所示,冒泡過程可以利用元素交換操作來模擬:從陣列最左端開始向右走訪,依次比較相鄰元素大小,如果“左元素 > 右元素”就交換二者。走訪完成後,最大的元素會被移動到陣列的最右端。
=== "<1>"
![利用元素交換操作模擬冒泡](bubble_sort.assets/bubble_operation_step1.png){ class="animation-figure" }
=== "<2>"
![bubble_operation_step2](bubble_sort.assets/bubble_operation_step2.png){ class="animation-figure" }
=== "<3>"
![bubble_operation_step3](bubble_sort.assets/bubble_operation_step3.png){ class="animation-figure" }
=== "<4>"
![bubble_operation_step4](bubble_sort.assets/bubble_operation_step4.png){ class="animation-figure" }
=== "<5>"
![bubble_operation_step5](bubble_sort.assets/bubble_operation_step5.png){ class="animation-figure" }
=== "<6>"
![bubble_operation_step6](bubble_sort.assets/bubble_operation_step6.png){ class="animation-figure" }
=== "<7>"
![bubble_operation_step7](bubble_sort.assets/bubble_operation_step7.png){ class="animation-figure" }
<p align="center"> 圖 11-4 &nbsp; 利用元素交換操作模擬冒泡 </p>
## 11.3.1 &nbsp; 演算法流程
設陣列的長度為 $n$ ,泡沫排序的步驟如圖 11-5 所示。
1. 首先,對 $n$ 個元素執行“冒泡”,**將陣列的最大元素交換至正確位置**。
2. 接下來,對剩餘 $n - 1$ 個元素執行“冒泡”,**將第二大元素交換至正確位置**。
3. 以此類推,經過 $n - 1$ 輪“冒泡”後,**前 $n - 1$ 大的元素都被交換至正確位置**。
4. 僅剩的一個元素必定是最小元素,無須排序,因此陣列排序完成。
![泡沫排序流程](bubble_sort.assets/bubble_sort_overview.png){ class="animation-figure" }
<p align="center"> 圖 11-5 &nbsp; 泡沫排序流程 </p>
示例程式碼如下:
=== "Python"
```python title="bubble_sort.py"
def bubble_sort(nums: list[int]):
"""泡沫排序"""
n = len(nums)
# 外迴圈:未排序區間為 [0, i]
for i in range(n - 1, 0, -1):
# 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for j in range(i):
if nums[j] > nums[j + 1]:
# 交換 nums[j] 與 nums[j + 1]
nums[j], nums[j + 1] = nums[j + 1], nums[j]
```
=== "C++"
```cpp title="bubble_sort.cpp"
/* 泡沫排序 */
void bubbleSort(vector<int> &nums) {
// 外迴圈:未排序區間為 [0, i]
for (int i = nums.size() - 1; i > 0; i--) {
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (int j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// 交換 nums[j] 與 nums[j + 1]
// 這裡使用了 std::swap() 函式
swap(nums[j], nums[j + 1]);
}
}
}
}
```
=== "Java"
```java title="bubble_sort.java"
/* 泡沫排序 */
void bubbleSort(int[] nums) {
// 外迴圈:未排序區間為 [0, i]
for (int i = nums.length - 1; i > 0; i--) {
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (int j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// 交換 nums[j] 與 nums[j + 1]
int tmp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = tmp;
}
}
}
}
```
=== "C#"
```csharp title="bubble_sort.cs"
/* 泡沫排序 */
void BubbleSort(int[] nums) {
// 外迴圈:未排序區間為 [0, i]
for (int i = nums.Length - 1; i > 0; i--) {
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (int j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// 交換 nums[j] 與 nums[j + 1]
(nums[j + 1], nums[j]) = (nums[j], nums[j + 1]);
}
}
}
}
```
=== "Go"
```go title="bubble_sort.go"
/* 泡沫排序 */
func bubbleSort(nums []int) {
// 外迴圈:未排序區間為 [0, i]
for i := len(nums) - 1; i > 0; i-- {
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for j := 0; j < i; j++ {
if nums[j] > nums[j+1] {
// 交換 nums[j] 與 nums[j + 1]
nums[j], nums[j+1] = nums[j+1], nums[j]
}
}
}
}
```
=== "Swift"
```swift title="bubble_sort.swift"
/* 泡沫排序 */
func bubbleSort(nums: inout [Int]) {
// 外迴圈:未排序區間為 [0, i]
for i in nums.indices.dropFirst().reversed() {
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for j in 0 ..< i {
if nums[j] > nums[j + 1] {
// 交換 nums[j] 與 nums[j + 1]
nums.swapAt(j, j + 1)
}
}
}
}
```
=== "JS"
```javascript title="bubble_sort.js"
/* 泡沫排序 */
function bubbleSort(nums) {
// 外迴圈:未排序區間為 [0, i]
for (let i = nums.length - 1; i > 0; i--) {
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (let j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// 交換 nums[j] 與 nums[j + 1]
let tmp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = tmp;
}
}
}
}
```
=== "TS"
```typescript title="bubble_sort.ts"
/* 泡沫排序 */
function bubbleSort(nums: number[]): void {
// 外迴圈:未排序區間為 [0, i]
for (let i = nums.length - 1; i > 0; i--) {
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (let j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// 交換 nums[j] 與 nums[j + 1]
let tmp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = tmp;
}
}
}
}
```
=== "Dart"
```dart title="bubble_sort.dart"
/* 泡沫排序 */
void bubbleSort(List<int> nums) {
// 外迴圈:未排序區間為 [0, i]
for (int i = nums.length - 1; i > 0; i--) {
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (int j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// 交換 nums[j] 與 nums[j + 1]
int tmp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = tmp;
}
}
}
}
```
=== "Rust"
```rust title="bubble_sort.rs"
/* 泡沫排序 */
fn bubble_sort(nums: &mut [i32]) {
// 外迴圈:未排序區間為 [0, i]
for i in (1..nums.len()).rev() {
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for j in 0..i {
if nums[j] > nums[j + 1] {
// 交換 nums[j] 與 nums[j + 1]
let tmp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = tmp;
}
}
}
}
```
=== "C"
```c title="bubble_sort.c"
/* 泡沫排序 */
void bubbleSort(int nums[], int size) {
// 外迴圈:未排序區間為 [0, i]
for (int i = size - 1; i > 0; i--) {
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (int j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
int temp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = temp;
}
}
}
}
```
=== "Kotlin"
```kotlin title="bubble_sort.kt"
/* 泡沫排序 */
fun bubbleSort(nums: IntArray) {
// 外迴圈:未排序區間為 [0, i]
for (i in nums.size - 1 downTo 1) {
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (j in 0..<i) {
if (nums[j] > nums[j + 1]) {
// 交換 nums[j] 與 nums[j + 1]
nums[j] = nums[j+1].also { nums[j+1] = nums[j] }
}
}
}
}
```
=== "Ruby"
```ruby title="bubble_sort.rb"
[class]{}-[func]{bubble_sort}
```
=== "Zig"
```zig title="bubble_sort.zig"
// 泡沫排序
fn bubbleSort(nums: []i32) void {
// 外迴圈:未排序區間為 [0, i]
var i: usize = nums.len - 1;
while (i > 0) : (i -= 1) {
var j: usize = 0;
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
while (j < i) : (j += 1) {
if (nums[j] > nums[j + 1]) {
// 交換 nums[j] 與 nums[j + 1]
var tmp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = tmp;
}
}
}
}
```
??? pythontutor "視覺化執行"
<div style="height: 477px; width: 100%;"><iframe class="pythontutor-iframe" src="https://pythontutor.com/iframe-embed.html#code=def%20bubble_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E5%BE%AA%E7%8E%AF%EF%BC%9A%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5B0,%20i%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201,%200,%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%BE%AA%E7%8E%AF%EF%BC%9A%E5%B0%86%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%20%5B0,%20i%5D%20%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%E8%87%B3%E8%AF%A5%E5%8C%BA%E9%97%B4%E7%9A%84%E6%9C%80%E5%8F%B3%E7%AB%AF%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%20nums%5Bj%5D%20%E4%B8%8E%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D,%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%20%2B%201%5D,%20nums%5Bj%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4,%201,%203,%201,%205,%202%5D%0A%20%20%20%20bubble_sort%28nums%29%0A%20%20%20%20print%28%22%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&codeDivHeight=472&codeDivWidth=350&cumulative=false&curInstr=4&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe></div>
<div style="margin-top: 5px;"><a href="https://pythontutor.com/iframe-embed.html#code=def%20bubble_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E5%BE%AA%E7%8E%AF%EF%BC%9A%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5B0,%20i%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201,%200,%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%BE%AA%E7%8E%AF%EF%BC%9A%E5%B0%86%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%20%5B0,%20i%5D%20%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%E8%87%B3%E8%AF%A5%E5%8C%BA%E9%97%B4%E7%9A%84%E6%9C%80%E5%8F%B3%E7%AB%AF%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%20nums%5Bj%5D%20%E4%B8%8E%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D,%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%20%2B%201%5D,%20nums%5Bj%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4,%201,%203,%201,%205,%202%5D%0A%20%20%20%20bubble_sort%28nums%29%0A%20%20%20%20print%28%22%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=4&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">全螢幕觀看 ></a></div>
## 11.3.2 &nbsp; 效率最佳化
我們發現,如果某輪“冒泡”中沒有執行任何交換操作,說明陣列已經完成排序,可直接返回結果。因此,可以增加一個標誌位 `flag` 來監測這種情況,一旦出現就立即返回。
經過最佳化,泡沫排序的最差時間複雜度和平均時間複雜度仍為 $O(n^2)$ ;但當輸入陣列完全有序時,可達到最佳時間複雜度 $O(n)$ 。
=== "Python"
```python title="bubble_sort.py"
def bubble_sort_with_flag(nums: list[int]):
"""泡沫排序(標誌最佳化)"""
n = len(nums)
# 外迴圈:未排序區間為 [0, i]
for i in range(n - 1, 0, -1):
flag = False # 初始化標誌位
# 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for j in range(i):
if nums[j] > nums[j + 1]:
# 交換 nums[j] 與 nums[j + 1]
nums[j], nums[j + 1] = nums[j + 1], nums[j]
flag = True # 記錄交換元素
if not flag:
break # 此輪“冒泡”未交換任何元素,直接跳出
```
=== "C++"
```cpp title="bubble_sort.cpp"
/* 泡沫排序(標誌最佳化)*/
void bubbleSortWithFlag(vector<int> &nums) {
// 外迴圈:未排序區間為 [0, i]
for (int i = nums.size() - 1; i > 0; i--) {
bool flag = false; // 初始化標誌位
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (int j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// 交換 nums[j] 與 nums[j + 1]
// 這裡使用了 std::swap() 函式
swap(nums[j], nums[j + 1]);
flag = true; // 記錄交換元素
}
}
if (!flag)
break; // 此輪“冒泡”未交換任何元素,直接跳出
}
}
```
=== "Java"
```java title="bubble_sort.java"
/* 泡沫排序(標誌最佳化) */
void bubbleSortWithFlag(int[] nums) {
// 外迴圈:未排序區間為 [0, i]
for (int i = nums.length - 1; i > 0; i--) {
boolean flag = false; // 初始化標誌位
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (int j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// 交換 nums[j] 與 nums[j + 1]
int tmp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = tmp;
flag = true; // 記錄交換元素
}
}
if (!flag)
break; // 此輪“冒泡”未交換任何元素,直接跳出
}
}
```
=== "C#"
```csharp title="bubble_sort.cs"
/* 泡沫排序(標誌最佳化)*/
void BubbleSortWithFlag(int[] nums) {
// 外迴圈:未排序區間為 [0, i]
for (int i = nums.Length - 1; i > 0; i--) {
bool flag = false; // 初始化標誌位
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (int j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// 交換 nums[j] 與 nums[j + 1]
(nums[j + 1], nums[j]) = (nums[j], nums[j + 1]);
flag = true; // 記錄交換元素
}
}
if (!flag) break; // 此輪“冒泡”未交換任何元素,直接跳出
}
}
```
=== "Go"
```go title="bubble_sort.go"
/* 泡沫排序(標誌最佳化)*/
func bubbleSortWithFlag(nums []int) {
// 外迴圈:未排序區間為 [0, i]
for i := len(nums) - 1; i > 0; i-- {
flag := false // 初始化標誌位
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for j := 0; j < i; j++ {
if nums[j] > nums[j+1] {
// 交換 nums[j] 與 nums[j + 1]
nums[j], nums[j+1] = nums[j+1], nums[j]
flag = true // 記錄交換元素
}
}
if flag == false { // 此輪“冒泡”未交換任何元素,直接跳出
break
}
}
}
```
=== "Swift"
```swift title="bubble_sort.swift"
/* 泡沫排序(標誌最佳化)*/
func bubbleSortWithFlag(nums: inout [Int]) {
// 外迴圈:未排序區間為 [0, i]
for i in nums.indices.dropFirst().reversed() {
var flag = false // 初始化標誌位
for j in 0 ..< i {
if nums[j] > nums[j + 1] {
// 交換 nums[j] 與 nums[j + 1]
nums.swapAt(j, j + 1)
flag = true // 記錄交換元素
}
}
if !flag { // 此輪“冒泡”未交換任何元素,直接跳出
break
}
}
}
```
=== "JS"
```javascript title="bubble_sort.js"
/* 泡沫排序(標誌最佳化)*/
function bubbleSortWithFlag(nums) {
// 外迴圈:未排序區間為 [0, i]
for (let i = nums.length - 1; i > 0; i--) {
let flag = false; // 初始化標誌位
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (let j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// 交換 nums[j] 與 nums[j + 1]
let tmp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = tmp;
flag = true; // 記錄交換元素
}
}
if (!flag) break; // 此輪“冒泡”未交換任何元素,直接跳出
}
}
```
=== "TS"
```typescript title="bubble_sort.ts"
/* 泡沫排序(標誌最佳化)*/
function bubbleSortWithFlag(nums: number[]): void {
// 外迴圈:未排序區間為 [0, i]
for (let i = nums.length - 1; i > 0; i--) {
let flag = false; // 初始化標誌位
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (let j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// 交換 nums[j] 與 nums[j + 1]
let tmp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = tmp;
flag = true; // 記錄交換元素
}
}
if (!flag) break; // 此輪“冒泡”未交換任何元素,直接跳出
}
}
```
=== "Dart"
```dart title="bubble_sort.dart"
/* 泡沫排序(標誌最佳化)*/
void bubbleSortWithFlag(List<int> nums) {
// 外迴圈:未排序區間為 [0, i]
for (int i = nums.length - 1; i > 0; i--) {
bool flag = false; // 初始化標誌位
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (int j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// 交換 nums[j] 與 nums[j + 1]
int tmp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = tmp;
flag = true; // 記錄交換元素
}
}
if (!flag) break; // 此輪“冒泡”未交換任何元素,直接跳出
}
}
```
=== "Rust"
```rust title="bubble_sort.rs"
/* 泡沫排序(標誌最佳化) */
fn bubble_sort_with_flag(nums: &mut [i32]) {
// 外迴圈:未排序區間為 [0, i]
for i in (1..nums.len()).rev() {
let mut flag = false; // 初始化標誌位
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for j in 0..i {
if nums[j] > nums[j + 1] {
// 交換 nums[j] 與 nums[j + 1]
let tmp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = tmp;
flag = true; // 記錄交換元素
}
}
if !flag {
break; // 此輪“冒泡”未交換任何元素,直接跳出
};
}
}
```
=== "C"
```c title="bubble_sort.c"
/* 泡沫排序(標誌最佳化)*/
void bubbleSortWithFlag(int nums[], int size) {
// 外迴圈:未排序區間為 [0, i]
for (int i = size - 1; i > 0; i--) {
bool flag = false;
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (int j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
int temp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = temp;
flag = true;
}
}
if (!flag)
break;
}
}
```
=== "Kotlin"
```kotlin title="bubble_sort.kt"
/* 泡沫排序(標誌最佳化) */
fun bubbleSortWithFlag(nums: IntArray) {
// 外迴圈:未排序區間為 [0, i]
for (i in nums.size - 1 downTo 1) {
var flag = false // 初始化標誌位
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
for (j in 0..<i) {
if (nums[j] > nums[j + 1]) {
// 交換 nums[j] 與 nums[j + 1]
nums[j] = nums[j + 1].also { nums[j] = nums[j + 1] }
flag = true // 記錄交換元素
}
}
if (!flag) break // 此輪“冒泡”未交換任何元素,直接跳出
}
}
```
=== "Ruby"
```ruby title="bubble_sort.rb"
[class]{}-[func]{bubble_sort_with_flag}
```
=== "Zig"
```zig title="bubble_sort.zig"
// 泡沫排序(標誌最佳化)
fn bubbleSortWithFlag(nums: []i32) void {
// 外迴圈:未排序區間為 [0, i]
var i: usize = nums.len - 1;
while (i > 0) : (i -= 1) {
var flag = false; // 初始化標誌位
var j: usize = 0;
// 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端
while (j < i) : (j += 1) {
if (nums[j] > nums[j + 1]) {
// 交換 nums[j] 與 nums[j + 1]
var tmp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = tmp;
flag = true;
}
}
if (!flag) break; // 此輪“冒泡”未交換任何元素,直接跳出
}
}
```
??? pythontutor "視覺化執行"
<div style="height: 549px; width: 100%;"><iframe class="pythontutor-iframe" src="https://pythontutor.com/iframe-embed.html#code=def%20bubble_sort_with_flag%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%EF%BC%88%E6%A0%87%E5%BF%97%E4%BC%98%E5%8C%96%EF%BC%89%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E5%BE%AA%E7%8E%AF%EF%BC%9A%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5B0,%20i%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201,%200,%20-1%29%3A%0A%20%20%20%20%20%20%20%20flag%20%3D%20False%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%87%E5%BF%97%E4%BD%8D%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%BE%AA%E7%8E%AF%EF%BC%9A%E5%B0%86%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%20%5B0,%20i%5D%20%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%E8%87%B3%E8%AF%A5%E5%8C%BA%E9%97%B4%E7%9A%84%E6%9C%80%E5%8F%B3%E7%AB%AF%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%20nums%5Bj%5D%20%E4%B8%8E%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D,%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%20%2B%201%5D,%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20flag%20%3D%20True%20%20%23%20%E8%AE%B0%E5%BD%95%E4%BA%A4%E6%8D%A2%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20if%20not%20flag%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%20%20%23%20%E6%AD%A4%E8%BD%AE%E2%80%9C%E5%86%92%E6%B3%A1%E2%80%9D%E6%9C%AA%E4%BA%A4%E6%8D%A2%E4%BB%BB%E4%BD%95%E5%85%83%E7%B4%A0%EF%BC%8C%E7%9B%B4%E6%8E%A5%E8%B7%B3%E5%87%BA%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4,%201,%203,%201,%205,%202%5D%0A%20%20%20%20bubble_sort_with_flag%28nums%29%0A%20%20%20%20print%28%22%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&codeDivHeight=472&codeDivWidth=350&cumulative=false&curInstr=4&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe></div>
<div style="margin-top: 5px;"><a href="https://pythontutor.com/iframe-embed.html#code=def%20bubble_sort_with_flag%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%EF%BC%88%E6%A0%87%E5%BF%97%E4%BC%98%E5%8C%96%EF%BC%89%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E5%BE%AA%E7%8E%AF%EF%BC%9A%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5B0,%20i%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201,%200,%20-1%29%3A%0A%20%20%20%20%20%20%20%20flag%20%3D%20False%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%87%E5%BF%97%E4%BD%8D%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%BE%AA%E7%8E%AF%EF%BC%9A%E5%B0%86%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%20%5B0,%20i%5D%20%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%E8%87%B3%E8%AF%A5%E5%8C%BA%E9%97%B4%E7%9A%84%E6%9C%80%E5%8F%B3%E7%AB%AF%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%20nums%5Bj%5D%20%E4%B8%8E%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D,%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%20%2B%201%5D,%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20flag%20%3D%20True%20%20%23%20%E8%AE%B0%E5%BD%95%E4%BA%A4%E6%8D%A2%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20if%20not%20flag%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%20%20%23%20%E6%AD%A4%E8%BD%AE%E2%80%9C%E5%86%92%E6%B3%A1%E2%80%9D%E6%9C%AA%E4%BA%A4%E6%8D%A2%E4%BB%BB%E4%BD%95%E5%85%83%E7%B4%A0%EF%BC%8C%E7%9B%B4%E6%8E%A5%E8%B7%B3%E5%87%BA%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4,%201,%203,%201,%205,%202%5D%0A%20%20%20%20bubble_sort_with_flag%28nums%29%0A%20%20%20%20print%28%22%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=4&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">全螢幕觀看 ></a></div>
## 11.3.3 &nbsp; 演算法特性
- **時間複雜度為 $O(n^2)$、自適應排序**:各輪“冒泡”走訪的陣列長度依次為 $n - 1$、$n - 2$、$\dots$、$2$、$1$ ,總和為 $(n - 1) n / 2$ 。在引入 `flag` 最佳化後,最佳時間複雜度可達到 $O(n)$ 。
- **空間複雜度為 $O(1)$、原地排序**:指標 $i$ 和 $j$ 使用常數大小的額外空間。
- **穩定排序**:由於在“冒泡”中遇到相等元素不交換。
+468
View File
@@ -0,0 +1,468 @@
---
comments: true
---
# 11.8 &nbsp; 桶排序
前述幾種排序演算法都屬於“基於比較的排序演算法”,它們透過比較元素間的大小來實現排序。此類排序演算法的時間複雜度無法超越 $O(n \log n)$ 。接下來,我們將探討幾種“非比較排序演算法”,它們的時間複雜度可以達到線性階。
<u>桶排序(bucket sort</u>是分治策略的一個典型應用。它透過設定一些具有大小順序的桶,每個桶對應一個數據範圍,將資料平均分配到各個桶中;然後,在每個桶內部分別執行排序;最終按照桶的順序將所有資料合併。
## 11.8.1 &nbsp; 演算法流程
考慮一個長度為 $n$ 的陣列,其元素是範圍 $[0, 1)$ 內的浮點數。桶排序的流程如圖 11-13 所示。
1. 初始化 $k$ 個桶,將 $n$ 個元素分配到 $k$ 個桶中。
2. 對每個桶分別執行排序(這裡採用程式語言的內建排序函式)。
3. 按照桶從小到大的順序合併結果。
![桶排序演算法流程](bucket_sort.assets/bucket_sort_overview.png){ class="animation-figure" }
<p align="center"> 圖 11-13 &nbsp; 桶排序演算法流程 </p>
程式碼如下所示:
=== "Python"
```python title="bucket_sort.py"
def bucket_sort(nums: list[float]):
"""桶排序"""
# 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素
k = len(nums) // 2
buckets = [[] for _ in range(k)]
# 1. 將陣列元素分配到各個桶中
for num in nums:
# 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1]
i = int(num * k)
# 將 num 新增進桶 i
buckets[i].append(num)
# 2. 對各個桶執行排序
for bucket in buckets:
# 使用內建排序函式,也可以替換成其他排序演算法
bucket.sort()
# 3. 走訪桶合併結果
i = 0
for bucket in buckets:
for num in bucket:
nums[i] = num
i += 1
```
=== "C++"
```cpp title="bucket_sort.cpp"
/* 桶排序 */
void bucketSort(vector<float> &nums) {
// 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素
int k = nums.size() / 2;
vector<vector<float>> buckets(k);
// 1. 將陣列元素分配到各個桶中
for (float num : nums) {
// 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1]
int i = num * k;
// 將 num 新增進桶 bucket_idx
buckets[i].push_back(num);
}
// 2. 對各個桶執行排序
for (vector<float> &bucket : buckets) {
// 使用內建排序函式,也可以替換成其他排序演算法
sort(bucket.begin(), bucket.end());
}
// 3. 走訪桶合併結果
int i = 0;
for (vector<float> &bucket : buckets) {
for (float num : bucket) {
nums[i++] = num;
}
}
}
```
=== "Java"
```java title="bucket_sort.java"
/* 桶排序 */
void bucketSort(float[] nums) {
// 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素
int k = nums.length / 2;
List<List<Float>> buckets = new ArrayList<>();
for (int i = 0; i < k; i++) {
buckets.add(new ArrayList<>());
}
// 1. 將陣列元素分配到各個桶中
for (float num : nums) {
// 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1]
int i = (int) (num * k);
// 將 num 新增進桶 i
buckets.get(i).add(num);
}
// 2. 對各個桶執行排序
for (List<Float> bucket : buckets) {
// 使用內建排序函式,也可以替換成其他排序演算法
Collections.sort(bucket);
}
// 3. 走訪桶合併結果
int i = 0;
for (List<Float> bucket : buckets) {
for (float num : bucket) {
nums[i++] = num;
}
}
}
```
=== "C#"
```csharp title="bucket_sort.cs"
/* 桶排序 */
void BucketSort(float[] nums) {
// 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素
int k = nums.Length / 2;
List<List<float>> buckets = [];
for (int i = 0; i < k; i++) {
buckets.Add([]);
}
// 1. 將陣列元素分配到各個桶中
foreach (float num in nums) {
// 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1]
int i = (int)(num * k);
// 將 num 新增進桶 i
buckets[i].Add(num);
}
// 2. 對各個桶執行排序
foreach (List<float> bucket in buckets) {
// 使用內建排序函式,也可以替換成其他排序演算法
bucket.Sort();
}
// 3. 走訪桶合併結果
int j = 0;
foreach (List<float> bucket in buckets) {
foreach (float num in bucket) {
nums[j++] = num;
}
}
}
```
=== "Go"
```go title="bucket_sort.go"
/* 桶排序 */
func bucketSort(nums []float64) {
// 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素
k := len(nums) / 2
buckets := make([][]float64, k)
for i := 0; i < k; i++ {
buckets[i] = make([]float64, 0)
}
// 1. 將陣列元素分配到各個桶中
for _, num := range nums {
// 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1]
i := int(num * float64(k))
// 將 num 新增進桶 i
buckets[i] = append(buckets[i], num)
}
// 2. 對各個桶執行排序
for i := 0; i < k; i++ {
// 使用內建切片排序函式,也可以替換成其他排序演算法
sort.Float64s(buckets[i])
}
// 3. 走訪桶合併結果
i := 0
for _, bucket := range buckets {
for _, num := range bucket {
nums[i] = num
i++
}
}
}
```
=== "Swift"
```swift title="bucket_sort.swift"
/* 桶排序 */
func bucketSort(nums: inout [Double]) {
// 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素
let k = nums.count / 2
var buckets = (0 ..< k).map { _ in [Double]() }
// 1. 將陣列元素分配到各個桶中
for num in nums {
// 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1]
let i = Int(num * Double(k))
// 將 num 新增進桶 i
buckets[i].append(num)
}
// 2. 對各個桶執行排序
for i in buckets.indices {
// 使用內建排序函式,也可以替換成其他排序演算法
buckets[i].sort()
}
// 3. 走訪桶合併結果
var i = nums.startIndex
for bucket in buckets {
for num in bucket {
nums[i] = num
i += 1
}
}
}
```
=== "JS"
```javascript title="bucket_sort.js"
/* 桶排序 */
function bucketSort(nums) {
// 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素
const k = nums.length / 2;
const buckets = [];
for (let i = 0; i < k; i++) {
buckets.push([]);
}
// 1. 將陣列元素分配到各個桶中
for (const num of nums) {
// 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1]
const i = Math.floor(num * k);
// 將 num 新增進桶 i
buckets[i].push(num);
}
// 2. 對各個桶執行排序
for (const bucket of buckets) {
// 使用內建排序函式,也可以替換成其他排序演算法
bucket.sort((a, b) => a - b);
}
// 3. 走訪桶合併結果
let i = 0;
for (const bucket of buckets) {
for (const num of bucket) {
nums[i++] = num;
}
}
}
```
=== "TS"
```typescript title="bucket_sort.ts"
/* 桶排序 */
function bucketSort(nums: number[]): void {
// 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素
const k = nums.length / 2;
const buckets: number[][] = [];
for (let i = 0; i < k; i++) {
buckets.push([]);
}
// 1. 將陣列元素分配到各個桶中
for (const num of nums) {
// 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1]
const i = Math.floor(num * k);
// 將 num 新增進桶 i
buckets[i].push(num);
}
// 2. 對各個桶執行排序
for (const bucket of buckets) {
// 使用內建排序函式,也可以替換成其他排序演算法
bucket.sort((a, b) => a - b);
}
// 3. 走訪桶合併結果
let i = 0;
for (const bucket of buckets) {
for (const num of bucket) {
nums[i++] = num;
}
}
}
```
=== "Dart"
```dart title="bucket_sort.dart"
/* 桶排序 */
void bucketSort(List<double> nums) {
// 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素
int k = nums.length ~/ 2;
List<List<double>> buckets = List.generate(k, (index) => []);
// 1. 將陣列元素分配到各個桶中
for (double _num in nums) {
// 輸入資料範圍為 [0, 1),使用 _num * k 對映到索引範圍 [0, k-1]
int i = (_num * k).toInt();
// 將 _num 新增進桶 bucket_idx
buckets[i].add(_num);
}
// 2. 對各個桶執行排序
for (List<double> bucket in buckets) {
bucket.sort();
}
// 3. 走訪桶合併結果
int i = 0;
for (List<double> bucket in buckets) {
for (double _num in bucket) {
nums[i++] = _num;
}
}
}
```
=== "Rust"
```rust title="bucket_sort.rs"
/* 桶排序 */
fn bucket_sort(nums: &mut [f64]) {
// 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素
let k = nums.len() / 2;
let mut buckets = vec![vec![]; k];
// 1. 將陣列元素分配到各個桶中
for &mut num in &mut *nums {
// 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1]
let i = (num * k as f64) as usize;
// 將 num 新增進桶 i
buckets[i].push(num);
}
// 2. 對各個桶執行排序
for bucket in &mut buckets {
// 使用內建排序函式,也可以替換成其他排序演算法
bucket.sort_by(|a, b| a.partial_cmp(b).unwrap());
}
// 3. 走訪桶合併結果
let mut i = 0;
for bucket in &mut buckets {
for &mut num in bucket {
nums[i] = num;
i += 1;
}
}
}
```
=== "C"
```c title="bucket_sort.c"
/* 桶排序 */
void bucketSort(float nums[], int size) {
// 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素
int k = size / 2;
float **buckets = calloc(k, sizeof(float *));
for (int i = 0; i < k; i++) {
// 每個桶最多可以分配 size 個元素
buckets[i] = calloc(size, sizeof(float));
}
// 1. 將陣列元素分配到各個桶中
for (int i = 0; i < size; i++) {
// 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1]
int bucket_idx = nums[i] * k;
int j = 0;
// 如果桶中有資料且資料小於當前值 nums[i], 要將其放到當前桶的後面,相當於 cpp 中的 push_back
while (buckets[bucket_idx][j] > 0 && buckets[bucket_idx][j] < nums[i]) {
j++;
}
float temp = nums[i];
while (j < size && buckets[bucket_idx][j] > 0) {
swap(&temp, &buckets[bucket_idx][j]);
j++;
}
buckets[bucket_idx][j] = temp;
}
// 2. 對各個桶執行排序
for (int i = 0; i < k; i++) {
qsort(buckets[i], size, sizeof(float), compare_float);
}
// 3. 走訪桶合併結果
for (int i = 0, j = 0; j < k; j++) {
for (int l = 0; l < size; l++) {
if (buckets[j][l] > 0) {
nums[i++] = buckets[j][l];
}
}
}
// 釋放上述分配的記憶體
for (int i = 0; i < k; i++) {
free(buckets[i]);
}
free(buckets);
}
```
=== "Kotlin"
```kotlin title="bucket_sort.kt"
/* 桶排序 */
fun bucketSort(nums: FloatArray) {
// 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素
val k = nums.size / 2
val buckets = ArrayList<ArrayList<Float>>()
for (i in 0..<k) {
buckets.add(ArrayList())
}
// 1. 將陣列元素分配到各個桶中
for (num in nums) {
// 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1]
val i = (num * k).toInt()
// 將 num 新增進桶 i
buckets[i].add(num)
}
// 2. 對各個桶執行排序
for (bucket in buckets) {
// 使用內建排序函式,也可以替換成其他排序演算法
bucket.sort()
}
// 3. 走訪桶合併結果
var i = 0
for (bucket in buckets) {
for (num in bucket) {
nums[i++] = num
}
}
}
```
=== "Ruby"
```ruby title="bucket_sort.rb"
[class]{}-[func]{bucket_sort}
```
=== "Zig"
```zig title="bucket_sort.zig"
[class]{}-[func]{bucketSort}
```
??? pythontutor "視覺化執行"
<div style="height: 549px; width: 100%;"><iframe class="pythontutor-iframe" src="https://pythontutor.com/iframe-embed.html#code=def%20bucket_sort%28nums%3A%20list%5Bfloat%5D%29%3A%0A%20%20%20%20%22%22%22%E6%A1%B6%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20k%20%3D%20n/2%20%E4%B8%AA%E6%A1%B6%EF%BC%8C%E9%A2%84%E6%9C%9F%E5%90%91%E6%AF%8F%E4%B8%AA%E6%A1%B6%E5%88%86%E9%85%8D%202%20%E4%B8%AA%E5%85%83%E7%B4%A0%0A%20%20%20%20k%20%3D%20len%28nums%29%20//%202%0A%20%20%20%20buckets%20%3D%20%5B%5B%5D%20for%20_%20in%20range%28k%29%5D%0A%20%20%20%20%23%201.%20%E5%B0%86%E6%95%B0%E7%BB%84%E5%85%83%E7%B4%A0%E5%88%86%E9%85%8D%E5%88%B0%E5%90%84%E4%B8%AA%E6%A1%B6%E4%B8%AD%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E8%8C%83%E5%9B%B4%E4%B8%BA%20%5B0,%201%29%EF%BC%8C%E4%BD%BF%E7%94%A8%20num%20*%20k%20%E6%98%A0%E5%B0%84%E5%88%B0%E7%B4%A2%E5%BC%95%E8%8C%83%E5%9B%B4%20%5B0,%20k-1%5D%0A%20%20%20%20%20%20%20%20i%20%3D%20int%28num%20*%20k%29%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%86%20num%20%E6%B7%BB%E5%8A%A0%E8%BF%9B%E6%A1%B6%20i%0A%20%20%20%20%20%20%20%20buckets%5Bi%5D.append%28num%29%0A%20%20%20%20%23%202.%20%E5%AF%B9%E5%90%84%E4%B8%AA%E6%A1%B6%E6%89%A7%E8%A1%8C%E6%8E%92%E5%BA%8F%0A%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E5%86%85%E7%BD%AE%E6%8E%92%E5%BA%8F%E5%87%BD%E6%95%B0%EF%BC%8C%E4%B9%9F%E5%8F%AF%E4%BB%A5%E6%9B%BF%E6%8D%A2%E6%88%90%E5%85%B6%E4%BB%96%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%0A%20%20%20%20%20%20%20%20bucket.sort%28%29%0A%20%20%20%20%23%203.%20%E9%81%8D%E5%8E%86%E6%A1%B6%E5%90%88%E5%B9%B6%E7%BB%93%E6%9E%9C%0A%20%20%20%20i%20%3D%200%0A%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20for%20num%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E8%AE%BE%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E4%B8%BA%E6%B5%AE%E7%82%B9%E6%95%B0%EF%BC%8C%E8%8C%83%E5%9B%B4%E4%B8%BA%20%5B0,%201%29%0A%20%20%20%20nums%20%3D%20%5B0.49,%200.96,%200.82,%200.09,%200.57,%200.43,%200.91,%200.75,%200.15,%200.37%5D%0A%20%20%20%20bucket_sort%28nums%29%0A%20%20%20%20print%28%22%E6%A1%B6%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&codeDivHeight=472&codeDivWidth=350&cumulative=false&curInstr=3&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe></div>
<div style="margin-top: 5px;"><a href="https://pythontutor.com/iframe-embed.html#code=def%20bucket_sort%28nums%3A%20list%5Bfloat%5D%29%3A%0A%20%20%20%20%22%22%22%E6%A1%B6%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20k%20%3D%20n/2%20%E4%B8%AA%E6%A1%B6%EF%BC%8C%E9%A2%84%E6%9C%9F%E5%90%91%E6%AF%8F%E4%B8%AA%E6%A1%B6%E5%88%86%E9%85%8D%202%20%E4%B8%AA%E5%85%83%E7%B4%A0%0A%20%20%20%20k%20%3D%20len%28nums%29%20//%202%0A%20%20%20%20buckets%20%3D%20%5B%5B%5D%20for%20_%20in%20range%28k%29%5D%0A%20%20%20%20%23%201.%20%E5%B0%86%E6%95%B0%E7%BB%84%E5%85%83%E7%B4%A0%E5%88%86%E9%85%8D%E5%88%B0%E5%90%84%E4%B8%AA%E6%A1%B6%E4%B8%AD%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E8%8C%83%E5%9B%B4%E4%B8%BA%20%5B0,%201%29%EF%BC%8C%E4%BD%BF%E7%94%A8%20num%20*%20k%20%E6%98%A0%E5%B0%84%E5%88%B0%E7%B4%A2%E5%BC%95%E8%8C%83%E5%9B%B4%20%5B0,%20k-1%5D%0A%20%20%20%20%20%20%20%20i%20%3D%20int%28num%20*%20k%29%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%86%20num%20%E6%B7%BB%E5%8A%A0%E8%BF%9B%E6%A1%B6%20i%0A%20%20%20%20%20%20%20%20buckets%5Bi%5D.append%28num%29%0A%20%20%20%20%23%202.%20%E5%AF%B9%E5%90%84%E4%B8%AA%E6%A1%B6%E6%89%A7%E8%A1%8C%E6%8E%92%E5%BA%8F%0A%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E5%86%85%E7%BD%AE%E6%8E%92%E5%BA%8F%E5%87%BD%E6%95%B0%EF%BC%8C%E4%B9%9F%E5%8F%AF%E4%BB%A5%E6%9B%BF%E6%8D%A2%E6%88%90%E5%85%B6%E4%BB%96%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%0A%20%20%20%20%20%20%20%20bucket.sort%28%29%0A%20%20%20%20%23%203.%20%E9%81%8D%E5%8E%86%E6%A1%B6%E5%90%88%E5%B9%B6%E7%BB%93%E6%9E%9C%0A%20%20%20%20i%20%3D%200%0A%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20for%20num%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E8%AE%BE%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E4%B8%BA%E6%B5%AE%E7%82%B9%E6%95%B0%EF%BC%8C%E8%8C%83%E5%9B%B4%E4%B8%BA%20%5B0,%201%29%0A%20%20%20%20nums%20%3D%20%5B0.49,%200.96,%200.82,%200.09,%200.57,%200.43,%200.91,%200.75,%200.15,%200.37%5D%0A%20%20%20%20bucket_sort%28nums%29%0A%20%20%20%20print%28%22%E6%A1%B6%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=3&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">全螢幕觀看 ></a></div>
## 11.8.2 &nbsp; 演算法特性
桶排序適用於處理體量很大的資料。例如,輸入資料包含 100 萬個元素,由於空間限制,系統記憶體無法一次性載入所有資料。此時,可以將資料分成 1000 個桶,然後分別對每個桶進行排序,最後將結果合併。
- **時間複雜度為 $O(n + k)$** :假設元素在各個桶內平均分佈,那麼每個桶內的元素數量為 $\frac{n}{k}$ 。假設排序單個桶使用 $O(\frac{n}{k} \log\frac{n}{k})$ 時間,則排序所有桶使用 $O(n \log\frac{n}{k})$ 時間。**當桶數量 $k$ 比較大時,時間複雜度則趨向於 $O(n)$** 。合併結果時需要走訪所有桶和元素,花費 $O(n + k)$ 時間。
- **自適應排序**:在最差情況下,所有資料被分配到一個桶中,且排序該桶使用 $O(n^2)$ 時間。
- **空間複雜度為 $O(n + k)$、非原地排序**:需要藉助 $k$ 個桶和總共 $n$ 個元素的額外空間。
- 桶排序是否穩定取決於排序桶內元素的演算法是否穩定。
## 11.8.3 &nbsp; 如何實現平均分配
桶排序的時間複雜度理論上可以達到 $O(n)$ ,**關鍵在於將元素均勻分配到各個桶中**,因為實際資料往往不是均勻分佈的。例如,我們想要將淘寶上的所有商品按價格範圍平均分配到 10 個桶中,但商品價格分佈不均,低於 100 元的非常多,高於 1000 元的非常少。若將價格區間平均劃分為 10 個,各個桶中的商品數量差距會非常大。
為實現平均分配,我們可以先設定一條大致的分界線,將資料粗略地分到 3 個桶中。**分配完畢後,再將商品較多的桶繼續劃分為 3 個桶,直至所有桶中的元素數量大致相等**。
如圖 11-14 所示,這種方法本質上是建立一棵遞迴樹,目標是讓葉節點的值儘可能平均。當然,不一定要每輪將資料劃分為 3 個桶,具體劃分方式可根據資料特點靈活選擇。
![遞迴劃分桶](bucket_sort.assets/scatter_in_buckets_recursively.png){ class="animation-figure" }
<p align="center"> 圖 11-14 &nbsp; 遞迴劃分桶 </p>
如果我們提前知道商品價格的機率分佈,**則可以根據資料機率分佈設定每個桶的價格分界線**。值得注意的是,資料分佈並不一定需要特意統計,也可以根據資料特點採用某種機率模型進行近似。
如圖 11-15 所示,我們假設商品價格服從正態分佈,這樣就可以合理地設定價格區間,從而將商品平均分配到各個桶中。
![根據機率分佈劃分桶](bucket_sort.assets/scatter_in_buckets_distribution.png){ class="animation-figure" }
<p align="center"> 圖 11-15 &nbsp; 根據機率分佈劃分桶 </p>
@@ -0,0 +1,883 @@
---
comments: true
---
# 11.9 &nbsp; 計數排序
<u>計數排序(counting sort</u>透過統計元素數量來實現排序,通常應用於整數陣列。
## 11.9.1 &nbsp; 簡單實現
先來看一個簡單的例子。給定一個長度為 $n$ 的陣列 `nums` ,其中的元素都是“非負整數”,計數排序的整體流程如圖 11-16 所示。
1. 走訪陣列,找出其中的最大數字,記為 $m$ ,然後建立一個長度為 $m + 1$ 的輔助陣列 `counter`
2. **藉助 `counter` 統計 `nums` 中各數字的出現次數**,其中 `counter[num]` 對應數字 `num` 的出現次數。統計方法很簡單,只需走訪 `nums`(設當前數字為 `num`),每輪將 `counter[num]` 增加 $1$ 即可。
3. **由於 `counter` 的各個索引天然有序,因此相當於所有數字已經排序好了**。接下來,我們走訪 `counter` ,根據各數字出現次數從小到大的順序填入 `nums` 即可。
![計數排序流程](counting_sort.assets/counting_sort_overview.png){ class="animation-figure" }
<p align="center"> 圖 11-16 &nbsp; 計數排序流程 </p>
程式碼如下所示:
=== "Python"
```python title="counting_sort.py"
def counting_sort_naive(nums: list[int]):
"""計數排序"""
# 簡單實現,無法用於排序物件
# 1. 統計陣列最大元素 m
m = 0
for num in nums:
m = max(m, num)
# 2. 統計各數字的出現次數
# counter[num] 代表 num 的出現次數
counter = [0] * (m + 1)
for num in nums:
counter[num] += 1
# 3. 走訪 counter ,將各元素填入原陣列 nums
i = 0
for num in range(m + 1):
for _ in range(counter[num]):
nums[i] = num
i += 1
```
=== "C++"
```cpp title="counting_sort.cpp"
/* 計數排序 */
// 簡單實現,無法用於排序物件
void countingSortNaive(vector<int> &nums) {
// 1. 統計陣列最大元素 m
int m = 0;
for (int num : nums) {
m = max(m, num);
}
// 2. 統計各數字的出現次數
// counter[num] 代表 num 的出現次數
vector<int> counter(m + 1, 0);
for (int num : nums) {
counter[num]++;
}
// 3. 走訪 counter ,將各元素填入原陣列 nums
int i = 0;
for (int num = 0; num < m + 1; num++) {
for (int j = 0; j < counter[num]; j++, i++) {
nums[i] = num;
}
}
}
```
=== "Java"
```java title="counting_sort.java"
/* 計數排序 */
// 簡單實現,無法用於排序物件
void countingSortNaive(int[] nums) {
// 1. 統計陣列最大元素 m
int m = 0;
for (int num : nums) {
m = Math.max(m, num);
}
// 2. 統計各數字的出現次數
// counter[num] 代表 num 的出現次數
int[] counter = new int[m + 1];
for (int num : nums) {
counter[num]++;
}
// 3. 走訪 counter ,將各元素填入原陣列 nums
int i = 0;
for (int num = 0; num < m + 1; num++) {
for (int j = 0; j < counter[num]; j++, i++) {
nums[i] = num;
}
}
}
```
=== "C#"
```csharp title="counting_sort.cs"
/* 計數排序 */
// 簡單實現,無法用於排序物件
void CountingSortNaive(int[] nums) {
// 1. 統計陣列最大元素 m
int m = 0;
foreach (int num in nums) {
m = Math.Max(m, num);
}
// 2. 統計各數字的出現次數
// counter[num] 代表 num 的出現次數
int[] counter = new int[m + 1];
foreach (int num in nums) {
counter[num]++;
}
// 3. 走訪 counter ,將各元素填入原陣列 nums
int i = 0;
for (int num = 0; num < m + 1; num++) {
for (int j = 0; j < counter[num]; j++, i++) {
nums[i] = num;
}
}
}
```
=== "Go"
```go title="counting_sort.go"
/* 計數排序 */
// 簡單實現,無法用於排序物件
func countingSortNaive(nums []int) {
// 1. 統計陣列最大元素 m
m := 0
for _, num := range nums {
if num > m {
m = num
}
}
// 2. 統計各數字的出現次數
// counter[num] 代表 num 的出現次數
counter := make([]int, m+1)
for _, num := range nums {
counter[num]++
}
// 3. 走訪 counter ,將各元素填入原陣列 nums
for i, num := 0, 0; num < m+1; num++ {
for j := 0; j < counter[num]; j++ {
nums[i] = num
i++
}
}
}
```
=== "Swift"
```swift title="counting_sort.swift"
/* 計數排序 */
// 簡單實現,無法用於排序物件
func countingSortNaive(nums: inout [Int]) {
// 1. 統計陣列最大元素 m
let m = nums.max()!
// 2. 統計各數字的出現次數
// counter[num] 代表 num 的出現次數
var counter = Array(repeating: 0, count: m + 1)
for num in nums {
counter[num] += 1
}
// 3. 走訪 counter ,將各元素填入原陣列 nums
var i = 0
for num in 0 ..< m + 1 {
for _ in 0 ..< counter[num] {
nums[i] = num
i += 1
}
}
}
```
=== "JS"
```javascript title="counting_sort.js"
/* 計數排序 */
// 簡單實現,無法用於排序物件
function countingSortNaive(nums) {
// 1. 統計陣列最大元素 m
let m = 0;
for (const num of nums) {
m = Math.max(m, num);
}
// 2. 統計各數字的出現次數
// counter[num] 代表 num 的出現次數
const counter = new Array(m + 1).fill(0);
for (const num of nums) {
counter[num]++;
}
// 3. 走訪 counter ,將各元素填入原陣列 nums
let i = 0;
for (let num = 0; num < m + 1; num++) {
for (let j = 0; j < counter[num]; j++, i++) {
nums[i] = num;
}
}
}
```
=== "TS"
```typescript title="counting_sort.ts"
/* 計數排序 */
// 簡單實現,無法用於排序物件
function countingSortNaive(nums: number[]): void {
// 1. 統計陣列最大元素 m
let m = 0;
for (const num of nums) {
m = Math.max(m, num);
}
// 2. 統計各數字的出現次數
// counter[num] 代表 num 的出現次數
const counter: number[] = new Array<number>(m + 1).fill(0);
for (const num of nums) {
counter[num]++;
}
// 3. 走訪 counter ,將各元素填入原陣列 nums
let i = 0;
for (let num = 0; num < m + 1; num++) {
for (let j = 0; j < counter[num]; j++, i++) {
nums[i] = num;
}
}
}
```
=== "Dart"
```dart title="counting_sort.dart"
/* 計數排序 */
// 簡單實現,無法用於排序物件
void countingSortNaive(List<int> nums) {
// 1. 統計陣列最大元素 m
int m = 0;
for (int _num in nums) {
m = max(m, _num);
}
// 2. 統計各數字的出現次數
// counter[_num] 代表 _num 的出現次數
List<int> counter = List.filled(m + 1, 0);
for (int _num in nums) {
counter[_num]++;
}
// 3. 走訪 counter ,將各元素填入原陣列 nums
int i = 0;
for (int _num = 0; _num < m + 1; _num++) {
for (int j = 0; j < counter[_num]; j++, i++) {
nums[i] = _num;
}
}
}
```
=== "Rust"
```rust title="counting_sort.rs"
/* 計數排序 */
// 簡單實現,無法用於排序物件
fn counting_sort_naive(nums: &mut [i32]) {
// 1. 統計陣列最大元素 m
let m = *nums.into_iter().max().unwrap();
// 2. 統計各數字的出現次數
// counter[num] 代表 num 的出現次數
let mut counter = vec![0; m as usize + 1];
for &num in &*nums {
counter[num as usize] += 1;
}
// 3. 走訪 counter ,將各元素填入原陣列 nums
let mut i = 0;
for num in 0..m + 1 {
for _ in 0..counter[num as usize] {
nums[i] = num;
i += 1;
}
}
}
```
=== "C"
```c title="counting_sort.c"
/* 計數排序 */
// 簡單實現,無法用於排序物件
void countingSortNaive(int nums[], int size) {
// 1. 統計陣列最大元素 m
int m = 0;
for (int i = 0; i < size; i++) {
if (nums[i] > m) {
m = nums[i];
}
}
// 2. 統計各數字的出現次數
// counter[num] 代表 num 的出現次數
int *counter = calloc(m + 1, sizeof(int));
for (int i = 0; i < size; i++) {
counter[nums[i]]++;
}
// 3. 走訪 counter ,將各元素填入原陣列 nums
int i = 0;
for (int num = 0; num < m + 1; num++) {
for (int j = 0; j < counter[num]; j++, i++) {
nums[i] = num;
}
}
// 4. 釋放記憶體
free(counter);
}
```
=== "Kotlin"
```kotlin title="counting_sort.kt"
/* 計數排序 */
// 簡單實現,無法用於排序物件
fun countingSortNaive(nums: IntArray) {
// 1. 統計陣列最大元素 m
var m = 0
for (num in nums) {
m = max(m.toDouble(), num.toDouble()).toInt()
}
// 2. 統計各數字的出現次數
// counter[num] 代表 num 的出現次數
val counter = IntArray(m + 1)
for (num in nums) {
counter[num]++
}
// 3. 走訪 counter ,將各元素填入原陣列 nums
var i = 0
for (num in 0..<m + 1) {
var j = 0
while (j < counter[num]) {
nums[i] = num
j++
i++
}
}
}
```
=== "Ruby"
```ruby title="counting_sort.rb"
[class]{}-[func]{counting_sort_naive}
```
=== "Zig"
```zig title="counting_sort.zig"
[class]{}-[func]{countingSortNaive}
```
??? pythontutor "視覺化執行"
<div style="height: 549px; width: 100%;"><iframe class="pythontutor-iframe" src="https://pythontutor.com/iframe-embed.html#code=def%20counting_sort_naive%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E7%AE%80%E5%8D%95%E5%AE%9E%E7%8E%B0%EF%BC%8C%E6%97%A0%E6%B3%95%E7%94%A8%E4%BA%8E%E6%8E%92%E5%BA%8F%E5%AF%B9%E8%B1%A1%0A%20%20%20%20%23%201.%20%E7%BB%9F%E8%AE%A1%E6%95%B0%E7%BB%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%20m%0A%20%20%20%20m%20%3D%200%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20max%28m,%20num%29%0A%20%20%20%20%23%202.%20%E7%BB%9F%E8%AE%A1%E5%90%84%E6%95%B0%E5%AD%97%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20%23%20counter%5Bnum%5D%20%E4%BB%A3%E8%A1%A8%20num%20%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20counter%20%3D%20%5B0%5D%20*%20%28m%20%2B%201%29%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20%2B%3D%201%0A%20%20%20%20%23%203.%20%E9%81%8D%E5%8E%86%20counter%20%EF%BC%8C%E5%B0%86%E5%90%84%E5%85%83%E7%B4%A0%E5%A1%AB%E5%85%A5%E5%8E%9F%E6%95%B0%E7%BB%84%20nums%0A%20%20%20%20i%20%3D%200%0A%20%20%20%20for%20num%20in%20range%28m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20_%20in%20range%28counter%5Bnum%5D%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1,%200,%201,%202,%200,%204,%200,%202,%202,%204%5D%0A%20%20%20%20counting_sort_naive%28nums%29%0A%20%20%20%20print%28f%22%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%EF%BC%88%E6%97%A0%E6%B3%95%E6%8E%92%E5%BA%8F%E5%AF%B9%E8%B1%A1%EF%BC%89%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%20%7Bnums%7D%22%29&codeDivHeight=472&codeDivWidth=350&cumulative=false&curInstr=4&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe></div>
<div style="margin-top: 5px;"><a href="https://pythontutor.com/iframe-embed.html#code=def%20counting_sort_naive%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E7%AE%80%E5%8D%95%E5%AE%9E%E7%8E%B0%EF%BC%8C%E6%97%A0%E6%B3%95%E7%94%A8%E4%BA%8E%E6%8E%92%E5%BA%8F%E5%AF%B9%E8%B1%A1%0A%20%20%20%20%23%201.%20%E7%BB%9F%E8%AE%A1%E6%95%B0%E7%BB%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%20m%0A%20%20%20%20m%20%3D%200%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20max%28m,%20num%29%0A%20%20%20%20%23%202.%20%E7%BB%9F%E8%AE%A1%E5%90%84%E6%95%B0%E5%AD%97%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20%23%20counter%5Bnum%5D%20%E4%BB%A3%E8%A1%A8%20num%20%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20counter%20%3D%20%5B0%5D%20*%20%28m%20%2B%201%29%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20%2B%3D%201%0A%20%20%20%20%23%203.%20%E9%81%8D%E5%8E%86%20counter%20%EF%BC%8C%E5%B0%86%E5%90%84%E5%85%83%E7%B4%A0%E5%A1%AB%E5%85%A5%E5%8E%9F%E6%95%B0%E7%BB%84%20nums%0A%20%20%20%20i%20%3D%200%0A%20%20%20%20for%20num%20in%20range%28m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20_%20in%20range%28counter%5Bnum%5D%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1,%200,%201,%202,%200,%204,%200,%202,%202,%204%5D%0A%20%20%20%20counting_sort_naive%28nums%29%0A%20%20%20%20print%28f%22%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%EF%BC%88%E6%97%A0%E6%B3%95%E6%8E%92%E5%BA%8F%E5%AF%B9%E8%B1%A1%EF%BC%89%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%20%7Bnums%7D%22%29&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=4&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">全螢幕觀看 ></a></div>
!!! note "計數排序與桶排序的關聯"
從桶排序的角度看,我們可以將計數排序中的計數陣列 `counter` 的每個索引視為一個桶,將統計數量的過程看作將各個元素分配到對應的桶中。本質上,計數排序是桶排序在整型資料下的一個特例。
## 11.9.2 &nbsp; 完整實現
細心的讀者可能發現了,**如果輸入資料是物件,上述步驟 `3.` 就失效了**。假設輸入資料是商品物件,我們想按照商品價格(類別的成員變數)對商品進行排序,而上述演算法只能給出價格的排序結果。
那麼如何才能得到原資料的排序結果呢?我們首先計算 `counter` 的“前綴和”。顧名思義,索引 `i` 處的前綴和 `prefix[i]` 等於陣列前 `i` 個元素之和:
$$
\text{prefix}[i] = \sum_{j=0}^i \text{counter[j]}
$$
**前綴和具有明確的意義,`prefix[num] - 1` 代表元素 `num` 在結果陣列 `res` 中最後一次出現的索引**。這個資訊非常關鍵,因為它告訴我們各個元素應該出現在結果陣列的哪個位置。接下來,我們倒序走訪原陣列 `nums` 的每個元素 `num` ,在每輪迭代中執行以下兩步。
1. 將 `num` 填入陣列 `res` 的索引 `prefix[num] - 1` 處。
2. 令前綴和 `prefix[num]` 減小 $1$ ,從而得到下次放置 `num` 的索引。
走訪完成後,陣列 `res` 中就是排序好的結果,最後使用 `res` 覆蓋原陣列 `nums` 即可。圖 11-17 展示了完整的計數排序流程。
=== "<1>"
![計數排序步驟](counting_sort.assets/counting_sort_step1.png){ class="animation-figure" }
=== "<2>"
![counting_sort_step2](counting_sort.assets/counting_sort_step2.png){ class="animation-figure" }
=== "<3>"
![counting_sort_step3](counting_sort.assets/counting_sort_step3.png){ class="animation-figure" }
=== "<4>"
![counting_sort_step4](counting_sort.assets/counting_sort_step4.png){ class="animation-figure" }
=== "<5>"
![counting_sort_step5](counting_sort.assets/counting_sort_step5.png){ class="animation-figure" }
=== "<6>"
![counting_sort_step6](counting_sort.assets/counting_sort_step6.png){ class="animation-figure" }
=== "<7>"
![counting_sort_step7](counting_sort.assets/counting_sort_step7.png){ class="animation-figure" }
=== "<8>"
![counting_sort_step8](counting_sort.assets/counting_sort_step8.png){ class="animation-figure" }
<p align="center"> 圖 11-17 &nbsp; 計數排序步驟 </p>
計數排序的實現程式碼如下所示:
=== "Python"
```python title="counting_sort.py"
def counting_sort(nums: list[int]):
"""計數排序"""
# 完整實現,可排序物件,並且是穩定排序
# 1. 統計陣列最大元素 m
m = max(nums)
# 2. 統計各數字的出現次數
# counter[num] 代表 num 的出現次數
counter = [0] * (m + 1)
for num in nums:
counter[num] += 1
# 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引”
# 即 counter[num]-1 是 num 在 res 中最後一次出現的索引
for i in range(m):
counter[i + 1] += counter[i]
# 4. 倒序走訪 nums ,將各元素填入結果陣列 res
# 初始化陣列 res 用於記錄結果
n = len(nums)
res = [0] * n
for i in range(n - 1, -1, -1):
num = nums[i]
res[counter[num] - 1] = num # 將 num 放置到對應索引處
counter[num] -= 1 # 令前綴和自減 1 ,得到下次放置 num 的索引
# 使用結果陣列 res 覆蓋原陣列 nums
for i in range(n):
nums[i] = res[i]
```
=== "C++"
```cpp title="counting_sort.cpp"
/* 計數排序 */
// 完整實現,可排序物件,並且是穩定排序
void countingSort(vector<int> &nums) {
// 1. 統計陣列最大元素 m
int m = 0;
for (int num : nums) {
m = max(m, num);
}
// 2. 統計各數字的出現次數
// counter[num] 代表 num 的出現次數
vector<int> counter(m + 1, 0);
for (int num : nums) {
counter[num]++;
}
// 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引”
// 即 counter[num]-1 是 num 在 res 中最後一次出現的索引
for (int i = 0; i < m; i++) {
counter[i + 1] += counter[i];
}
// 4. 倒序走訪 nums ,將各元素填入結果陣列 res
// 初始化陣列 res 用於記錄結果
int n = nums.size();
vector<int> res(n);
for (int i = n - 1; i >= 0; i--) {
int num = nums[i];
res[counter[num] - 1] = num; // 將 num 放置到對應索引處
counter[num]--; // 令前綴和自減 1 ,得到下次放置 num 的索引
}
// 使用結果陣列 res 覆蓋原陣列 nums
nums = res;
}
```
=== "Java"
```java title="counting_sort.java"
/* 計數排序 */
// 完整實現,可排序物件,並且是穩定排序
void countingSort(int[] nums) {
// 1. 統計陣列最大元素 m
int m = 0;
for (int num : nums) {
m = Math.max(m, num);
}
// 2. 統計各數字的出現次數
// counter[num] 代表 num 的出現次數
int[] counter = new int[m + 1];
for (int num : nums) {
counter[num]++;
}
// 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引”
// 即 counter[num]-1 是 num 在 res 中最後一次出現的索引
for (int i = 0; i < m; i++) {
counter[i + 1] += counter[i];
}
// 4. 倒序走訪 nums ,將各元素填入結果陣列 res
// 初始化陣列 res 用於記錄結果
int n = nums.length;
int[] res = new int[n];
for (int i = n - 1; i >= 0; i--) {
int num = nums[i];
res[counter[num] - 1] = num; // 將 num 放置到對應索引處
counter[num]--; // 令前綴和自減 1 ,得到下次放置 num 的索引
}
// 使用結果陣列 res 覆蓋原陣列 nums
for (int i = 0; i < n; i++) {
nums[i] = res[i];
}
}
```
=== "C#"
```csharp title="counting_sort.cs"
/* 計數排序 */
// 完整實現,可排序物件,並且是穩定排序
void CountingSort(int[] nums) {
// 1. 統計陣列最大元素 m
int m = 0;
foreach (int num in nums) {
m = Math.Max(m, num);
}
// 2. 統計各數字的出現次數
// counter[num] 代表 num 的出現次數
int[] counter = new int[m + 1];
foreach (int num in nums) {
counter[num]++;
}
// 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引”
// 即 counter[num]-1 是 num 在 res 中最後一次出現的索引
for (int i = 0; i < m; i++) {
counter[i + 1] += counter[i];
}
// 4. 倒序走訪 nums ,將各元素填入結果陣列 res
// 初始化陣列 res 用於記錄結果
int n = nums.Length;
int[] res = new int[n];
for (int i = n - 1; i >= 0; i--) {
int num = nums[i];
res[counter[num] - 1] = num; // 將 num 放置到對應索引處
counter[num]--; // 令前綴和自減 1 ,得到下次放置 num 的索引
}
// 使用結果陣列 res 覆蓋原陣列 nums
for (int i = 0; i < n; i++) {
nums[i] = res[i];
}
}
```
=== "Go"
```go title="counting_sort.go"
/* 計數排序 */
// 完整實現,可排序物件,並且是穩定排序
func countingSort(nums []int) {
// 1. 統計陣列最大元素 m
m := 0
for _, num := range nums {
if num > m {
m = num
}
}
// 2. 統計各數字的出現次數
// counter[num] 代表 num 的出現次數
counter := make([]int, m+1)
for _, num := range nums {
counter[num]++
}
// 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引”
// 即 counter[num]-1 是 num 在 res 中最後一次出現的索引
for i := 0; i < m; i++ {
counter[i+1] += counter[i]
}
// 4. 倒序走訪 nums ,將各元素填入結果陣列 res
// 初始化陣列 res 用於記錄結果
n := len(nums)
res := make([]int, n)
for i := n - 1; i >= 0; i-- {
num := nums[i]
// 將 num 放置到對應索引處
res[counter[num]-1] = num
// 令前綴和自減 1 ,得到下次放置 num 的索引
counter[num]--
}
// 使用結果陣列 res 覆蓋原陣列 nums
copy(nums, res)
}
```
=== "Swift"
```swift title="counting_sort.swift"
/* 計數排序 */
// 完整實現,可排序物件,並且是穩定排序
func countingSort(nums: inout [Int]) {
// 1. 統計陣列最大元素 m
let m = nums.max()!
// 2. 統計各數字的出現次數
// counter[num] 代表 num 的出現次數
var counter = Array(repeating: 0, count: m + 1)
for num in nums {
counter[num] += 1
}
// 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引”
// 即 counter[num]-1 是 num 在 res 中最後一次出現的索引
for i in 0 ..< m {
counter[i + 1] += counter[i]
}
// 4. 倒序走訪 nums ,將各元素填入結果陣列 res
// 初始化陣列 res 用於記錄結果
var res = Array(repeating: 0, count: nums.count)
for i in nums.indices.reversed() {
let num = nums[i]
res[counter[num] - 1] = num // 將 num 放置到對應索引處
counter[num] -= 1 // 令前綴和自減 1 ,得到下次放置 num 的索引
}
// 使用結果陣列 res 覆蓋原陣列 nums
for i in nums.indices {
nums[i] = res[i]
}
}
```
=== "JS"
```javascript title="counting_sort.js"
/* 計數排序 */
// 完整實現,可排序物件,並且是穩定排序
function countingSort(nums) {
// 1. 統計陣列最大元素 m
let m = 0;
for (const num of nums) {
m = Math.max(m, num);
}
// 2. 統計各數字的出現次數
// counter[num] 代表 num 的出現次數
const counter = new Array(m + 1).fill(0);
for (const num of nums) {
counter[num]++;
}
// 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引”
// 即 counter[num]-1 是 num 在 res 中最後一次出現的索引
for (let i = 0; i < m; i++) {
counter[i + 1] += counter[i];
}
// 4. 倒序走訪 nums ,將各元素填入結果陣列 res
// 初始化陣列 res 用於記錄結果
const n = nums.length;
const res = new Array(n);
for (let i = n - 1; i >= 0; i--) {
const num = nums[i];
res[counter[num] - 1] = num; // 將 num 放置到對應索引處
counter[num]--; // 令前綴和自減 1 ,得到下次放置 num 的索引
}
// 使用結果陣列 res 覆蓋原陣列 nums
for (let i = 0; i < n; i++) {
nums[i] = res[i];
}
}
```
=== "TS"
```typescript title="counting_sort.ts"
/* 計數排序 */
// 完整實現,可排序物件,並且是穩定排序
function countingSort(nums: number[]): void {
// 1. 統計陣列最大元素 m
let m = 0;
for (const num of nums) {
m = Math.max(m, num);
}
// 2. 統計各數字的出現次數
// counter[num] 代表 num 的出現次數
const counter: number[] = new Array<number>(m + 1).fill(0);
for (const num of nums) {
counter[num]++;
}
// 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引”
// 即 counter[num]-1 是 num 在 res 中最後一次出現的索引
for (let i = 0; i < m; i++) {
counter[i + 1] += counter[i];
}
// 4. 倒序走訪 nums ,將各元素填入結果陣列 res
// 初始化陣列 res 用於記錄結果
const n = nums.length;
const res: number[] = new Array<number>(n);
for (let i = n - 1; i >= 0; i--) {
const num = nums[i];
res[counter[num] - 1] = num; // 將 num 放置到對應索引處
counter[num]--; // 令前綴和自減 1 ,得到下次放置 num 的索引
}
// 使用結果陣列 res 覆蓋原陣列 nums
for (let i = 0; i < n; i++) {
nums[i] = res[i];
}
}
```
=== "Dart"
```dart title="counting_sort.dart"
/* 計數排序 */
// 完整實現,可排序物件,並且是穩定排序
void countingSort(List<int> nums) {
// 1. 統計陣列最大元素 m
int m = 0;
for (int _num in nums) {
m = max(m, _num);
}
// 2. 統計各數字的出現次數
// counter[_num] 代表 _num 的出現次數
List<int> counter = List.filled(m + 1, 0);
for (int _num in nums) {
counter[_num]++;
}
// 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引”
// 即 counter[_num]-1 是 _num 在 res 中最後一次出現的索引
for (int i = 0; i < m; i++) {
counter[i + 1] += counter[i];
}
// 4. 倒序走訪 nums ,將各元素填入結果陣列 res
// 初始化陣列 res 用於記錄結果
int n = nums.length;
List<int> res = List.filled(n, 0);
for (int i = n - 1; i >= 0; i--) {
int _num = nums[i];
res[counter[_num] - 1] = _num; // 將 _num 放置到對應索引處
counter[_num]--; // 令前綴和自減 1 ,得到下次放置 _num 的索引
}
// 使用結果陣列 res 覆蓋原陣列 nums
nums.setAll(0, res);
}
```
=== "Rust"
```rust title="counting_sort.rs"
/* 計數排序 */
// 完整實現,可排序物件,並且是穩定排序
fn counting_sort(nums: &mut [i32]) {
// 1. 統計陣列最大元素 m
let m = *nums.into_iter().max().unwrap();
// 2. 統計各數字的出現次數
// counter[num] 代表 num 的出現次數
let mut counter = vec![0; m as usize + 1];
for &num in &*nums {
counter[num as usize] += 1;
}
// 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引”
// 即 counter[num]-1 是 num 在 res 中最後一次出現的索引
for i in 0..m as usize {
counter[i + 1] += counter[i];
}
// 4. 倒序走訪 nums ,將各元素填入結果陣列 res
// 初始化陣列 res 用於記錄結果
let n = nums.len();
let mut res = vec![0; n];
for i in (0..n).rev() {
let num = nums[i];
res[counter[num as usize] - 1] = num; // 將 num 放置到對應索引處
counter[num as usize] -= 1; // 令前綴和自減 1 ,得到下次放置 num 的索引
}
// 使用結果陣列 res 覆蓋原陣列 nums
for i in 0..n {
nums[i] = res[i];
}
}
```
=== "C"
```c title="counting_sort.c"
/* 計數排序 */
// 完整實現,可排序物件,並且是穩定排序
void countingSort(int nums[], int size) {
// 1. 統計陣列最大元素 m
int m = 0;
for (int i = 0; i < size; i++) {
if (nums[i] > m) {
m = nums[i];
}
}
// 2. 統計各數字的出現次數
// counter[num] 代表 num 的出現次數
int *counter = calloc(m, sizeof(int));
for (int i = 0; i < size; i++) {
counter[nums[i]]++;
}
// 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引”
// 即 counter[num]-1 是 num 在 res 中最後一次出現的索引
for (int i = 0; i < m; i++) {
counter[i + 1] += counter[i];
}
// 4. 倒序走訪 nums ,將各元素填入結果陣列 res
// 初始化陣列 res 用於記錄結果
int *res = malloc(sizeof(int) * size);
for (int i = size - 1; i >= 0; i--) {
int num = nums[i];
res[counter[num] - 1] = num; // 將 num 放置到對應索引處
counter[num]--; // 令前綴和自減 1 ,得到下次放置 num 的索引
}
// 使用結果陣列 res 覆蓋原陣列 nums
memcpy(nums, res, size * sizeof(int));
// 5. 釋放記憶體
free(counter);
}
```
=== "Kotlin"
```kotlin title="counting_sort.kt"
/* 計數排序 */
// 完整實現,可排序物件,並且是穩定排序
fun countingSort(nums: IntArray) {
// 1. 統計陣列最大元素 m
var m = 0
for (num in nums) {
m = max(m.toDouble(), num.toDouble()).toInt()
}
// 2. 統計各數字的出現次數
// counter[num] 代表 num 的出現次數
val counter = IntArray(m + 1)
for (num in nums) {
counter[num]++
}
// 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引”
// 即 counter[num]-1 是 num 在 res 中最後一次出現的索引
for (i in 0..<m) {
counter[i + 1] += counter[i]
}
// 4. 倒序走訪 nums ,將各元素填入結果陣列 res
// 初始化陣列 res 用於記錄結果
val n = nums.size
val res = IntArray(n)
for (i in n - 1 downTo 0) {
val num = nums[i]
res[counter[num] - 1] = num // 將 num 放置到對應索引處
counter[num]-- // 令前綴和自減 1 ,得到下次放置 num 的索引
}
// 使用結果陣列 res 覆蓋原陣列 nums
for (i in 0..<n) {
nums[i] = res[i]
}
}
```
=== "Ruby"
```ruby title="counting_sort.rb"
[class]{}-[func]{counting_sort}
```
=== "Zig"
```zig title="counting_sort.zig"
[class]{}-[func]{countingSort}
```
??? pythontutor "視覺化執行"
<div style="height: 549px; width: 100%;"><iframe class="pythontutor-iframe" src="https://pythontutor.com/iframe-embed.html#code=def%20counting_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%AE%8C%E6%95%B4%E5%AE%9E%E7%8E%B0%EF%BC%8C%E5%8F%AF%E6%8E%92%E5%BA%8F%E5%AF%B9%E8%B1%A1%EF%BC%8C%E5%B9%B6%E4%B8%94%E6%98%AF%E7%A8%B3%E5%AE%9A%E6%8E%92%E5%BA%8F%0A%20%20%20%20%23%201.%20%E7%BB%9F%E8%AE%A1%E6%95%B0%E7%BB%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%20m%0A%20%20%20%20m%20%3D%20max%28nums%29%0A%20%20%20%20%23%202.%20%E7%BB%9F%E8%AE%A1%E5%90%84%E6%95%B0%E5%AD%97%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20%23%20counter%5Bnum%5D%20%E4%BB%A3%E8%A1%A8%20num%20%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20counter%20%3D%20%5B0%5D%20*%20%28m%20%2B%201%29%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20%2B%3D%201%0A%20%20%20%20%23%203.%20%E6%B1%82%20counter%20%E7%9A%84%E5%89%8D%E7%BC%80%E5%92%8C%EF%BC%8C%E5%B0%86%E2%80%9C%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%E2%80%9D%E8%BD%AC%E6%8D%A2%E4%B8%BA%E2%80%9C%E5%B0%BE%E7%B4%A2%E5%BC%95%E2%80%9D%0A%20%20%20%20%23%20%E5%8D%B3%20counter%5Bnum%5D-1%20%E6%98%AF%20num%20%E5%9C%A8%20res%20%E4%B8%AD%E6%9C%80%E5%90%8E%E4%B8%80%E6%AC%A1%E5%87%BA%E7%8E%B0%E7%9A%84%E7%B4%A2%E5%BC%95%0A%20%20%20%20for%20i%20in%20range%28m%29%3A%0A%20%20%20%20%20%20%20%20counter%5Bi%20%2B%201%5D%20%2B%3D%20counter%5Bi%5D%0A%20%20%20%20%23%204.%20%E5%80%92%E5%BA%8F%E9%81%8D%E5%8E%86%20nums%20%EF%BC%8C%E5%B0%86%E5%90%84%E5%85%83%E7%B4%A0%E5%A1%AB%E5%85%A5%E7%BB%93%E6%9E%9C%E6%95%B0%E7%BB%84%20res%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%20res%20%E7%94%A8%E4%BA%8E%E8%AE%B0%E5%BD%95%E7%BB%93%E6%9E%9C%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20res%20%3D%20%5B0%5D%20*%20n%0A%20%20%20%20for%20i%20in%20range%28n%20-%201,%20-1,%20-1%29%3A%0A%20%20%20%20%20%20%20%20num%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20res%5Bcounter%5Bnum%5D%20-%201%5D%20%3D%20num%20%20%23%20%E5%B0%86%20num%20%E6%94%BE%E7%BD%AE%E5%88%B0%E5%AF%B9%E5%BA%94%E7%B4%A2%E5%BC%95%E5%A4%84%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20-%3D%201%20%20%23%20%E4%BB%A4%E5%89%8D%E7%BC%80%E5%92%8C%E8%87%AA%E5%87%8F%201%20%EF%BC%8C%E5%BE%97%E5%88%B0%E4%B8%8B%E6%AC%A1%E6%94%BE%E7%BD%AE%20num%20%E7%9A%84%E7%B4%A2%E5%BC%95%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E7%BB%93%E6%9E%9C%E6%95%B0%E7%BB%84%20res%20%E8%A6%86%E7%9B%96%E5%8E%9F%E6%95%B0%E7%BB%84%20nums%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20res%5Bi%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1,%200,%201,%202,%200,%204,%200,%202,%202,%204%5D%0A%20%20%20%20counting_sort%28nums%29%0A%20%20%20%20print%28f%22%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%20%7Bnums%7D%22%29&codeDivHeight=472&codeDivWidth=350&cumulative=false&curInstr=4&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe></div>
<div style="margin-top: 5px;"><a href="https://pythontutor.com/iframe-embed.html#code=def%20counting_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%AE%8C%E6%95%B4%E5%AE%9E%E7%8E%B0%EF%BC%8C%E5%8F%AF%E6%8E%92%E5%BA%8F%E5%AF%B9%E8%B1%A1%EF%BC%8C%E5%B9%B6%E4%B8%94%E6%98%AF%E7%A8%B3%E5%AE%9A%E6%8E%92%E5%BA%8F%0A%20%20%20%20%23%201.%20%E7%BB%9F%E8%AE%A1%E6%95%B0%E7%BB%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%20m%0A%20%20%20%20m%20%3D%20max%28nums%29%0A%20%20%20%20%23%202.%20%E7%BB%9F%E8%AE%A1%E5%90%84%E6%95%B0%E5%AD%97%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20%23%20counter%5Bnum%5D%20%E4%BB%A3%E8%A1%A8%20num%20%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20counter%20%3D%20%5B0%5D%20*%20%28m%20%2B%201%29%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20%2B%3D%201%0A%20%20%20%20%23%203.%20%E6%B1%82%20counter%20%E7%9A%84%E5%89%8D%E7%BC%80%E5%92%8C%EF%BC%8C%E5%B0%86%E2%80%9C%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%E2%80%9D%E8%BD%AC%E6%8D%A2%E4%B8%BA%E2%80%9C%E5%B0%BE%E7%B4%A2%E5%BC%95%E2%80%9D%0A%20%20%20%20%23%20%E5%8D%B3%20counter%5Bnum%5D-1%20%E6%98%AF%20num%20%E5%9C%A8%20res%20%E4%B8%AD%E6%9C%80%E5%90%8E%E4%B8%80%E6%AC%A1%E5%87%BA%E7%8E%B0%E7%9A%84%E7%B4%A2%E5%BC%95%0A%20%20%20%20for%20i%20in%20range%28m%29%3A%0A%20%20%20%20%20%20%20%20counter%5Bi%20%2B%201%5D%20%2B%3D%20counter%5Bi%5D%0A%20%20%20%20%23%204.%20%E5%80%92%E5%BA%8F%E9%81%8D%E5%8E%86%20nums%20%EF%BC%8C%E5%B0%86%E5%90%84%E5%85%83%E7%B4%A0%E5%A1%AB%E5%85%A5%E7%BB%93%E6%9E%9C%E6%95%B0%E7%BB%84%20res%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%20res%20%E7%94%A8%E4%BA%8E%E8%AE%B0%E5%BD%95%E7%BB%93%E6%9E%9C%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20res%20%3D%20%5B0%5D%20*%20n%0A%20%20%20%20for%20i%20in%20range%28n%20-%201,%20-1,%20-1%29%3A%0A%20%20%20%20%20%20%20%20num%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20res%5Bcounter%5Bnum%5D%20-%201%5D%20%3D%20num%20%20%23%20%E5%B0%86%20num%20%E6%94%BE%E7%BD%AE%E5%88%B0%E5%AF%B9%E5%BA%94%E7%B4%A2%E5%BC%95%E5%A4%84%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20-%3D%201%20%20%23%20%E4%BB%A4%E5%89%8D%E7%BC%80%E5%92%8C%E8%87%AA%E5%87%8F%201%20%EF%BC%8C%E5%BE%97%E5%88%B0%E4%B8%8B%E6%AC%A1%E6%94%BE%E7%BD%AE%20num%20%E7%9A%84%E7%B4%A2%E5%BC%95%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E7%BB%93%E6%9E%9C%E6%95%B0%E7%BB%84%20res%20%E8%A6%86%E7%9B%96%E5%8E%9F%E6%95%B0%E7%BB%84%20nums%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20res%5Bi%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1,%200,%201,%202,%200,%204,%200,%202,%202,%204%5D%0A%20%20%20%20counting_sort%28nums%29%0A%20%20%20%20print%28f%22%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%20%7Bnums%7D%22%29&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=4&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">全螢幕觀看 ></a></div>
## 11.9.3 &nbsp; 演算法特性
- **時間複雜度為 $O(n + m)$** :涉及走訪 `nums` 和走訪 `counter` ,都使用線性時間。一般情況下 $n \gg m$ ,時間複雜度趨於 $O(n)$ 。
- **空間複雜度為 $O(n + m)$、非原地排序**:藉助了長度分別為 $n$ 和 $m$ 的陣列 `res` 和 `counter` 。
- **穩定排序**:由於向 `res` 中填充元素的順序是“從右向左”的,因此倒序走訪 `nums` 可以避免改變相等元素之間的相對位置,從而實現穩定排序。實際上,正序走訪 `nums` 也可以得到正確的排序結果,但結果是非穩定的。
## 11.9.4 &nbsp; 侷限性
看到這裡,你也許會覺得計數排序非常巧妙,僅透過統計數量就可以實現高效的排序。然而,使用計數排序的前置條件相對較為嚴格。
**計數排序只適用於非負整數**。若想將其用於其他型別的資料,需要確保這些資料可以轉換為非負整數,並且在轉換過程中不能改變各個元素之間的相對大小關係。例如,對於包含負數的整數陣列,可以先給所有數字加上一個常數,將全部數字轉化為正數,排序完成後再轉換回去。
**計數排序適用於資料量大但資料範圍較小的情況**。比如,在上述示例中 $m$ 不能太大,否則會佔用過多空間。而當 $n \ll m$ 時,計數排序使用 $O(m)$ 時間,可能比 $O(n \log n)$ 的排序演算法還要慢。
+600
View File
@@ -0,0 +1,600 @@
---
comments: true
---
# 11.7 &nbsp; 堆積排序
!!! tip
閱讀本節前,請確保已學完“堆積“章節。
<u>堆積排序(heap sort</u>是一種基於堆積資料結構實現的高效排序演算法。我們可以利用已經學過的“建堆積操作”和“元素出堆積操作”實現堆積排序。
1. 輸入陣列並建立小頂堆積,此時最小元素位於堆積頂。
2. 不斷執行出堆積操作,依次記錄出堆積元素,即可得到從小到大排序的序列。
以上方法雖然可行,但需要藉助一個額外陣列來儲存彈出的元素,比較浪費空間。在實際中,我們通常使用一種更加優雅的實現方式。
## 11.7.1 &nbsp; 演算法流程
設陣列的長度為 $n$ ,堆積排序的流程如圖 11-12 所示。
1. 輸入陣列並建立大頂堆積。完成後,最大元素位於堆積頂。
2. 將堆積頂元素(第一個元素)與堆積底元素(最後一個元素)交換。完成交換後,堆積的長度減 $1$ ,已排序元素數量加 $1$ 。
3. 從堆積頂元素開始,從頂到底執行堆積化操作(sift down)。完成堆積化後,堆積的性質得到修復。
4. 迴圈執行第 `2.` 步和第 `3.` 步。迴圈 $n - 1$ 輪後,即可完成陣列排序。
!!! tip
實際上,元素出堆積操作中也包含第 `2.` 步和第 `3.` 步,只是多了一個彈出元素的步驟。
=== "<1>"
![堆積排序步驟](heap_sort.assets/heap_sort_step1.png){ class="animation-figure" }
=== "<2>"
![heap_sort_step2](heap_sort.assets/heap_sort_step2.png){ class="animation-figure" }
=== "<3>"
![heap_sort_step3](heap_sort.assets/heap_sort_step3.png){ class="animation-figure" }
=== "<4>"
![heap_sort_step4](heap_sort.assets/heap_sort_step4.png){ class="animation-figure" }
=== "<5>"
![heap_sort_step5](heap_sort.assets/heap_sort_step5.png){ class="animation-figure" }
=== "<6>"
![heap_sort_step6](heap_sort.assets/heap_sort_step6.png){ class="animation-figure" }
=== "<7>"
![heap_sort_step7](heap_sort.assets/heap_sort_step7.png){ class="animation-figure" }
=== "<8>"
![heap_sort_step8](heap_sort.assets/heap_sort_step8.png){ class="animation-figure" }
=== "<9>"
![heap_sort_step9](heap_sort.assets/heap_sort_step9.png){ class="animation-figure" }
=== "<10>"
![heap_sort_step10](heap_sort.assets/heap_sort_step10.png){ class="animation-figure" }
=== "<11>"
![heap_sort_step11](heap_sort.assets/heap_sort_step11.png){ class="animation-figure" }
=== "<12>"
![heap_sort_step12](heap_sort.assets/heap_sort_step12.png){ class="animation-figure" }
<p align="center"> 圖 11-12 &nbsp; 堆積排序步驟 </p>
在程式碼實現中,我們使用了與“堆積”章節相同的從頂至底堆積化 `sift_down()` 函式。值得注意的是,由於堆積的長度會隨著提取最大元素而減小,因此我們需要給 `sift_down()` 函式新增一個長度參數 $n$ ,用於指定堆積的當前有效長度。程式碼如下所示:
=== "Python"
```python title="heap_sort.py"
def sift_down(nums: list[int], n: int, i: int):
"""堆積的長度為 n ,從節點 i 開始,從頂至底堆積化"""
while True:
# 判斷節點 i, l, r 中值最大的節點,記為 ma
l = 2 * i + 1
r = 2 * i + 2
ma = i
if l < n and nums[l] > nums[ma]:
ma = l
if r < n and nums[r] > nums[ma]:
ma = r
# 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出
if ma == i:
break
# 交換兩節點
nums[i], nums[ma] = nums[ma], nums[i]
# 迴圈向下堆積化
i = ma
def heap_sort(nums: list[int]):
"""堆積排序"""
# 建堆積操作:堆積化除葉節點以外的其他所有節點
for i in range(len(nums) // 2 - 1, -1, -1):
sift_down(nums, len(nums), i)
# 從堆積中提取最大元素,迴圈 n-1 輪
for i in range(len(nums) - 1, 0, -1):
# 交換根節點與最右葉節點(交換首元素與尾元素)
nums[0], nums[i] = nums[i], nums[0]
# 以根節點為起點,從頂至底進行堆積化
sift_down(nums, i, 0)
```
=== "C++"
```cpp title="heap_sort.cpp"
/* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */
void siftDown(vector<int> &nums, int n, int i) {
while (true) {
// 判斷節點 i, l, r 中值最大的節點,記為 ma
int l = 2 * i + 1;
int r = 2 * i + 2;
int ma = i;
if (l < n && nums[l] > nums[ma])
ma = l;
if (r < n && nums[r] > nums[ma])
ma = r;
// 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出
if (ma == i) {
break;
}
// 交換兩節點
swap(nums[i], nums[ma]);
// 迴圈向下堆積化
i = ma;
}
}
/* 堆積排序 */
void heapSort(vector<int> &nums) {
// 建堆積操作:堆積化除葉節點以外的其他所有節點
for (int i = nums.size() / 2 - 1; i >= 0; --i) {
siftDown(nums, nums.size(), i);
}
// 從堆積中提取最大元素,迴圈 n-1 輪
for (int i = nums.size() - 1; i > 0; --i) {
// 交換根節點與最右葉節點(交換首元素與尾元素)
swap(nums[0], nums[i]);
// 以根節點為起點,從頂至底進行堆積化
siftDown(nums, i, 0);
}
}
```
=== "Java"
```java title="heap_sort.java"
/* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */
void siftDown(int[] nums, int n, int i) {
while (true) {
// 判斷節點 i, l, r 中值最大的節點,記為 ma
int l = 2 * i + 1;
int r = 2 * i + 2;
int ma = i;
if (l < n && nums[l] > nums[ma])
ma = l;
if (r < n && nums[r] > nums[ma])
ma = r;
// 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出
if (ma == i)
break;
// 交換兩節點
int temp = nums[i];
nums[i] = nums[ma];
nums[ma] = temp;
// 迴圈向下堆積化
i = ma;
}
}
/* 堆積排序 */
void heapSort(int[] nums) {
// 建堆積操作:堆積化除葉節點以外的其他所有節點
for (int i = nums.length / 2 - 1; i >= 0; i--) {
siftDown(nums, nums.length, i);
}
// 從堆積中提取最大元素,迴圈 n-1 輪
for (int i = nums.length - 1; i > 0; i--) {
// 交換根節點與最右葉節點(交換首元素與尾元素)
int tmp = nums[0];
nums[0] = nums[i];
nums[i] = tmp;
// 以根節點為起點,從頂至底進行堆積化
siftDown(nums, i, 0);
}
}
```
=== "C#"
```csharp title="heap_sort.cs"
/* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */
void SiftDown(int[] nums, int n, int i) {
while (true) {
// 判斷節點 i, l, r 中值最大的節點,記為 ma
int l = 2 * i + 1;
int r = 2 * i + 2;
int ma = i;
if (l < n && nums[l] > nums[ma])
ma = l;
if (r < n && nums[r] > nums[ma])
ma = r;
// 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出
if (ma == i)
break;
// 交換兩節點
(nums[ma], nums[i]) = (nums[i], nums[ma]);
// 迴圈向下堆積化
i = ma;
}
}
/* 堆積排序 */
void HeapSort(int[] nums) {
// 建堆積操作:堆積化除葉節點以外的其他所有節點
for (int i = nums.Length / 2 - 1; i >= 0; i--) {
SiftDown(nums, nums.Length, i);
}
// 從堆積中提取最大元素,迴圈 n-1 輪
for (int i = nums.Length - 1; i > 0; i--) {
// 交換根節點與最右葉節點(交換首元素與尾元素)
(nums[i], nums[0]) = (nums[0], nums[i]);
// 以根節點為起點,從頂至底進行堆積化
SiftDown(nums, i, 0);
}
}
```
=== "Go"
```go title="heap_sort.go"
/* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */
func siftDown(nums *[]int, n, i int) {
for true {
// 判斷節點 i, l, r 中值最大的節點,記為 ma
l := 2*i + 1
r := 2*i + 2
ma := i
if l < n && (*nums)[l] > (*nums)[ma] {
ma = l
}
if r < n && (*nums)[r] > (*nums)[ma] {
ma = r
}
// 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出
if ma == i {
break
}
// 交換兩節點
(*nums)[i], (*nums)[ma] = (*nums)[ma], (*nums)[i]
// 迴圈向下堆積化
i = ma
}
}
/* 堆積排序 */
func heapSort(nums *[]int) {
// 建堆積操作:堆積化除葉節點以外的其他所有節點
for i := len(*nums)/2 - 1; i >= 0; i-- {
siftDown(nums, len(*nums), i)
}
// 從堆積中提取最大元素,迴圈 n-1 輪
for i := len(*nums) - 1; i > 0; i-- {
// 交換根節點與最右葉節點(交換首元素與尾元素)
(*nums)[0], (*nums)[i] = (*nums)[i], (*nums)[0]
// 以根節點為起點,從頂至底進行堆積化
siftDown(nums, i, 0)
}
}
```
=== "Swift"
```swift title="heap_sort.swift"
/* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */
func siftDown(nums: inout [Int], n: Int, i: Int) {
var i = i
while true {
// 判斷節點 i, l, r 中值最大的節點,記為 ma
let l = 2 * i + 1
let r = 2 * i + 2
var ma = i
if l < n, nums[l] > nums[ma] {
ma = l
}
if r < n, nums[r] > nums[ma] {
ma = r
}
// 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出
if ma == i {
break
}
// 交換兩節點
nums.swapAt(i, ma)
// 迴圈向下堆積化
i = ma
}
}
/* 堆積排序 */
func heapSort(nums: inout [Int]) {
// 建堆積操作:堆積化除葉節點以外的其他所有節點
for i in stride(from: nums.count / 2 - 1, through: 0, by: -1) {
siftDown(nums: &nums, n: nums.count, i: i)
}
// 從堆積中提取最大元素,迴圈 n-1 輪
for i in nums.indices.dropFirst().reversed() {
// 交換根節點與最右葉節點(交換首元素與尾元素)
nums.swapAt(0, i)
// 以根節點為起點,從頂至底進行堆積化
siftDown(nums: &nums, n: i, i: 0)
}
}
```
=== "JS"
```javascript title="heap_sort.js"
/* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */
function siftDown(nums, n, i) {
while (true) {
// 判斷節點 i, l, r 中值最大的節點,記為 ma
let l = 2 * i + 1;
let r = 2 * i + 2;
let ma = i;
if (l < n && nums[l] > nums[ma]) {
ma = l;
}
if (r < n && nums[r] > nums[ma]) {
ma = r;
}
// 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出
if (ma === i) {
break;
}
// 交換兩節點
[nums[i], nums[ma]] = [nums[ma], nums[i]];
// 迴圈向下堆積化
i = ma;
}
}
/* 堆積排序 */
function heapSort(nums) {
// 建堆積操作:堆積化除葉節點以外的其他所有節點
for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) {
siftDown(nums, nums.length, i);
}
// 從堆積中提取最大元素,迴圈 n-1 輪
for (let i = nums.length - 1; i > 0; i--) {
// 交換根節點與最右葉節點(交換首元素與尾元素)
[nums[0], nums[i]] = [nums[i], nums[0]];
// 以根節點為起點,從頂至底進行堆積化
siftDown(nums, i, 0);
}
}
```
=== "TS"
```typescript title="heap_sort.ts"
/* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */
function siftDown(nums: number[], n: number, i: number): void {
while (true) {
// 判斷節點 i, l, r 中值最大的節點,記為 ma
let l = 2 * i + 1;
let r = 2 * i + 2;
let ma = i;
if (l < n && nums[l] > nums[ma]) {
ma = l;
}
if (r < n && nums[r] > nums[ma]) {
ma = r;
}
// 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出
if (ma === i) {
break;
}
// 交換兩節點
[nums[i], nums[ma]] = [nums[ma], nums[i]];
// 迴圈向下堆積化
i = ma;
}
}
/* 堆積排序 */
function heapSort(nums: number[]): void {
// 建堆積操作:堆積化除葉節點以外的其他所有節點
for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) {
siftDown(nums, nums.length, i);
}
// 從堆積中提取最大元素,迴圈 n-1 輪
for (let i = nums.length - 1; i > 0; i--) {
// 交換根節點與最右葉節點(交換首元素與尾元素)
[nums[0], nums[i]] = [nums[i], nums[0]];
// 以根節點為起點,從頂至底進行堆積化
siftDown(nums, i, 0);
}
}
```
=== "Dart"
```dart title="heap_sort.dart"
/* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */
void siftDown(List<int> nums, int n, int i) {
while (true) {
// 判斷節點 i, l, r 中值最大的節點,記為 ma
int l = 2 * i + 1;
int r = 2 * i + 2;
int ma = i;
if (l < n && nums[l] > nums[ma]) ma = l;
if (r < n && nums[r] > nums[ma]) ma = r;
// 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出
if (ma == i) break;
// 交換兩節點
int temp = nums[i];
nums[i] = nums[ma];
nums[ma] = temp;
// 迴圈向下堆積化
i = ma;
}
}
/* 堆積排序 */
void heapSort(List<int> nums) {
// 建堆積操作:堆積化除葉節點以外的其他所有節點
for (int i = nums.length ~/ 2 - 1; i >= 0; i--) {
siftDown(nums, nums.length, i);
}
// 從堆積中提取最大元素,迴圈 n-1 輪
for (int i = nums.length - 1; i > 0; i--) {
// 交換根節點與最右葉節點(交換首元素與尾元素)
int tmp = nums[0];
nums[0] = nums[i];
nums[i] = tmp;
// 以根節點為起點,從頂至底進行堆積化
siftDown(nums, i, 0);
}
}
```
=== "Rust"
```rust title="heap_sort.rs"
/* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */
fn sift_down(nums: &mut [i32], n: usize, mut i: usize) {
loop {
// 判斷節點 i, l, r 中值最大的節點,記為 ma
let l = 2 * i + 1;
let r = 2 * i + 2;
let mut ma = i;
if l < n && nums[l] > nums[ma] {
ma = l;
}
if r < n && nums[r] > nums[ma] {
ma = r;
}
// 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出
if ma == i {
break;
}
// 交換兩節點
let temp = nums[i];
nums[i] = nums[ma];
nums[ma] = temp;
// 迴圈向下堆積化
i = ma;
}
}
/* 堆積排序 */
fn heap_sort(nums: &mut [i32]) {
// 建堆積操作:堆積化除葉節點以外的其他所有節點
for i in (0..=nums.len() / 2 - 1).rev() {
sift_down(nums, nums.len(), i);
}
// 從堆積中提取最大元素,迴圈 n-1 輪
for i in (1..=nums.len() - 1).rev() {
// 交換根節點與最右葉節點(交換首元素與尾元素)
let tmp = nums[0];
nums[0] = nums[i];
nums[i] = tmp;
// 以根節點為起點,從頂至底進行堆積化
sift_down(nums, i, 0);
}
}
```
=== "C"
```c title="heap_sort.c"
/* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */
void siftDown(int nums[], int n, int i) {
while (1) {
// 判斷節點 i, l, r 中值最大的節點,記為 ma
int l = 2 * i + 1;
int r = 2 * i + 2;
int ma = i;
if (l < n && nums[l] > nums[ma])
ma = l;
if (r < n && nums[r] > nums[ma])
ma = r;
// 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出
if (ma == i) {
break;
}
// 交換兩節點
int temp = nums[i];
nums[i] = nums[ma];
nums[ma] = temp;
// 迴圈向下堆積化
i = ma;
}
}
/* 堆積排序 */
void heapSort(int nums[], int n) {
// 建堆積操作:堆積化除葉節點以外的其他所有節點
for (int i = n / 2 - 1; i >= 0; --i) {
siftDown(nums, n, i);
}
// 從堆積中提取最大元素,迴圈 n-1 輪
for (int i = n - 1; i > 0; --i) {
// 交換根節點與最右葉節點(交換首元素與尾元素)
int tmp = nums[0];
nums[0] = nums[i];
nums[i] = tmp;
// 以根節點為起點,從頂至底進行堆積化
siftDown(nums, i, 0);
}
}
```
=== "Kotlin"
```kotlin title="heap_sort.kt"
/* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */
fun siftDown(nums: IntArray, n: Int, li: Int) {
var i = li
while (true) {
// 判斷節點 i, l, r 中值最大的節點,記為 ma
val l = 2 * i + 1
val r = 2 * i + 2
var ma = i
if (l < n && nums[l] > nums[ma]) ma = l
if (r < n && nums[r] > nums[ma]) ma = r
// 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出
if (ma == i) break
// 交換兩節點
nums[i] = nums[ma].also { nums[ma] = nums[i] }
// 迴圈向下堆積化
i = ma
}
}
/* 堆積排序 */
fun heapSort(nums: IntArray) {
// 建堆積操作:堆積化除葉節點以外的其他所有節點
for (i in nums.size / 2 - 1 downTo 0) {
siftDown(nums, nums.size, i)
}
// 從堆積中提取最大元素,迴圈 n-1 輪
for (i in nums.size - 1 downTo 1) {
// 交換根節點與最右葉節點(交換首元素與尾元素)
nums[0] = nums[i].also { nums[i] = nums[0] }
// 以根節點為起點,從頂至底進行堆積化
siftDown(nums, i, 0)
}
}
```
=== "Ruby"
```ruby title="heap_sort.rb"
[class]{}-[func]{sift_down}
[class]{}-[func]{heap_sort}
```
=== "Zig"
```zig title="heap_sort.zig"
[class]{}-[func]{siftDown}
[class]{}-[func]{heapSort}
```
??? pythontutor "視覺化執行"
<div style="height: 549px; width: 100%;"><iframe class="pythontutor-iframe" src="https://pythontutor.com/iframe-embed.html#code=def%20sift_down%28nums%3A%20list%5Bint%5D,%20n%3A%20int,%20i%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%A0%86%E7%9A%84%E9%95%BF%E5%BA%A6%E4%B8%BA%20n%20%EF%BC%8C%E4%BB%8E%E8%8A%82%E7%82%B9%20i%20%E5%BC%80%E5%A7%8B%EF%BC%8C%E4%BB%8E%E9%A1%B6%E8%87%B3%E5%BA%95%E5%A0%86%E5%8C%96%22%22%22%0A%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E8%8A%82%E7%82%B9%20i,%20l,%20r%20%E4%B8%AD%E5%80%BC%E6%9C%80%E5%A4%A7%E7%9A%84%E8%8A%82%E7%82%B9%EF%BC%8C%E8%AE%B0%E4%B8%BA%20ma%0A%20%20%20%20%20%20%20%20l%20%3D%202%20*%20i%20%2B%201%0A%20%20%20%20%20%20%20%20r%20%3D%202%20*%20i%20%2B%202%0A%20%20%20%20%20%20%20%20ma%20%3D%20i%0A%20%20%20%20%20%20%20%20if%20l%20%3C%20n%20and%20nums%5Bl%5D%20%3E%20nums%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20l%0A%20%20%20%20%20%20%20%20if%20r%20%3C%20n%20and%20nums%5Br%5D%20%3E%20nums%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20r%0A%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%8A%82%E7%82%B9%20i%20%E6%9C%80%E5%A4%A7%E6%88%96%E7%B4%A2%E5%BC%95%20l,%20r%20%E8%B6%8A%E7%95%8C%EF%BC%8C%E5%88%99%E6%97%A0%E9%A1%BB%E7%BB%A7%E7%BB%AD%E5%A0%86%E5%8C%96%EF%BC%8C%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20if%20ma%20%3D%3D%20i%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%E4%B8%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20nums%5Bi%5D,%20nums%5Bma%5D%20%3D%20nums%5Bma%5D,%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E5%90%91%E4%B8%8B%E5%A0%86%E5%8C%96%0A%20%20%20%20%20%20%20%20i%20%3D%20ma%0A%0Adef%20heap_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E5%A0%86%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%BB%BA%E5%A0%86%E6%93%8D%E4%BD%9C%EF%BC%9A%E5%A0%86%E5%8C%96%E9%99%A4%E5%8F%B6%E8%8A%82%E7%82%B9%E4%BB%A5%E5%A4%96%E7%9A%84%E5%85%B6%E4%BB%96%E6%89%80%E6%9C%89%E8%8A%82%E7%82%B9%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20//%202%20-%201,%20-1,%20-1%29%3A%0A%20%20%20%20%20%20%20%20sift_down%28nums,%20len%28nums%29,%20i%29%0A%20%20%20%20%23%20%E4%BB%8E%E5%A0%86%E4%B8%AD%E6%8F%90%E5%8F%96%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%EF%BC%8C%E5%BE%AA%E7%8E%AF%20n-1%20%E8%BD%AE%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201,%200,%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%E6%A0%B9%E8%8A%82%E7%82%B9%E4%B8%8E%E6%9C%80%E5%8F%B3%E5%8F%B6%E8%8A%82%E7%82%B9%EF%BC%88%E4%BA%A4%E6%8D%A2%E9%A6%96%E5%85%83%E7%B4%A0%E4%B8%8E%E5%B0%BE%E5%85%83%E7%B4%A0%EF%BC%89%0A%20%20%20%20%20%20%20%20nums%5B0%5D,%20nums%5Bi%5D%20%3D%20nums%5Bi%5D,%20nums%5B0%5D%0A%20%20%20%20%20%20%20%20%23%20%E4%BB%A5%E6%A0%B9%E8%8A%82%E7%82%B9%E4%B8%BA%E8%B5%B7%E7%82%B9%EF%BC%8C%E4%BB%8E%E9%A1%B6%E8%87%B3%E5%BA%95%E8%BF%9B%E8%A1%8C%E5%A0%86%E5%8C%96%0A%20%20%20%20%20%20%20%20sift_down%28nums,%20i,%200%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4,%201,%203,%201,%205,%202%5D%0A%20%20%20%20heap_sort%28nums%29%0A%20%20%20%20print%28%22%E5%A0%86%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&codeDivHeight=472&codeDivWidth=350&cumulative=false&curInstr=5&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe></div>
<div style="margin-top: 5px;"><a href="https://pythontutor.com/iframe-embed.html#code=def%20sift_down%28nums%3A%20list%5Bint%5D,%20n%3A%20int,%20i%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%A0%86%E7%9A%84%E9%95%BF%E5%BA%A6%E4%B8%BA%20n%20%EF%BC%8C%E4%BB%8E%E8%8A%82%E7%82%B9%20i%20%E5%BC%80%E5%A7%8B%EF%BC%8C%E4%BB%8E%E9%A1%B6%E8%87%B3%E5%BA%95%E5%A0%86%E5%8C%96%22%22%22%0A%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E8%8A%82%E7%82%B9%20i,%20l,%20r%20%E4%B8%AD%E5%80%BC%E6%9C%80%E5%A4%A7%E7%9A%84%E8%8A%82%E7%82%B9%EF%BC%8C%E8%AE%B0%E4%B8%BA%20ma%0A%20%20%20%20%20%20%20%20l%20%3D%202%20*%20i%20%2B%201%0A%20%20%20%20%20%20%20%20r%20%3D%202%20*%20i%20%2B%202%0A%20%20%20%20%20%20%20%20ma%20%3D%20i%0A%20%20%20%20%20%20%20%20if%20l%20%3C%20n%20and%20nums%5Bl%5D%20%3E%20nums%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20l%0A%20%20%20%20%20%20%20%20if%20r%20%3C%20n%20and%20nums%5Br%5D%20%3E%20nums%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20r%0A%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%8A%82%E7%82%B9%20i%20%E6%9C%80%E5%A4%A7%E6%88%96%E7%B4%A2%E5%BC%95%20l,%20r%20%E8%B6%8A%E7%95%8C%EF%BC%8C%E5%88%99%E6%97%A0%E9%A1%BB%E7%BB%A7%E7%BB%AD%E5%A0%86%E5%8C%96%EF%BC%8C%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20if%20ma%20%3D%3D%20i%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%E4%B8%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20nums%5Bi%5D,%20nums%5Bma%5D%20%3D%20nums%5Bma%5D,%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E5%90%91%E4%B8%8B%E5%A0%86%E5%8C%96%0A%20%20%20%20%20%20%20%20i%20%3D%20ma%0A%0Adef%20heap_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E5%A0%86%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%BB%BA%E5%A0%86%E6%93%8D%E4%BD%9C%EF%BC%9A%E5%A0%86%E5%8C%96%E9%99%A4%E5%8F%B6%E8%8A%82%E7%82%B9%E4%BB%A5%E5%A4%96%E7%9A%84%E5%85%B6%E4%BB%96%E6%89%80%E6%9C%89%E8%8A%82%E7%82%B9%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20//%202%20-%201,%20-1,%20-1%29%3A%0A%20%20%20%20%20%20%20%20sift_down%28nums,%20len%28nums%29,%20i%29%0A%20%20%20%20%23%20%E4%BB%8E%E5%A0%86%E4%B8%AD%E6%8F%90%E5%8F%96%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%EF%BC%8C%E5%BE%AA%E7%8E%AF%20n-1%20%E8%BD%AE%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201,%200,%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%E6%A0%B9%E8%8A%82%E7%82%B9%E4%B8%8E%E6%9C%80%E5%8F%B3%E5%8F%B6%E8%8A%82%E7%82%B9%EF%BC%88%E4%BA%A4%E6%8D%A2%E9%A6%96%E5%85%83%E7%B4%A0%E4%B8%8E%E5%B0%BE%E5%85%83%E7%B4%A0%EF%BC%89%0A%20%20%20%20%20%20%20%20nums%5B0%5D,%20nums%5Bi%5D%20%3D%20nums%5Bi%5D,%20nums%5B0%5D%0A%20%20%20%20%20%20%20%20%23%20%E4%BB%A5%E6%A0%B9%E8%8A%82%E7%82%B9%E4%B8%BA%E8%B5%B7%E7%82%B9%EF%BC%8C%E4%BB%8E%E9%A1%B6%E8%87%B3%E5%BA%95%E8%BF%9B%E8%A1%8C%E5%A0%86%E5%8C%96%0A%20%20%20%20%20%20%20%20sift_down%28nums,%20i,%200%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4,%201,%203,%201,%205,%202%5D%0A%20%20%20%20heap_sort%28nums%29%0A%20%20%20%20print%28%22%E5%A0%86%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=5&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">全螢幕觀看 ></a></div>
## 11.7.2 &nbsp; 演算法特性
- **時間複雜度為 $O(n \log n)$、非自適應排序**:建堆積操作使用 $O(n)$ 時間。從堆積中提取最大元素的時間複雜度為 $O(\log n)$ ,共迴圈 $n - 1$ 輪。
- **空間複雜度為 $O(1)$、原地排序**:幾個指標變數使用 $O(1)$ 空間。元素交換和堆積化操作都是在原陣列上進行的。
- **非穩定排序**:在交換堆積頂元素和堆積底元素時,相等元素的相對位置可能發生變化。
+28
View File
@@ -0,0 +1,28 @@
---
comments: true
icon: material/sort-ascending
---
# 第 11 章 &nbsp; 排序
![排序](../assets/covers/chapter_sorting.jpg){ class="cover-image" }
!!! abstract
排序猶如一把將混亂變為秩序的魔法鑰匙,使我們能以更高效的方式理解與處理資料。
無論是簡單的升序,還是複雜的分類排列,排序都向我們展示了資料的和諧美感。
## Chapter Contents
- [11.1 &nbsp; 排序演算法](https://www.hello-algo.com/en/chapter_sorting/sorting_algorithm/)
- [11.2 &nbsp; 選擇排序](https://www.hello-algo.com/en/chapter_sorting/selection_sort/)
- [11.3 &nbsp; 泡沫排序](https://www.hello-algo.com/en/chapter_sorting/bubble_sort/)
- [11.4 &nbsp; 插入排序](https://www.hello-algo.com/en/chapter_sorting/insertion_sort/)
- [11.5 &nbsp; 快速排序](https://www.hello-algo.com/en/chapter_sorting/quick_sort/)
- [11.6 &nbsp; 合併排序](https://www.hello-algo.com/en/chapter_sorting/merge_sort/)
- [11.7 &nbsp; 堆積排序](https://www.hello-algo.com/en/chapter_sorting/heap_sort/)
- [11.8 &nbsp; 桶排序](https://www.hello-algo.com/en/chapter_sorting/bucket_sort/)
- [11.9 &nbsp; 計數排序](https://www.hello-algo.com/en/chapter_sorting/counting_sort/)
- [11.10 &nbsp; 基數排序](https://www.hello-algo.com/en/chapter_sorting/radix_sort/)
- [11.11 &nbsp; 小結](https://www.hello-algo.com/en/chapter_sorting/summary/)
+301
View File
@@ -0,0 +1,301 @@
---
comments: true
---
# 11.4 &nbsp; 插入排序
<u>插入排序(insertion sort</u>是一種簡單的排序演算法,它的工作原理與手動整理一副牌的過程非常相似。
具體來說,我們在未排序區間選擇一個基準元素,將該元素與其左側已排序區間的元素逐一比較大小,並將該元素插入到正確的位置。
圖 11-6 展示了陣列插入元素的操作流程。設基準元素為 `base` ,我們需要將從目標索引到 `base` 之間的所有元素向右移動一位,然後將 `base` 賦值給目標索引。
![單次插入操作](insertion_sort.assets/insertion_operation.png){ class="animation-figure" }
<p align="center"> 圖 11-6 &nbsp; 單次插入操作 </p>
## 11.4.1 &nbsp; 演算法流程
插入排序的整體流程如圖 11-7 所示。
1. 初始狀態下,陣列的第 1 個元素已完成排序。
2. 選取陣列的第 2 個元素作為 `base` ,將其插入到正確位置後,**陣列的前 2 個元素已排序**。
3. 選取第 3 個元素作為 `base` ,將其插入到正確位置後,**陣列的前 3 個元素已排序**。
4. 以此類推,在最後一輪中,選取最後一個元素作為 `base` ,將其插入到正確位置後,**所有元素均已排序**。
![插入排序流程](insertion_sort.assets/insertion_sort_overview.png){ class="animation-figure" }
<p align="center"> 圖 11-7 &nbsp; 插入排序流程 </p>
示例程式碼如下:
=== "Python"
```python title="insertion_sort.py"
def insertion_sort(nums: list[int]):
"""插入排序"""
# 外迴圈:已排序區間為 [0, i-1]
for i in range(1, len(nums)):
base = nums[i]
j = i - 1
# 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置
while j >= 0 and nums[j] > base:
nums[j + 1] = nums[j] # 將 nums[j] 向右移動一位
j -= 1
nums[j + 1] = base # 將 base 賦值到正確位置
```
=== "C++"
```cpp title="insertion_sort.cpp"
/* 插入排序 */
void insertionSort(vector<int> &nums) {
// 外迴圈:已排序區間為 [0, i-1]
for (int i = 1; i < nums.size(); i++) {
int base = nums[i], j = i - 1;
// 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置
while (j >= 0 && nums[j] > base) {
nums[j + 1] = nums[j]; // 將 nums[j] 向右移動一位
j--;
}
nums[j + 1] = base; // 將 base 賦值到正確位置
}
}
```
=== "Java"
```java title="insertion_sort.java"
/* 插入排序 */
void insertionSort(int[] nums) {
// 外迴圈:已排序區間為 [0, i-1]
for (int i = 1; i < nums.length; i++) {
int base = nums[i], j = i - 1;
// 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置
while (j >= 0 && nums[j] > base) {
nums[j + 1] = nums[j]; // 將 nums[j] 向右移動一位
j--;
}
nums[j + 1] = base; // 將 base 賦值到正確位置
}
}
```
=== "C#"
```csharp title="insertion_sort.cs"
/* 插入排序 */
void InsertionSort(int[] nums) {
// 外迴圈:已排序區間為 [0, i-1]
for (int i = 1; i < nums.Length; i++) {
int bas = nums[i], j = i - 1;
// 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置
while (j >= 0 && nums[j] > bas) {
nums[j + 1] = nums[j]; // 將 nums[j] 向右移動一位
j--;
}
nums[j + 1] = bas; // 將 base 賦值到正確位置
}
}
```
=== "Go"
```go title="insertion_sort.go"
/* 插入排序 */
func insertionSort(nums []int) {
// 外迴圈:已排序區間為 [0, i-1]
for i := 1; i < len(nums); i++ {
base := nums[i]
j := i - 1
// 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置
for j >= 0 && nums[j] > base {
nums[j+1] = nums[j] // 將 nums[j] 向右移動一位
j--
}
nums[j+1] = base // 將 base 賦值到正確位置
}
}
```
=== "Swift"
```swift title="insertion_sort.swift"
/* 插入排序 */
func insertionSort(nums: inout [Int]) {
// 外迴圈:已排序區間為 [0, i-1]
for i in nums.indices.dropFirst() {
let base = nums[i]
var j = i - 1
// 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置
while j >= 0, nums[j] > base {
nums[j + 1] = nums[j] // 將 nums[j] 向右移動一位
j -= 1
}
nums[j + 1] = base // 將 base 賦值到正確位置
}
}
```
=== "JS"
```javascript title="insertion_sort.js"
/* 插入排序 */
function insertionSort(nums) {
// 外迴圈:已排序區間為 [0, i-1]
for (let i = 1; i < nums.length; i++) {
let base = nums[i],
j = i - 1;
// 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置
while (j >= 0 && nums[j] > base) {
nums[j + 1] = nums[j]; // 將 nums[j] 向右移動一位
j--;
}
nums[j + 1] = base; // 將 base 賦值到正確位置
}
}
```
=== "TS"
```typescript title="insertion_sort.ts"
/* 插入排序 */
function insertionSort(nums: number[]): void {
// 外迴圈:已排序區間為 [0, i-1]
for (let i = 1; i < nums.length; i++) {
const base = nums[i];
let j = i - 1;
// 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置
while (j >= 0 && nums[j] > base) {
nums[j + 1] = nums[j]; // 將 nums[j] 向右移動一位
j--;
}
nums[j + 1] = base; // 將 base 賦值到正確位置
}
}
```
=== "Dart"
```dart title="insertion_sort.dart"
/* 插入排序 */
void insertionSort(List<int> nums) {
// 外迴圈:已排序區間為 [0, i-1]
for (int i = 1; i < nums.length; i++) {
int base = nums[i], j = i - 1;
// 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置
while (j >= 0 && nums[j] > base) {
nums[j + 1] = nums[j]; // 將 nums[j] 向右移動一位
j--;
}
nums[j + 1] = base; // 將 base 賦值到正確位置
}
}
```
=== "Rust"
```rust title="insertion_sort.rs"
/* 插入排序 */
fn insertion_sort(nums: &mut [i32]) {
// 外迴圈:已排序區間為 [0, i-1]
for i in 1..nums.len() {
let (base, mut j) = (nums[i], (i - 1) as i32);
// 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置
while j >= 0 && nums[j as usize] > base {
nums[(j + 1) as usize] = nums[j as usize]; // 將 nums[j] 向右移動一位
j -= 1;
}
nums[(j + 1) as usize] = base; // 將 base 賦值到正確位置
}
}
```
=== "C"
```c title="insertion_sort.c"
/* 插入排序 */
void insertionSort(int nums[], int size) {
// 外迴圈:已排序區間為 [0, i-1]
for (int i = 1; i < size; i++) {
int base = nums[i], j = i - 1;
// 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置
while (j >= 0 && nums[j] > base) {
// 將 nums[j] 向右移動一位
nums[j + 1] = nums[j];
j--;
}
// 將 base 賦值到正確位置
nums[j + 1] = base;
}
}
```
=== "Kotlin"
```kotlin title="insertion_sort.kt"
/* 插入排序 */
fun insertionSort(nums: IntArray) {
//外迴圈: 已排序元素為 1, 2, ..., n
for (i in nums.indices) {
val base = nums[i]
var j = i - 1
// 內迴圈: 將 base 插入到已排序部分的正確位置
while (j >= 0 && nums[j] > base) {
nums[j + 1] = nums[j] // 將 nums[j] 向右移動一位
j--
}
nums[j + 1] = base // 將 base 賦值到正確位置
}
}
```
=== "Ruby"
```ruby title="insertion_sort.rb"
[class]{}-[func]{insertion_sort}
```
=== "Zig"
```zig title="insertion_sort.zig"
// 插入排序
fn insertionSort(nums: []i32) void {
// 外迴圈:已排序區間為 [0, i-1]
var i: usize = 1;
while (i < nums.len) : (i += 1) {
var base = nums[i];
var j: usize = i;
// 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置
while (j >= 1 and nums[j - 1] > base) : (j -= 1) {
nums[j] = nums[j - 1]; // 將 nums[j] 向右移動一位
}
nums[j] = base; // 將 base 賦值到正確位置
}
}
```
??? pythontutor "視覺化執行"
<div style="height: 513px; width: 100%;"><iframe class="pythontutor-iframe" src="https://pythontutor.com/iframe-embed.html#code=def%20insertion_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%A4%96%E5%BE%AA%E7%8E%AF%EF%BC%9A%E5%B7%B2%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5B0,%20i-1%5D%0A%20%20%20%20for%20i%20in%20range%281,%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20base%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20j%20%3D%20i%20-%201%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%BE%AA%E7%8E%AF%EF%BC%9A%E5%B0%86%20base%20%E6%8F%92%E5%85%A5%E5%88%B0%E5%B7%B2%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%20%5B0,%20i-1%5D%20%E4%B8%AD%E7%9A%84%E6%AD%A3%E7%A1%AE%E4%BD%8D%E7%BD%AE%0A%20%20%20%20%20%20%20%20while%20j%20%3E%3D%200%20and%20nums%5Bj%5D%20%3E%20base%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%5D%20%20%23%20%E5%B0%86%20nums%5Bj%5D%20%E5%90%91%E5%8F%B3%E7%A7%BB%E5%8A%A8%E4%B8%80%E4%BD%8D%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%0A%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20base%20%20%23%20%E5%B0%86%20base%20%E8%B5%8B%E5%80%BC%E5%88%B0%E6%AD%A3%E7%A1%AE%E4%BD%8D%E7%BD%AE%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4,%201,%203,%201,%205,%202%5D%0A%20%20%20%20insertion_sort%28nums%29%0A%20%20%20%20print%28%22%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&codeDivHeight=472&codeDivWidth=350&cumulative=false&curInstr=4&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe></div>
<div style="margin-top: 5px;"><a href="https://pythontutor.com/iframe-embed.html#code=def%20insertion_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%A4%96%E5%BE%AA%E7%8E%AF%EF%BC%9A%E5%B7%B2%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5B0,%20i-1%5D%0A%20%20%20%20for%20i%20in%20range%281,%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20base%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20j%20%3D%20i%20-%201%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%BE%AA%E7%8E%AF%EF%BC%9A%E5%B0%86%20base%20%E6%8F%92%E5%85%A5%E5%88%B0%E5%B7%B2%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%20%5B0,%20i-1%5D%20%E4%B8%AD%E7%9A%84%E6%AD%A3%E7%A1%AE%E4%BD%8D%E7%BD%AE%0A%20%20%20%20%20%20%20%20while%20j%20%3E%3D%200%20and%20nums%5Bj%5D%20%3E%20base%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%5D%20%20%23%20%E5%B0%86%20nums%5Bj%5D%20%E5%90%91%E5%8F%B3%E7%A7%BB%E5%8A%A8%E4%B8%80%E4%BD%8D%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%0A%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20base%20%20%23%20%E5%B0%86%20base%20%E8%B5%8B%E5%80%BC%E5%88%B0%E6%AD%A3%E7%A1%AE%E4%BD%8D%E7%BD%AE%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4,%201,%203,%201,%205,%202%5D%0A%20%20%20%20insertion_sort%28nums%29%0A%20%20%20%20print%28%22%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=4&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">全螢幕觀看 ></a></div>
## 11.4.2 &nbsp; 演算法特性
- **時間複雜度為 $O(n^2)$、自適應排序**:在最差情況下,每次插入操作分別需要迴圈 $n - 1$、$n-2$、$\dots$、$2$、$1$ 次,求和得到 $(n - 1) n / 2$ ,因此時間複雜度為 $O(n^2)$ 。在遇到有序資料時,插入操作會提前終止。當輸入陣列完全有序時,插入排序達到最佳時間複雜度 $O(n)$ 。
- **空間複雜度為 $O(1)$、原地排序**:指標 $i$ 和 $j$ 使用常數大小的額外空間。
- **穩定排序**:在插入操作過程中,我們會將元素插入到相等元素的右側,不會改變它們的順序。
## 11.4.3 &nbsp; 插入排序的優勢
插入排序的時間複雜度為 $O(n^2)$ ,而我們即將學習的快速排序的時間複雜度為 $O(n \log n)$ 。儘管插入排序的時間複雜度更高,**但在資料量較小的情況下,插入排序通常更快**。
這個結論與線性查詢和二分搜尋的適用情況的結論類似。快速排序這類 $O(n \log n)$ 的演算法屬於基於分治策略的排序演算法,往往包含更多單元計算操作。而在資料量較小時,$n^2$ 和 $n \log n$ 的數值比較接近,複雜度不佔主導地位,每輪中的單元操作數量起到決定性作用。
實際上,許多程式語言(例如 Java)的內建排序函式採用了插入排序,大致思路為:對於長陣列,採用基於分治策略的排序演算法,例如快速排序;對於短陣列,直接使用插入排序。
雖然泡沫排序、選擇排序和插入排序的時間複雜度都為 $O(n^2)$ ,但在實際情況中,**插入排序的使用頻率顯著高於泡沫排序和選擇排序**,主要有以下原因。
- 泡沫排序基於元素交換實現,需要藉助一個臨時變數,共涉及 3 個單元操作;插入排序基於元素賦值實現,僅需 1 個單元操作。因此,**泡沫排序的計算開銷通常比插入排序更高**。
- 選擇排序在任何情況下的時間複雜度都為 $O(n^2)$ 。**如果給定一組部分有序的資料,插入排序通常比選擇排序效率更高**。
- 選擇排序不穩定,無法應用於多級排序。
+708
View File
@@ -0,0 +1,708 @@
---
comments: true
---
# 11.6 &nbsp; 合併排序
<u>合併排序(merge sort</u>是一種基於分治策略的排序演算法,包含圖 11-10 所示的“劃分”和“合併”階段。
1. **劃分階段**:透過遞迴不斷地將陣列從中點處分開,將長陣列的排序問題轉換為短陣列的排序問題。
2. **合併階段**:當子陣列長度為 1 時終止劃分,開始合併,持續地將左右兩個較短的有序陣列合併為一個較長的有序陣列,直至結束。
![合併排序的劃分與合併階段](merge_sort.assets/merge_sort_overview.png){ class="animation-figure" }
<p align="center"> 圖 11-10 &nbsp; 合併排序的劃分與合併階段 </p>
## 11.6.1 &nbsp; 演算法流程
如圖 11-11 所示,“劃分階段”從頂至底遞迴地將陣列從中點切分為兩個子陣列。
1. 計算陣列中點 `mid` ,遞迴劃分左子陣列(區間 `[left, mid]` )和右子陣列(區間 `[mid + 1, right]` )。
2. 遞迴執行步驟 `1.` ,直至子陣列區間長度為 1 時終止。
“合併階段”從底至頂地將左子陣列和右子陣列合併為一個有序陣列。需要注意的是,從長度為 1 的子陣列開始合併,合併階段中的每個子陣列都是有序的。
=== "<1>"
![合併排序步驟](merge_sort.assets/merge_sort_step1.png){ class="animation-figure" }
=== "<2>"
![merge_sort_step2](merge_sort.assets/merge_sort_step2.png){ class="animation-figure" }
=== "<3>"
![merge_sort_step3](merge_sort.assets/merge_sort_step3.png){ class="animation-figure" }
=== "<4>"
![merge_sort_step4](merge_sort.assets/merge_sort_step4.png){ class="animation-figure" }
=== "<5>"
![merge_sort_step5](merge_sort.assets/merge_sort_step5.png){ class="animation-figure" }
=== "<6>"
![merge_sort_step6](merge_sort.assets/merge_sort_step6.png){ class="animation-figure" }
=== "<7>"
![merge_sort_step7](merge_sort.assets/merge_sort_step7.png){ class="animation-figure" }
=== "<8>"
![merge_sort_step8](merge_sort.assets/merge_sort_step8.png){ class="animation-figure" }
=== "<9>"
![merge_sort_step9](merge_sort.assets/merge_sort_step9.png){ class="animation-figure" }
=== "<10>"
![merge_sort_step10](merge_sort.assets/merge_sort_step10.png){ class="animation-figure" }
<p align="center"> 圖 11-11 &nbsp; 合併排序步驟 </p>
觀察發現,合併排序與二元樹後序走訪的遞迴順序是一致的。
- **後序走訪**:先遞迴左子樹,再遞迴右子樹,最後處理根節點。
- **合併排序**:先遞迴左子陣列,再遞迴右子陣列,最後處理合併。
合併排序的實現如以下程式碼所示。請注意,`nums` 的待合併區間為 `[left, right]` ,而 `tmp` 的對應區間為 `[0, right - left]`
=== "Python"
```python title="merge_sort.py"
def merge(nums: list[int], left: int, mid: int, right: int):
"""合併左子陣列和右子陣列"""
# 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right]
# 建立一個臨時陣列 tmp ,用於存放合併後的結果
tmp = [0] * (right - left + 1)
# 初始化左子陣列和右子陣列的起始索引
i, j, k = left, mid + 1, 0
# 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中
while i <= mid and j <= right:
if nums[i] <= nums[j]:
tmp[k] = nums[i]
i += 1
else:
tmp[k] = nums[j]
j += 1
k += 1
# 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中
while i <= mid:
tmp[k] = nums[i]
i += 1
k += 1
while j <= right:
tmp[k] = nums[j]
j += 1
k += 1
# 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間
for k in range(0, len(tmp)):
nums[left + k] = tmp[k]
def merge_sort(nums: list[int], left: int, right: int):
"""合併排序"""
# 終止條件
if left >= right:
return # 當子陣列長度為 1 時終止遞迴
# 劃分階段
mid = (left + right) // 2 # 計算中點
merge_sort(nums, left, mid) # 遞迴左子陣列
merge_sort(nums, mid + 1, right) # 遞迴右子陣列
# 合併階段
merge(nums, left, mid, right)
```
=== "C++"
```cpp title="merge_sort.cpp"
/* 合併左子陣列和右子陣列 */
void merge(vector<int> &nums, int left, int mid, int right) {
// 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right]
// 建立一個臨時陣列 tmp ,用於存放合併後的結果
vector<int> tmp(right - left + 1);
// 初始化左子陣列和右子陣列的起始索引
int i = left, j = mid + 1, k = 0;
// 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中
while (i <= mid && j <= right) {
if (nums[i] <= nums[j])
tmp[k++] = nums[i++];
else
tmp[k++] = nums[j++];
}
// 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中
while (i <= mid) {
tmp[k++] = nums[i++];
}
while (j <= right) {
tmp[k++] = nums[j++];
}
// 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間
for (k = 0; k < tmp.size(); k++) {
nums[left + k] = tmp[k];
}
}
/* 合併排序 */
void mergeSort(vector<int> &nums, int left, int right) {
// 終止條件
if (left >= right)
return; // 當子陣列長度為 1 時終止遞迴
// 劃分階段
int mid = (left + right) / 2; // 計算中點
mergeSort(nums, left, mid); // 遞迴左子陣列
mergeSort(nums, mid + 1, right); // 遞迴右子陣列
// 合併階段
merge(nums, left, mid, right);
}
```
=== "Java"
```java title="merge_sort.java"
/* 合併左子陣列和右子陣列 */
void merge(int[] nums, int left, int mid, int right) {
// 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right]
// 建立一個臨時陣列 tmp ,用於存放合併後的結果
int[] tmp = new int[right - left + 1];
// 初始化左子陣列和右子陣列的起始索引
int i = left, j = mid + 1, k = 0;
// 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中
while (i <= mid && j <= right) {
if (nums[i] <= nums[j])
tmp[k++] = nums[i++];
else
tmp[k++] = nums[j++];
}
// 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中
while (i <= mid) {
tmp[k++] = nums[i++];
}
while (j <= right) {
tmp[k++] = nums[j++];
}
// 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間
for (k = 0; k < tmp.length; k++) {
nums[left + k] = tmp[k];
}
}
/* 合併排序 */
void mergeSort(int[] nums, int left, int right) {
// 終止條件
if (left >= right)
return; // 當子陣列長度為 1 時終止遞迴
// 劃分階段
int mid = (left + right) / 2; // 計算中點
mergeSort(nums, left, mid); // 遞迴左子陣列
mergeSort(nums, mid + 1, right); // 遞迴右子陣列
// 合併階段
merge(nums, left, mid, right);
}
```
=== "C#"
```csharp title="merge_sort.cs"
/* 合併左子陣列和右子陣列 */
void Merge(int[] nums, int left, int mid, int right) {
// 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right]
// 建立一個臨時陣列 tmp ,用於存放合併後的結果
int[] tmp = new int[right - left + 1];
// 初始化左子陣列和右子陣列的起始索引
int i = left, j = mid + 1, k = 0;
// 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中
while (i <= mid && j <= right) {
if (nums[i] <= nums[j])
tmp[k++] = nums[i++];
else
tmp[k++] = nums[j++];
}
// 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中
while (i <= mid) {
tmp[k++] = nums[i++];
}
while (j <= right) {
tmp[k++] = nums[j++];
}
// 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間
for (k = 0; k < tmp.Length; ++k) {
nums[left + k] = tmp[k];
}
}
/* 合併排序 */
void MergeSort(int[] nums, int left, int right) {
// 終止條件
if (left >= right) return; // 當子陣列長度為 1 時終止遞迴
// 劃分階段
int mid = (left + right) / 2; // 計算中點
MergeSort(nums, left, mid); // 遞迴左子陣列
MergeSort(nums, mid + 1, right); // 遞迴右子陣列
// 合併階段
Merge(nums, left, mid, right);
}
```
=== "Go"
```go title="merge_sort.go"
/* 合併左子陣列和右子陣列 */
func merge(nums []int, left, mid, right int) {
// 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right]
// 建立一個臨時陣列 tmp ,用於存放合併後的結果
tmp := make([]int, right-left+1)
// 初始化左子陣列和右子陣列的起始索引
i, j, k := left, mid+1, 0
// 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中
for i <= mid && j <= right {
if nums[i] <= nums[j] {
tmp[k] = nums[i]
i++
} else {
tmp[k] = nums[j]
j++
}
k++
}
// 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中
for i <= mid {
tmp[k] = nums[i]
i++
k++
}
for j <= right {
tmp[k] = nums[j]
j++
k++
}
// 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間
for k := 0; k < len(tmp); k++ {
nums[left+k] = tmp[k]
}
}
/* 合併排序 */
func mergeSort(nums []int, left, right int) {
// 終止條件
if left >= right {
return
}
// 劃分階段
mid := (left + right) / 2
mergeSort(nums, left, mid)
mergeSort(nums, mid+1, right)
// 合併階段
merge(nums, left, mid, right)
}
```
=== "Swift"
```swift title="merge_sort.swift"
/* 合併左子陣列和右子陣列 */
func merge(nums: inout [Int], left: Int, mid: Int, right: Int) {
// 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right]
// 建立一個臨時陣列 tmp ,用於存放合併後的結果
var tmp = Array(repeating: 0, count: right - left + 1)
// 初始化左子陣列和右子陣列的起始索引
var i = left, j = mid + 1, k = 0
// 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中
while i <= mid, j <= right {
if nums[i] <= nums[j] {
tmp[k] = nums[i]
i += 1
} else {
tmp[k] = nums[j]
j += 1
}
k += 1
}
// 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中
while i <= mid {
tmp[k] = nums[i]
i += 1
k += 1
}
while j <= right {
tmp[k] = nums[j]
j += 1
k += 1
}
// 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間
for k in tmp.indices {
nums[left + k] = tmp[k]
}
}
/* 合併排序 */
func mergeSort(nums: inout [Int], left: Int, right: Int) {
// 終止條件
if left >= right { // 當子陣列長度為 1 時終止遞迴
return
}
// 劃分階段
let mid = (left + right) / 2 // 計算中點
mergeSort(nums: &nums, left: left, right: mid) // 遞迴左子陣列
mergeSort(nums: &nums, left: mid + 1, right: right) // 遞迴右子陣列
// 合併階段
merge(nums: &nums, left: left, mid: mid, right: right)
}
```
=== "JS"
```javascript title="merge_sort.js"
/* 合併左子陣列和右子陣列 */
function merge(nums, left, mid, right) {
// 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right]
// 建立一個臨時陣列 tmp ,用於存放合併後的結果
const tmp = new Array(right - left + 1);
// 初始化左子陣列和右子陣列的起始索引
let i = left,
j = mid + 1,
k = 0;
// 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中
while (i <= mid && j <= right) {
if (nums[i] <= nums[j]) {
tmp[k++] = nums[i++];
} else {
tmp[k++] = nums[j++];
}
}
// 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中
while (i <= mid) {
tmp[k++] = nums[i++];
}
while (j <= right) {
tmp[k++] = nums[j++];
}
// 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間
for (k = 0; k < tmp.length; k++) {
nums[left + k] = tmp[k];
}
}
/* 合併排序 */
function mergeSort(nums, left, right) {
// 終止條件
if (left >= right) return; // 當子陣列長度為 1 時終止遞迴
// 劃分階段
let mid = Math.floor((left + right) / 2); // 計算中點
mergeSort(nums, left, mid); // 遞迴左子陣列
mergeSort(nums, mid + 1, right); // 遞迴右子陣列
// 合併階段
merge(nums, left, mid, right);
}
```
=== "TS"
```typescript title="merge_sort.ts"
/* 合併左子陣列和右子陣列 */
function merge(nums: number[], left: number, mid: number, right: number): void {
// 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right]
// 建立一個臨時陣列 tmp ,用於存放合併後的結果
const tmp = new Array(right - left + 1);
// 初始化左子陣列和右子陣列的起始索引
let i = left,
j = mid + 1,
k = 0;
// 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中
while (i <= mid && j <= right) {
if (nums[i] <= nums[j]) {
tmp[k++] = nums[i++];
} else {
tmp[k++] = nums[j++];
}
}
// 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中
while (i <= mid) {
tmp[k++] = nums[i++];
}
while (j <= right) {
tmp[k++] = nums[j++];
}
// 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間
for (k = 0; k < tmp.length; k++) {
nums[left + k] = tmp[k];
}
}
/* 合併排序 */
function mergeSort(nums: number[], left: number, right: number): void {
// 終止條件
if (left >= right) return; // 當子陣列長度為 1 時終止遞迴
// 劃分階段
let mid = Math.floor((left + right) / 2); // 計算中點
mergeSort(nums, left, mid); // 遞迴左子陣列
mergeSort(nums, mid + 1, right); // 遞迴右子陣列
// 合併階段
merge(nums, left, mid, right);
}
```
=== "Dart"
```dart title="merge_sort.dart"
/* 合併左子陣列和右子陣列 */
void merge(List<int> nums, int left, int mid, int right) {
// 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right]
// 建立一個臨時陣列 tmp ,用於存放合併後的結果
List<int> tmp = List.filled(right - left + 1, 0);
// 初始化左子陣列和右子陣列的起始索引
int i = left, j = mid + 1, k = 0;
// 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中
while (i <= mid && j <= right) {
if (nums[i] <= nums[j])
tmp[k++] = nums[i++];
else
tmp[k++] = nums[j++];
}
// 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中
while (i <= mid) {
tmp[k++] = nums[i++];
}
while (j <= right) {
tmp[k++] = nums[j++];
}
// 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間
for (k = 0; k < tmp.length; k++) {
nums[left + k] = tmp[k];
}
}
/* 合併排序 */
void mergeSort(List<int> nums, int left, int right) {
// 終止條件
if (left >= right) return; // 當子陣列長度為 1 時終止遞迴
// 劃分階段
int mid = (left + right) ~/ 2; // 計算中點
mergeSort(nums, left, mid); // 遞迴左子陣列
mergeSort(nums, mid + 1, right); // 遞迴右子陣列
// 合併階段
merge(nums, left, mid, right);
}
```
=== "Rust"
```rust title="merge_sort.rs"
/* 合併左子陣列和右子陣列 */
fn merge(nums: &mut [i32], left: usize, mid: usize, right: usize) {
// 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right]
// 建立一個臨時陣列 tmp ,用於存放合併後的結果
let tmp_size = right - left + 1;
let mut tmp = vec![0; tmp_size];
// 初始化左子陣列和右子陣列的起始索引
let (mut i, mut j, mut k) = (left, mid + 1, 0);
// 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中
while i <= mid && j <= right {
if nums[i] <= nums[j] {
tmp[k] = nums[i];
i += 1;
} else {
tmp[k] = nums[j];
j += 1;
}
k += 1;
}
// 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中
while i <= mid {
tmp[k] = nums[i];
k += 1;
i += 1;
}
while j <= right {
tmp[k] = nums[j];
k += 1;
j += 1;
}
// 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間
for k in 0..tmp_size {
nums[left + k] = tmp[k];
}
}
/* 合併排序 */
fn merge_sort(nums: &mut [i32], left: usize, right: usize) {
// 終止條件
if left >= right {
return; // 當子陣列長度為 1 時終止遞迴
}
// 劃分階段
let mid = (left + right) / 2; // 計算中點
merge_sort(nums, left, mid); // 遞迴左子陣列
merge_sort(nums, mid + 1, right); // 遞迴右子陣列
// 合併階段
merge(nums, left, mid, right);
}
```
=== "C"
```c title="merge_sort.c"
/* 合併左子陣列和右子陣列 */
void merge(int *nums, int left, int mid, int right) {
// 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right]
// 建立一個臨時陣列 tmp ,用於存放合併後的結果
int tmpSize = right - left + 1;
int *tmp = (int *)malloc(tmpSize * sizeof(int));
// 初始化左子陣列和右子陣列的起始索引
int i = left, j = mid + 1, k = 0;
// 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中
while (i <= mid && j <= right) {
if (nums[i] <= nums[j]) {
tmp[k++] = nums[i++];
} else {
tmp[k++] = nums[j++];
}
}
// 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中
while (i <= mid) {
tmp[k++] = nums[i++];
}
while (j <= right) {
tmp[k++] = nums[j++];
}
// 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間
for (k = 0; k < tmpSize; ++k) {
nums[left + k] = tmp[k];
}
// 釋放記憶體
free(tmp);
}
/* 合併排序 */
void mergeSort(int *nums, int left, int right) {
// 終止條件
if (left >= right)
return; // 當子陣列長度為 1 時終止遞迴
// 劃分階段
int mid = (left + right) / 2; // 計算中點
mergeSort(nums, left, mid); // 遞迴左子陣列
mergeSort(nums, mid + 1, right); // 遞迴右子陣列
// 合併階段
merge(nums, left, mid, right);
}
```
=== "Kotlin"
```kotlin title="merge_sort.kt"
/* 合併左子陣列和右子陣列 */
fun merge(nums: IntArray, left: Int, mid: Int, right: Int) {
// 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right]
// 建立一個臨時陣列 tmp ,用於存放合併後的結果
val tmp = IntArray(right - left + 1)
// 初始化左子陣列和右子陣列的起始索引
var i = left
var j = mid + 1
var k = 0
// 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中
while (i <= mid && j <= right) {
if (nums[i] <= nums[j]) tmp[k++] = nums[i++]
else tmp[k++] = nums[j++]
}
// 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中
while (i <= mid) {
tmp[k++] = nums[i++]
}
while (j <= right) {
tmp[k++] = nums[j++]
}
// 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間
for (l in tmp.indices) {
nums[left + l] = tmp[l]
}
}
/* 合併排序 */
fun mergeSort(nums: IntArray, left: Int, right: Int) {
// 終止條件
if (left >= right) return // 當子陣列長度為 1 時終止遞迴
// 劃分階段
val mid = (left + right) / 2 // 計算中點
mergeSort(nums, left, mid) // 遞迴左子陣列
mergeSort(nums, mid + 1, right) // 遞迴右子陣列
// 合併階段
merge(nums, left, mid, right)
}
```
=== "Ruby"
```ruby title="merge_sort.rb"
[class]{}-[func]{merge}
[class]{}-[func]{merge_sort}
```
=== "Zig"
```zig title="merge_sort.zig"
// 合併左子陣列和右子陣列
// 左子陣列區間 [left, mid]
// 右子陣列區間 [mid + 1, right]
fn merge(nums: []i32, left: usize, mid: usize, right: usize) !void {
// 初始化輔助陣列
var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer mem_arena.deinit();
const mem_allocator = mem_arena.allocator();
var tmp = try mem_allocator.alloc(i32, right + 1 - left);
std.mem.copy(i32, tmp, nums[left..right+1]);
// 左子陣列的起始索引和結束索引
var leftStart = left - left;
var leftEnd = mid - left;
// 右子陣列的起始索引和結束索引
var rightStart = mid + 1 - left;
var rightEnd = right - left;
// i, j 分別指向左子陣列、右子陣列的首元素
var i = leftStart;
var j = rightStart;
// 透過覆蓋原陣列 nums 來合併左子陣列和右子陣列
var k = left;
while (k <= right) : (k += 1) {
// 若“左子陣列已全部合併完”,則選取右子陣列元素,並且 j++
if (i > leftEnd) {
nums[k] = tmp[j];
j += 1;
// 否則,若“右子陣列已全部合併完”或“左子陣列元素 <= 右子陣列元素”,則選取左子陣列元素,並且 i++
} else if (j > rightEnd or tmp[i] <= tmp[j]) {
nums[k] = tmp[i];
i += 1;
// 否則,若“左右子陣列都未全部合併完”且“左子陣列元素 > 右子陣列元素”,則選取右子陣列元素,並且 j++
} else {
nums[k] = tmp[j];
j += 1;
}
}
}
// 合併排序
fn mergeSort(nums: []i32, left: usize, right: usize) !void {
// 終止條件
if (left >= right) return; // 當子陣列長度為 1 時終止遞迴
// 劃分階段
var mid = (left + right) / 2; // 計算中點
try mergeSort(nums, left, mid); // 遞迴左子陣列
try mergeSort(nums, mid + 1, right); // 遞迴右子陣列
// 合併階段
try merge(nums, left, mid, right);
}
```
??? pythontutor "視覺化執行"
<div style="height: 549px; width: 100%;"><iframe class="pythontutor-iframe" src="https://pythontutor.com/iframe-embed.html#code=def%20merge%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20mid%3A%20int,%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%90%88%E5%B9%B6%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%22%22%22%0A%20%20%20%20%23%20%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5Bleft,%20mid%5D,%20%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5Bmid%2B1,%20right%5D%0A%20%20%20%20%23%20%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA%E4%B8%B4%E6%97%B6%E6%95%B0%E7%BB%84%20tmp%20%EF%BC%8C%E7%94%A8%E4%BA%8E%E5%AD%98%E6%94%BE%E5%90%88%E5%B9%B6%E5%90%8E%E7%9A%84%E7%BB%93%E6%9E%9C%0A%20%20%20%20tmp%20%3D%20%5B0%5D%20*%20%28right%20-%20left%20%2B%201%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E8%B5%B7%E5%A7%8B%E7%B4%A2%E5%BC%95%0A%20%20%20%20i,%20j,%20k%20%3D%20left,%20mid%20%2B%201,%200%0A%20%20%20%20%23%20%E5%BD%93%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%E9%83%BD%E8%BF%98%E6%9C%89%E5%85%83%E7%B4%A0%E6%97%B6%EF%BC%8C%E8%BF%9B%E8%A1%8C%E6%AF%94%E8%BE%83%E5%B9%B6%E5%B0%86%E8%BE%83%E5%B0%8F%E7%9A%84%E5%85%83%E7%B4%A0%E5%A4%8D%E5%88%B6%E5%88%B0%E4%B8%B4%E6%97%B6%E6%95%B0%E7%BB%84%E4%B8%AD%0A%20%20%20%20while%20i%20%3C%3D%20mid%20and%20j%20%3C%3D%20right%3A%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3C%3D%20nums%5Bj%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20%23%20%E5%B0%86%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E5%89%A9%E4%BD%99%E5%85%83%E7%B4%A0%E5%A4%8D%E5%88%B6%E5%88%B0%E4%B8%B4%E6%97%B6%E6%95%B0%E7%BB%84%E4%B8%AD%0A%20%20%20%20while%20i%20%3C%3D%20mid%3A%0A%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20while%20j%20%3C%3D%20right%3A%0A%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20%23%20%E5%B0%86%E4%B8%B4%E6%97%B6%E6%95%B0%E7%BB%84%20tmp%20%E4%B8%AD%E7%9A%84%E5%85%83%E7%B4%A0%E5%A4%8D%E5%88%B6%E5%9B%9E%E5%8E%9F%E6%95%B0%E7%BB%84%20nums%20%E7%9A%84%E5%AF%B9%E5%BA%94%E5%8C%BA%E9%97%B4%0A%20%20%20%20for%20k%20in%20range%280,%20len%28tmp%29%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bleft%20%2B%20k%5D%20%3D%20tmp%5Bk%5D%0A%0A%0Adef%20merge_sort%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E7%BB%88%E6%AD%A2%E6%9D%A1%E4%BB%B6%0A%20%20%20%20if%20left%20%3E%3D%20right%3A%0A%20%20%20%20%20%20%20%20return%20%20%23%20%E5%BD%93%E5%AD%90%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%E4%B8%BA%201%20%E6%97%B6%E7%BB%88%E6%AD%A2%E9%80%92%E5%BD%92%0A%20%20%20%20%23%20%E5%88%92%E5%88%86%E9%98%B6%E6%AE%B5%0A%20%20%20%20mid%20%3D%20%28left%20%2B%20right%29%20//%202%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%AD%E7%82%B9%0A%20%20%20%20merge_sort%28nums,%20left,%20mid%29%20%20%23%20%E9%80%92%E5%BD%92%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%0A%20%20%20%20merge_sort%28nums,%20mid%20%2B%201,%20right%29%20%20%23%20%E9%80%92%E5%BD%92%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%0A%20%20%20%20%23%20%E5%90%88%E5%B9%B6%E9%98%B6%E6%AE%B5%0A%20%20%20%20merge%28nums,%20left,%20mid,%20right%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B7,%203,%202,%206,%200,%201,%205,%204%5D%0A%20%20%20%20merge_sort%28nums,%200,%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&codeDivHeight=472&codeDivWidth=350&cumulative=false&curInstr=5&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe></div>
<div style="margin-top: 5px;"><a href="https://pythontutor.com/iframe-embed.html#code=def%20merge%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20mid%3A%20int,%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%90%88%E5%B9%B6%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%22%22%22%0A%20%20%20%20%23%20%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5Bleft,%20mid%5D,%20%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5Bmid%2B1,%20right%5D%0A%20%20%20%20%23%20%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA%E4%B8%B4%E6%97%B6%E6%95%B0%E7%BB%84%20tmp%20%EF%BC%8C%E7%94%A8%E4%BA%8E%E5%AD%98%E6%94%BE%E5%90%88%E5%B9%B6%E5%90%8E%E7%9A%84%E7%BB%93%E6%9E%9C%0A%20%20%20%20tmp%20%3D%20%5B0%5D%20*%20%28right%20-%20left%20%2B%201%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E8%B5%B7%E5%A7%8B%E7%B4%A2%E5%BC%95%0A%20%20%20%20i,%20j,%20k%20%3D%20left,%20mid%20%2B%201,%200%0A%20%20%20%20%23%20%E5%BD%93%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%E9%83%BD%E8%BF%98%E6%9C%89%E5%85%83%E7%B4%A0%E6%97%B6%EF%BC%8C%E8%BF%9B%E8%A1%8C%E6%AF%94%E8%BE%83%E5%B9%B6%E5%B0%86%E8%BE%83%E5%B0%8F%E7%9A%84%E5%85%83%E7%B4%A0%E5%A4%8D%E5%88%B6%E5%88%B0%E4%B8%B4%E6%97%B6%E6%95%B0%E7%BB%84%E4%B8%AD%0A%20%20%20%20while%20i%20%3C%3D%20mid%20and%20j%20%3C%3D%20right%3A%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3C%3D%20nums%5Bj%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20%23%20%E5%B0%86%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E5%89%A9%E4%BD%99%E5%85%83%E7%B4%A0%E5%A4%8D%E5%88%B6%E5%88%B0%E4%B8%B4%E6%97%B6%E6%95%B0%E7%BB%84%E4%B8%AD%0A%20%20%20%20while%20i%20%3C%3D%20mid%3A%0A%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20while%20j%20%3C%3D%20right%3A%0A%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20%23%20%E5%B0%86%E4%B8%B4%E6%97%B6%E6%95%B0%E7%BB%84%20tmp%20%E4%B8%AD%E7%9A%84%E5%85%83%E7%B4%A0%E5%A4%8D%E5%88%B6%E5%9B%9E%E5%8E%9F%E6%95%B0%E7%BB%84%20nums%20%E7%9A%84%E5%AF%B9%E5%BA%94%E5%8C%BA%E9%97%B4%0A%20%20%20%20for%20k%20in%20range%280,%20len%28tmp%29%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bleft%20%2B%20k%5D%20%3D%20tmp%5Bk%5D%0A%0A%0Adef%20merge_sort%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E7%BB%88%E6%AD%A2%E6%9D%A1%E4%BB%B6%0A%20%20%20%20if%20left%20%3E%3D%20right%3A%0A%20%20%20%20%20%20%20%20return%20%20%23%20%E5%BD%93%E5%AD%90%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%E4%B8%BA%201%20%E6%97%B6%E7%BB%88%E6%AD%A2%E9%80%92%E5%BD%92%0A%20%20%20%20%23%20%E5%88%92%E5%88%86%E9%98%B6%E6%AE%B5%0A%20%20%20%20mid%20%3D%20%28left%20%2B%20right%29%20//%202%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%AD%E7%82%B9%0A%20%20%20%20merge_sort%28nums,%20left,%20mid%29%20%20%23%20%E9%80%92%E5%BD%92%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%0A%20%20%20%20merge_sort%28nums,%20mid%20%2B%201,%20right%29%20%20%23%20%E9%80%92%E5%BD%92%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%0A%20%20%20%20%23%20%E5%90%88%E5%B9%B6%E9%98%B6%E6%AE%B5%0A%20%20%20%20merge%28nums,%20left,%20mid,%20right%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B7,%203,%202,%206,%200,%201,%205,%204%5D%0A%20%20%20%20merge_sort%28nums,%200,%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=5&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">全螢幕觀看 ></a></div>
## 11.6.2 &nbsp; 演算法特性
- **時間複雜度為 $O(n \log n)$、非自適應排序**:劃分產生高度為 $\log n$ 的遞迴樹,每層合併的總操作數量為 $n$ ,因此總體時間複雜度為 $O(n \log n)$ 。
- **空間複雜度為 $O(n)$、非原地排序**:遞迴深度為 $\log n$ ,使用 $O(\log n)$ 大小的堆疊幀空間。合併操作需要藉助輔助陣列實現,使用 $O(n)$ 大小的額外空間。
- **穩定排序**:在合併過程中,相等元素的次序保持不變。
## 11.6.3 &nbsp; 鏈結串列排序
對於鏈結串列,合併排序相較於其他排序演算法具有顯著優勢,**可以將鏈結串列排序任務的空間複雜度最佳化至 $O(1)$** 。
- **劃分階段**:可以使用“迭代”替代“遞迴”來實現鏈結串列劃分工作,從而省去遞迴使用的堆疊幀空間。
- **合併階段**:在鏈結串列中,節點增刪操作僅需改變引用(指標)即可實現,因此合併階段(將兩個短有序鏈結串列合併為一個長有序鏈結串列)無須建立額外鏈結串列。
具體實現細節比較複雜,有興趣的讀者可以查閱相關資料進行學習。
File diff suppressed because it is too large Load Diff
+764
View File
@@ -0,0 +1,764 @@
---
comments: true
---
# 11.10 &nbsp; 基數排序
上一節介紹了計數排序,它適用於資料量 $n$ 較大但資料範圍 $m$ 較小的情況。假設我們需要對 $n = 10^6$ 個學號進行排序,而學號是一個 $8$ 位數字,這意味著資料範圍 $m = 10^8$ 非常大,使用計數排序需要分配大量記憶體空間,而基數排序可以避免這種情況。
<u>基數排序(radix sort</u>的核心思想與計數排序一致,也透過統計個數來實現排序。在此基礎上,基數排序利用數字各位之間的遞進關係,依次對每一位進行排序,從而得到最終的排序結果。
## 11.10.1 &nbsp; 演算法流程
以學號資料為例,假設數字的最低位是第 $1$ 位,最高位是第 $8$ 位,基數排序的流程如圖 11-18 所示。
1. 初始化位數 $k = 1$ 。
2. 對學號的第 $k$ 位執行“計數排序”。完成後,資料會根據第 $k$ 位從小到大排序。
3. 將 $k$ 增加 $1$ ,然後返回步驟 `2.` 繼續迭代,直到所有位都排序完成後結束。
![基數排序演算法流程](radix_sort.assets/radix_sort_overview.png){ class="animation-figure" }
<p align="center"> 圖 11-18 &nbsp; 基數排序演算法流程 </p>
下面剖析程式碼實現。對於一個 $d$ 進位制的數字 $x$ ,要獲取其第 $k$ 位 $x_k$ ,可以使用以下計算公式:
$$
x_k = \lfloor\frac{x}{d^{k-1}}\rfloor \bmod d
$$
其中 $\lfloor a \rfloor$ 表示對浮點數 $a$ 向下取整,而 $\bmod \: d$ 表示對 $d$ 取模(取餘)。對於學號資料,$d = 10$ 且 $k \in [1, 8]$ 。
此外,我們需要小幅改動計數排序程式碼,使之可以根據數字的第 $k$ 位進行排序:
=== "Python"
```python title="radix_sort.py"
def digit(num: int, exp: int) -> int:
"""獲取元素 num 的第 k 位,其中 exp = 10^(k-1)"""
# 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算
return (num // exp) % 10
def counting_sort_digit(nums: list[int], exp: int):
"""計數排序(根據 nums 第 k 位排序)"""
# 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列
counter = [0] * 10
n = len(nums)
# 統計 0~9 各數字的出現次數
for i in range(n):
d = digit(nums[i], exp) # 獲取 nums[i] 第 k 位,記為 d
counter[d] += 1 # 統計數字 d 的出現次數
# 求前綴和,將“出現個數”轉換為“陣列索引”
for i in range(1, 10):
counter[i] += counter[i - 1]
# 倒序走訪,根據桶內統計結果,將各元素填入 res
res = [0] * n
for i in range(n - 1, -1, -1):
d = digit(nums[i], exp)
j = counter[d] - 1 # 獲取 d 在陣列中的索引 j
res[j] = nums[i] # 將當前元素填入索引 j
counter[d] -= 1 # 將 d 的數量減 1
# 使用結果覆蓋原陣列 nums
for i in range(n):
nums[i] = res[i]
def radix_sort(nums: list[int]):
"""基數排序"""
# 獲取陣列的最大元素,用於判斷最大位數
m = max(nums)
# 按照從低位到高位的順序走訪
exp = 1
while exp <= m:
# 對陣列元素的第 k 位執行計數排序
# k = 1 -> exp = 1
# k = 2 -> exp = 10
# 即 exp = 10^(k-1)
counting_sort_digit(nums, exp)
exp *= 10
```
=== "C++"
```cpp title="radix_sort.cpp"
/* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */
int digit(int num, int exp) {
// 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算
return (num / exp) % 10;
}
/* 計數排序(根據 nums 第 k 位排序) */
void countingSortDigit(vector<int> &nums, int exp) {
// 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列
vector<int> counter(10, 0);
int n = nums.size();
// 統計 0~9 各數字的出現次數
for (int i = 0; i < n; i++) {
int d = digit(nums[i], exp); // 獲取 nums[i] 第 k 位,記為 d
counter[d]++; // 統計數字 d 的出現次數
}
// 求前綴和,將“出現個數”轉換為“陣列索引”
for (int i = 1; i < 10; i++) {
counter[i] += counter[i - 1];
}
// 倒序走訪,根據桶內統計結果,將各元素填入 res
vector<int> res(n, 0);
for (int i = n - 1; i >= 0; i--) {
int d = digit(nums[i], exp);
int j = counter[d] - 1; // 獲取 d 在陣列中的索引 j
res[j] = nums[i]; // 將當前元素填入索引 j
counter[d]--; // 將 d 的數量減 1
}
// 使用結果覆蓋原陣列 nums
for (int i = 0; i < n; i++)
nums[i] = res[i];
}
/* 基數排序 */
void radixSort(vector<int> &nums) {
// 獲取陣列的最大元素,用於判斷最大位數
int m = *max_element(nums.begin(), nums.end());
// 按照從低位到高位的順序走訪
for (int exp = 1; exp <= m; exp *= 10)
// 對陣列元素的第 k 位執行計數排序
// k = 1 -> exp = 1
// k = 2 -> exp = 10
// 即 exp = 10^(k-1)
countingSortDigit(nums, exp);
}
```
=== "Java"
```java title="radix_sort.java"
/* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */
int digit(int num, int exp) {
// 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算
return (num / exp) % 10;
}
/* 計數排序(根據 nums 第 k 位排序) */
void countingSortDigit(int[] nums, int exp) {
// 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列
int[] counter = new int[10];
int n = nums.length;
// 統計 0~9 各數字的出現次數
for (int i = 0; i < n; i++) {
int d = digit(nums[i], exp); // 獲取 nums[i] 第 k 位,記為 d
counter[d]++; // 統計數字 d 的出現次數
}
// 求前綴和,將“出現個數”轉換為“陣列索引”
for (int i = 1; i < 10; i++) {
counter[i] += counter[i - 1];
}
// 倒序走訪,根據桶內統計結果,將各元素填入 res
int[] res = new int[n];
for (int i = n - 1; i >= 0; i--) {
int d = digit(nums[i], exp);
int j = counter[d] - 1; // 獲取 d 在陣列中的索引 j
res[j] = nums[i]; // 將當前元素填入索引 j
counter[d]--; // 將 d 的數量減 1
}
// 使用結果覆蓋原陣列 nums
for (int i = 0; i < n; i++)
nums[i] = res[i];
}
/* 基數排序 */
void radixSort(int[] nums) {
// 獲取陣列的最大元素,用於判斷最大位數
int m = Integer.MIN_VALUE;
for (int num : nums)
if (num > m)
m = num;
// 按照從低位到高位的順序走訪
for (int exp = 1; exp <= m; exp *= 10)
// 對陣列元素的第 k 位執行計數排序
// k = 1 -> exp = 1
// k = 2 -> exp = 10
// 即 exp = 10^(k-1)
countingSortDigit(nums, exp);
}
```
=== "C#"
```csharp title="radix_sort.cs"
/* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */
int Digit(int num, int exp) {
// 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算
return (num / exp) % 10;
}
/* 計數排序(根據 nums 第 k 位排序) */
void CountingSortDigit(int[] nums, int exp) {
// 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列
int[] counter = new int[10];
int n = nums.Length;
// 統計 0~9 各數字的出現次數
for (int i = 0; i < n; i++) {
int d = Digit(nums[i], exp); // 獲取 nums[i] 第 k 位,記為 d
counter[d]++; // 統計數字 d 的出現次數
}
// 求前綴和,將“出現個數”轉換為“陣列索引”
for (int i = 1; i < 10; i++) {
counter[i] += counter[i - 1];
}
// 倒序走訪,根據桶內統計結果,將各元素填入 res
int[] res = new int[n];
for (int i = n - 1; i >= 0; i--) {
int d = Digit(nums[i], exp);
int j = counter[d] - 1; // 獲取 d 在陣列中的索引 j
res[j] = nums[i]; // 將當前元素填入索引 j
counter[d]--; // 將 d 的數量減 1
}
// 使用結果覆蓋原陣列 nums
for (int i = 0; i < n; i++) {
nums[i] = res[i];
}
}
/* 基數排序 */
void RadixSort(int[] nums) {
// 獲取陣列的最大元素,用於判斷最大位數
int m = int.MinValue;
foreach (int num in nums) {
if (num > m) m = num;
}
// 按照從低位到高位的順序走訪
for (int exp = 1; exp <= m; exp *= 10) {
// 對陣列元素的第 k 位執行計數排序
// k = 1 -> exp = 1
// k = 2 -> exp = 10
// 即 exp = 10^(k-1)
CountingSortDigit(nums, exp);
}
}
```
=== "Go"
```go title="radix_sort.go"
/* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */
func digit(num, exp int) int {
// 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算
return (num / exp) % 10
}
/* 計數排序(根據 nums 第 k 位排序) */
func countingSortDigit(nums []int, exp int) {
// 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列
counter := make([]int, 10)
n := len(nums)
// 統計 0~9 各數字的出現次數
for i := 0; i < n; i++ {
d := digit(nums[i], exp) // 獲取 nums[i] 第 k 位,記為 d
counter[d]++ // 統計數字 d 的出現次數
}
// 求前綴和,將“出現個數”轉換為“陣列索引”
for i := 1; i < 10; i++ {
counter[i] += counter[i-1]
}
// 倒序走訪,根據桶內統計結果,將各元素填入 res
res := make([]int, n)
for i := n - 1; i >= 0; i-- {
d := digit(nums[i], exp)
j := counter[d] - 1 // 獲取 d 在陣列中的索引 j
res[j] = nums[i] // 將當前元素填入索引 j
counter[d]-- // 將 d 的數量減 1
}
// 使用結果覆蓋原陣列 nums
for i := 0; i < n; i++ {
nums[i] = res[i]
}
}
/* 基數排序 */
func radixSort(nums []int) {
// 獲取陣列的最大元素,用於判斷最大位數
max := math.MinInt
for _, num := range nums {
if num > max {
max = num
}
}
// 按照從低位到高位的順序走訪
for exp := 1; max >= exp; exp *= 10 {
// 對陣列元素的第 k 位執行計數排序
// k = 1 -> exp = 1
// k = 2 -> exp = 10
// 即 exp = 10^(k-1)
countingSortDigit(nums, exp)
}
}
```
=== "Swift"
```swift title="radix_sort.swift"
/* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */
func digit(num: Int, exp: Int) -> Int {
// 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算
(num / exp) % 10
}
/* 計數排序(根據 nums 第 k 位排序) */
func countingSortDigit(nums: inout [Int], exp: Int) {
// 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列
var counter = Array(repeating: 0, count: 10)
// 統計 0~9 各數字的出現次數
for i in nums.indices {
let d = digit(num: nums[i], exp: exp) // 獲取 nums[i] 第 k 位,記為 d
counter[d] += 1 // 統計數字 d 的出現次數
}
// 求前綴和,將“出現個數”轉換為“陣列索引”
for i in 1 ..< 10 {
counter[i] += counter[i - 1]
}
// 倒序走訪,根據桶內統計結果,將各元素填入 res
var res = Array(repeating: 0, count: nums.count)
for i in nums.indices.reversed() {
let d = digit(num: nums[i], exp: exp)
let j = counter[d] - 1 // 獲取 d 在陣列中的索引 j
res[j] = nums[i] // 將當前元素填入索引 j
counter[d] -= 1 // 將 d 的數量減 1
}
// 使用結果覆蓋原陣列 nums
for i in nums.indices {
nums[i] = res[i]
}
}
/* 基數排序 */
func radixSort(nums: inout [Int]) {
// 獲取陣列的最大元素,用於判斷最大位數
var m = Int.min
for num in nums {
if num > m {
m = num
}
}
// 按照從低位到高位的順序走訪
for exp in sequence(first: 1, next: { m >= ($0 * 10) ? $0 * 10 : nil }) {
// 對陣列元素的第 k 位執行計數排序
// k = 1 -> exp = 1
// k = 2 -> exp = 10
// 即 exp = 10^(k-1)
countingSortDigit(nums: &nums, exp: exp)
}
}
```
=== "JS"
```javascript title="radix_sort.js"
/* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */
function digit(num, exp) {
// 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算
return Math.floor(num / exp) % 10;
}
/* 計數排序(根據 nums 第 k 位排序) */
function countingSortDigit(nums, exp) {
// 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列
const counter = new Array(10).fill(0);
const n = nums.length;
// 統計 0~9 各數字的出現次數
for (let i = 0; i < n; i++) {
const d = digit(nums[i], exp); // 獲取 nums[i] 第 k 位,記為 d
counter[d]++; // 統計數字 d 的出現次數
}
// 求前綴和,將“出現個數”轉換為“陣列索引”
for (let i = 1; i < 10; i++) {
counter[i] += counter[i - 1];
}
// 倒序走訪,根據桶內統計結果,將各元素填入 res
const res = new Array(n).fill(0);
for (let i = n - 1; i >= 0; i--) {
const d = digit(nums[i], exp);
const j = counter[d] - 1; // 獲取 d 在陣列中的索引 j
res[j] = nums[i]; // 將當前元素填入索引 j
counter[d]--; // 將 d 的數量減 1
}
// 使用結果覆蓋原陣列 nums
for (let i = 0; i < n; i++) {
nums[i] = res[i];
}
}
/* 基數排序 */
function radixSort(nums) {
// 獲取陣列的最大元素,用於判斷最大位數
let m = Number.MIN_VALUE;
for (const num of nums) {
if (num > m) {
m = num;
}
}
// 按照從低位到高位的順序走訪
for (let exp = 1; exp <= m; exp *= 10) {
// 對陣列元素的第 k 位執行計數排序
// k = 1 -> exp = 1
// k = 2 -> exp = 10
// 即 exp = 10^(k-1)
countingSortDigit(nums, exp);
}
}
```
=== "TS"
```typescript title="radix_sort.ts"
/* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */
function digit(num: number, exp: number): number {
// 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算
return Math.floor(num / exp) % 10;
}
/* 計數排序(根據 nums 第 k 位排序) */
function countingSortDigit(nums: number[], exp: number): void {
// 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列
const counter = new Array(10).fill(0);
const n = nums.length;
// 統計 0~9 各數字的出現次數
for (let i = 0; i < n; i++) {
const d = digit(nums[i], exp); // 獲取 nums[i] 第 k 位,記為 d
counter[d]++; // 統計數字 d 的出現次數
}
// 求前綴和,將“出現個數”轉換為“陣列索引”
for (let i = 1; i < 10; i++) {
counter[i] += counter[i - 1];
}
// 倒序走訪,根據桶內統計結果,將各元素填入 res
const res = new Array(n).fill(0);
for (let i = n - 1; i >= 0; i--) {
const d = digit(nums[i], exp);
const j = counter[d] - 1; // 獲取 d 在陣列中的索引 j
res[j] = nums[i]; // 將當前元素填入索引 j
counter[d]--; // 將 d 的數量減 1
}
// 使用結果覆蓋原陣列 nums
for (let i = 0; i < n; i++) {
nums[i] = res[i];
}
}
/* 基數排序 */
function radixSort(nums: number[]): void {
// 獲取陣列的最大元素,用於判斷最大位數
let m = Number.MIN_VALUE;
for (const num of nums) {
if (num > m) {
m = num;
}
}
// 按照從低位到高位的順序走訪
for (let exp = 1; exp <= m; exp *= 10) {
// 對陣列元素的第 k 位執行計數排序
// k = 1 -> exp = 1
// k = 2 -> exp = 10
// 即 exp = 10^(k-1)
countingSortDigit(nums, exp);
}
}
```
=== "Dart"
```dart title="radix_sort.dart"
/* 獲取元素 _num 的第 k 位,其中 exp = 10^(k-1) */
int digit(int _num, int exp) {
// 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算
return (_num ~/ exp) % 10;
}
/* 計數排序(根據 nums 第 k 位排序) */
void countingSortDigit(List<int> nums, int exp) {
// 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列
List<int> counter = List<int>.filled(10, 0);
int n = nums.length;
// 統計 0~9 各數字的出現次數
for (int i = 0; i < n; i++) {
int d = digit(nums[i], exp); // 獲取 nums[i] 第 k 位,記為 d
counter[d]++; // 統計數字 d 的出現次數
}
// 求前綴和,將“出現個數”轉換為“陣列索引”
for (int i = 1; i < 10; i++) {
counter[i] += counter[i - 1];
}
// 倒序走訪,根據桶內統計結果,將各元素填入 res
List<int> res = List<int>.filled(n, 0);
for (int i = n - 1; i >= 0; i--) {
int d = digit(nums[i], exp);
int j = counter[d] - 1; // 獲取 d 在陣列中的索引 j
res[j] = nums[i]; // 將當前元素填入索引 j
counter[d]--; // 將 d 的數量減 1
}
// 使用結果覆蓋原陣列 nums
for (int i = 0; i < n; i++) nums[i] = res[i];
}
/* 基數排序 */
void radixSort(List<int> nums) {
// 獲取陣列的最大元素,用於判斷最大位數
// dart 中 int 的長度是 64 位的
int m = -1 << 63;
for (int _num in nums) if (_num > m) m = _num;
// 按照從低位到高位的順序走訪
for (int exp = 1; exp <= m; exp *= 10)
// 對陣列元素的第 k 位執行計數排序
// k = 1 -> exp = 1
// k = 2 -> exp = 10
// 即 exp = 10^(k-1)
countingSortDigit(nums, exp);
}
```
=== "Rust"
```rust title="radix_sort.rs"
/* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */
fn digit(num: i32, exp: i32) -> usize {
// 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算
return ((num / exp) % 10) as usize;
}
/* 計數排序(根據 nums 第 k 位排序) */
fn counting_sort_digit(nums: &mut [i32], exp: i32) {
// 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列
let mut counter = [0; 10];
let n = nums.len();
// 統計 0~9 各數字的出現次數
for i in 0..n {
let d = digit(nums[i], exp); // 獲取 nums[i] 第 k 位,記為 d
counter[d] += 1; // 統計數字 d 的出現次數
}
// 求前綴和,將“出現個數”轉換為“陣列索引”
for i in 1..10 {
counter[i] += counter[i - 1];
}
// 倒序走訪,根據桶內統計結果,將各元素填入 res
let mut res = vec![0; n];
for i in (0..n).rev() {
let d = digit(nums[i], exp);
let j = counter[d] - 1; // 獲取 d 在陣列中的索引 j
res[j] = nums[i]; // 將當前元素填入索引 j
counter[d] -= 1; // 將 d 的數量減 1
}
// 使用結果覆蓋原陣列 nums
for i in 0..n {
nums[i] = res[i];
}
}
/* 基數排序 */
fn radix_sort(nums: &mut [i32]) {
// 獲取陣列的最大元素,用於判斷最大位數
let m = *nums.into_iter().max().unwrap();
// 按照從低位到高位的順序走訪
let mut exp = 1;
while exp <= m {
counting_sort_digit(nums, exp);
exp *= 10;
}
}
```
=== "C"
```c title="radix_sort.c"
/* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */
int digit(int num, int exp) {
// 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算
return (num / exp) % 10;
}
/* 計數排序(根據 nums 第 k 位排序) */
void countingSortDigit(int nums[], int size, int exp) {
// 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列
int *counter = (int *)malloc((sizeof(int) * 10));
// 統計 0~9 各數字的出現次數
for (int i = 0; i < size; i++) {
// 獲取 nums[i] 第 k 位,記為 d
int d = digit(nums[i], exp);
// 統計數字 d 的出現次數
counter[d]++;
}
// 求前綴和,將“出現個數”轉換為“陣列索引”
for (int i = 1; i < 10; i++) {
counter[i] += counter[i - 1];
}
// 倒序走訪,根據桶內統計結果,將各元素填入 res
int *res = (int *)malloc(sizeof(int) * size);
for (int i = size - 1; i >= 0; i--) {
int d = digit(nums[i], exp);
int j = counter[d] - 1; // 獲取 d 在陣列中的索引 j
res[j] = nums[i]; // 將當前元素填入索引 j
counter[d]--; // 將 d 的數量減 1
}
// 使用結果覆蓋原陣列 nums
for (int i = 0; i < size; i++) {
nums[i] = res[i];
}
}
/* 基數排序 */
void radixSort(int nums[], int size) {
// 獲取陣列的最大元素,用於判斷最大位數
int max = INT32_MIN;
for (size_t i = 0; i < size - 1; i++) {
if (nums[i] > max) {
max = nums[i];
}
}
// 按照從低位到高位的順序走訪
for (int exp = 1; max >= exp; exp *= 10)
// 對陣列元素的第 k 位執行計數排序
// k = 1 -> exp = 1
// k = 2 -> exp = 10
// 即 exp = 10^(k-1)
countingSortDigit(nums, size, exp);
}
```
=== "Kotlin"
```kotlin title="radix_sort.kt"
/* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */
fun digit(num: Int, exp: Int): Int {
// 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算
return (num / exp) % 10
}
/* 計數排序(根據 nums 第 k 位排序) */
fun countingSortDigit(nums: IntArray, exp: Int) {
// 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列
val counter = IntArray(10)
val n = nums.size
// 統計 0~9 各數字的出現次數
for (i in 0..<n) {
val d = digit(nums[i], exp) // 獲取 nums[i] 第 k 位,記為 d
counter[d]++ // 統計數字 d 的出現次數
}
// 求前綴和,將“出現個數”轉換為“陣列索引”
for (i in 1..9) {
counter[i] += counter[i - 1]
}
// 倒序走訪,根據桶內統計結果,將各元素填入 res
val res = IntArray(n)
for (i in n - 1 downTo 0) {
val d = digit(nums[i], exp)
val j = counter[d] - 1 // 獲取 d 在陣列中的索引 j
res[j] = nums[i] // 將當前元素填入索引 j
counter[d]-- // 將 d 的數量減 1
}
// 使用結果覆蓋原陣列 nums
for (i in 0..<n) nums[i] = res[i]
}
/* 基數排序 */
fun radixSort(nums: IntArray) {
// 獲取陣列的最大元素,用於判斷最大位數
var m = Int.MIN_VALUE
for (num in nums) if (num > m) m = num
var exp = 1
// 按照從低位到高位的順序走訪
while (exp <= m) {
// 對陣列元素的第 k 位執行計數排序
// k = 1 -> exp = 1
// k = 2 -> exp = 10
// 即 exp = 10^(k-1)
countingSortDigit(nums, exp)
exp *= 10
}
}
```
=== "Ruby"
```ruby title="radix_sort.rb"
[class]{}-[func]{digit}
[class]{}-[func]{counting_sort_digit}
[class]{}-[func]{radix_sort}
```
=== "Zig"
```zig title="radix_sort.zig"
// 獲取元素 num 的第 k 位,其中 exp = 10^(k-1)
fn digit(num: i32, exp: i32) i32 {
// 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算
return @mod(@divFloor(num, exp), 10);
}
// 計數排序(根據 nums 第 k 位排序)
fn countingSortDigit(nums: []i32, exp: i32) !void {
// 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列
var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
// defer mem_arena.deinit();
const mem_allocator = mem_arena.allocator();
var counter = try mem_allocator.alloc(usize, 10);
@memset(counter, 0);
var n = nums.len;
// 統計 0~9 各數字的出現次數
for (nums) |num| {
var d: u32 = @bitCast(digit(num, exp)); // 獲取 nums[i] 第 k 位,記為 d
counter[d] += 1; // 統計數字 d 的出現次數
}
// 求前綴和,將“出現個數”轉換為“陣列索引”
var i: usize = 1;
while (i < 10) : (i += 1) {
counter[i] += counter[i - 1];
}
// 倒序走訪,根據桶內統計結果,將各元素填入 res
var res = try mem_allocator.alloc(i32, n);
i = n - 1;
while (i >= 0) : (i -= 1) {
var d: u32 = @bitCast(digit(nums[i], exp));
var j = counter[d] - 1; // 獲取 d 在陣列中的索引 j
res[j] = nums[i]; // 將當前元素填入索引 j
counter[d] -= 1; // 將 d 的數量減 1
if (i == 0) break;
}
// 使用結果覆蓋原陣列 nums
i = 0;
while (i < n) : (i += 1) {
nums[i] = res[i];
}
}
// 基數排序
fn radixSort(nums: []i32) !void {
// 獲取陣列的最大元素,用於判斷最大位數
var m: i32 = std.math.minInt(i32);
for (nums) |num| {
if (num > m) m = num;
}
// 按照從低位到高位的順序走訪
var exp: i32 = 1;
while (exp <= m) : (exp *= 10) {
// 對陣列元素的第 k 位執行計數排序
// k = 1 -> exp = 1
// k = 2 -> exp = 10
// 即 exp = 10^(k-1)
try countingSortDigit(nums, exp);
}
}
```
??? pythontutor "視覺化執行"
<div style="height: 549px; width: 100%;"><iframe class="pythontutor-iframe" src="https://pythontutor.com/iframe-embed.html#code=def%20digit%28num%3A%20int,%20exp%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%85%83%E7%B4%A0%20num%20%E7%9A%84%E7%AC%AC%20k%20%E4%BD%8D%EF%BC%8C%E5%85%B6%E4%B8%AD%20exp%20%3D%2010%5E%28k-1%29%22%22%22%0A%20%20%20%20%23%20%E4%BC%A0%E5%85%A5%20exp%20%E8%80%8C%E9%9D%9E%20k%20%E5%8F%AF%E4%BB%A5%E9%81%BF%E5%85%8D%E5%9C%A8%E6%AD%A4%E9%87%8D%E5%A4%8D%E6%89%A7%E8%A1%8C%E6%98%82%E8%B4%B5%E7%9A%84%E6%AC%A1%E6%96%B9%E8%AE%A1%E7%AE%97%0A%20%20%20%20return%20%28num%20//%20exp%29%20%25%2010%0A%0Adef%20counting_sort_digit%28nums%3A%20list%5Bint%5D,%20exp%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%EF%BC%88%E6%A0%B9%E6%8D%AE%20nums%20%E7%AC%AC%20k%20%E4%BD%8D%E6%8E%92%E5%BA%8F%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E5%8D%81%E8%BF%9B%E5%88%B6%E7%9A%84%E4%BD%8D%E8%8C%83%E5%9B%B4%E4%B8%BA%200~9%20%EF%BC%8C%E5%9B%A0%E6%AD%A4%E9%9C%80%E8%A6%81%E9%95%BF%E5%BA%A6%E4%B8%BA%2010%20%E7%9A%84%E6%A1%B6%E6%95%B0%E7%BB%84%0A%20%20%20%20counter%20%3D%20%5B0%5D%20*%2010%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E7%BB%9F%E8%AE%A1%200~9%20%E5%90%84%E6%95%B0%E5%AD%97%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20d%20%3D%20digit%28nums%5Bi%5D,%20exp%29%20%20%23%20%E8%8E%B7%E5%8F%96%20nums%5Bi%5D%20%E7%AC%AC%20k%20%E4%BD%8D%EF%BC%8C%E8%AE%B0%E4%B8%BA%20d%0A%20%20%20%20%20%20%20%20counter%5Bd%5D%20%2B%3D%201%20%20%23%20%E7%BB%9F%E8%AE%A1%E6%95%B0%E5%AD%97%20d%20%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20%23%20%E6%B1%82%E5%89%8D%E7%BC%80%E5%92%8C%EF%BC%8C%E5%B0%86%E2%80%9C%E5%87%BA%E7%8E%B0%E4%B8%AA%E6%95%B0%E2%80%9D%E8%BD%AC%E6%8D%A2%E4%B8%BA%E2%80%9C%E6%95%B0%E7%BB%84%E7%B4%A2%E5%BC%95%E2%80%9D%0A%20%20%20%20for%20i%20in%20range%281,%2010%29%3A%0A%20%20%20%20%20%20%20%20counter%5Bi%5D%20%2B%3D%20counter%5Bi%20-%201%5D%0A%20%20%20%20%23%20%E5%80%92%E5%BA%8F%E9%81%8D%E5%8E%86%EF%BC%8C%E6%A0%B9%E6%8D%AE%E6%A1%B6%E5%86%85%E7%BB%9F%E8%AE%A1%E7%BB%93%E6%9E%9C%EF%BC%8C%E5%B0%86%E5%90%84%E5%85%83%E7%B4%A0%E5%A1%AB%E5%85%A5%20res%0A%20%20%20%20res%20%3D%20%5B0%5D%20*%20n%0A%20%20%20%20for%20i%20in%20range%28n%20-%201,%20-1,%20-1%29%3A%0A%20%20%20%20%20%20%20%20d%20%3D%20digit%28nums%5Bi%5D,%20exp%29%0A%20%20%20%20%20%20%20%20j%20%3D%20counter%5Bd%5D%20-%201%20%20%23%20%E8%8E%B7%E5%8F%96%20d%20%E5%9C%A8%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E7%B4%A2%E5%BC%95%20j%0A%20%20%20%20%20%20%20%20res%5Bj%5D%20%3D%20nums%5Bi%5D%20%20%23%20%E5%B0%86%E5%BD%93%E5%89%8D%E5%85%83%E7%B4%A0%E5%A1%AB%E5%85%A5%E7%B4%A2%E5%BC%95%20j%0A%20%20%20%20%20%20%20%20counter%5Bd%5D%20-%3D%201%20%20%23%20%E5%B0%86%20d%20%E7%9A%84%E6%95%B0%E9%87%8F%E5%87%8F%201%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E7%BB%93%E6%9E%9C%E8%A6%86%E7%9B%96%E5%8E%9F%E6%95%B0%E7%BB%84%20nums%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20res%5Bi%5D%0A%0Adef%20radix_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E6%95%B0%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%EF%BC%8C%E7%94%A8%E4%BA%8E%E5%88%A4%E6%96%AD%E6%9C%80%E5%A4%A7%E4%BD%8D%E6%95%B0%0A%20%20%20%20m%20%3D%20max%28nums%29%0A%20%20%20%20%23%20%E6%8C%89%E7%85%A7%E4%BB%8E%E4%BD%8E%E4%BD%8D%E5%88%B0%E9%AB%98%E4%BD%8D%E7%9A%84%E9%A1%BA%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20exp%20%3D%201%0A%20%20%20%20while%20exp%20%3C%3D%20m%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%AF%B9%E6%95%B0%E7%BB%84%E5%85%83%E7%B4%A0%E7%9A%84%E7%AC%AC%20k%20%E4%BD%8D%E6%89%A7%E8%A1%8C%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%0A%20%20%20%20%20%20%20%20%23%20k%20%3D%201%20-%3E%20exp%20%3D%201%0A%20%20%20%20%20%20%20%20%23%20k%20%3D%202%20-%3E%20exp%20%3D%2010%0A%20%20%20%20%20%20%20%20%23%20%E5%8D%B3%20exp%20%3D%2010%5E%28k-1%29%0A%20%20%20%20%20%20%20%20counting_sort_digit%28nums,%20exp%29%0A%20%20%20%20%20%20%20%20exp%20*%3D%2010%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%9F%BA%E6%95%B0%E6%8E%92%E5%BA%8F%0A%20%20%20%20nums%20%3D%20%5B%0A%20%20%20%20%20%20%20%20105,%0A%20%20%20%20%20%20%20%20356,%0A%20%20%20%20%20%20%20%20428,%0A%20%20%20%20%20%20%20%20348,%0A%20%20%20%20%20%20%20%20818,%0A%20%20%20%20%5D%0A%20%20%20%20radix_sort%28nums%29%0A%20%20%20%20print%28%22%E5%9F%BA%E6%95%B0%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&codeDivHeight=472&codeDivWidth=350&cumulative=false&curInstr=6&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe></div>
<div style="margin-top: 5px;"><a href="https://pythontutor.com/iframe-embed.html#code=def%20digit%28num%3A%20int,%20exp%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%85%83%E7%B4%A0%20num%20%E7%9A%84%E7%AC%AC%20k%20%E4%BD%8D%EF%BC%8C%E5%85%B6%E4%B8%AD%20exp%20%3D%2010%5E%28k-1%29%22%22%22%0A%20%20%20%20%23%20%E4%BC%A0%E5%85%A5%20exp%20%E8%80%8C%E9%9D%9E%20k%20%E5%8F%AF%E4%BB%A5%E9%81%BF%E5%85%8D%E5%9C%A8%E6%AD%A4%E9%87%8D%E5%A4%8D%E6%89%A7%E8%A1%8C%E6%98%82%E8%B4%B5%E7%9A%84%E6%AC%A1%E6%96%B9%E8%AE%A1%E7%AE%97%0A%20%20%20%20return%20%28num%20//%20exp%29%20%25%2010%0A%0Adef%20counting_sort_digit%28nums%3A%20list%5Bint%5D,%20exp%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%EF%BC%88%E6%A0%B9%E6%8D%AE%20nums%20%E7%AC%AC%20k%20%E4%BD%8D%E6%8E%92%E5%BA%8F%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E5%8D%81%E8%BF%9B%E5%88%B6%E7%9A%84%E4%BD%8D%E8%8C%83%E5%9B%B4%E4%B8%BA%200~9%20%EF%BC%8C%E5%9B%A0%E6%AD%A4%E9%9C%80%E8%A6%81%E9%95%BF%E5%BA%A6%E4%B8%BA%2010%20%E7%9A%84%E6%A1%B6%E6%95%B0%E7%BB%84%0A%20%20%20%20counter%20%3D%20%5B0%5D%20*%2010%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E7%BB%9F%E8%AE%A1%200~9%20%E5%90%84%E6%95%B0%E5%AD%97%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20d%20%3D%20digit%28nums%5Bi%5D,%20exp%29%20%20%23%20%E8%8E%B7%E5%8F%96%20nums%5Bi%5D%20%E7%AC%AC%20k%20%E4%BD%8D%EF%BC%8C%E8%AE%B0%E4%B8%BA%20d%0A%20%20%20%20%20%20%20%20counter%5Bd%5D%20%2B%3D%201%20%20%23%20%E7%BB%9F%E8%AE%A1%E6%95%B0%E5%AD%97%20d%20%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20%23%20%E6%B1%82%E5%89%8D%E7%BC%80%E5%92%8C%EF%BC%8C%E5%B0%86%E2%80%9C%E5%87%BA%E7%8E%B0%E4%B8%AA%E6%95%B0%E2%80%9D%E8%BD%AC%E6%8D%A2%E4%B8%BA%E2%80%9C%E6%95%B0%E7%BB%84%E7%B4%A2%E5%BC%95%E2%80%9D%0A%20%20%20%20for%20i%20in%20range%281,%2010%29%3A%0A%20%20%20%20%20%20%20%20counter%5Bi%5D%20%2B%3D%20counter%5Bi%20-%201%5D%0A%20%20%20%20%23%20%E5%80%92%E5%BA%8F%E9%81%8D%E5%8E%86%EF%BC%8C%E6%A0%B9%E6%8D%AE%E6%A1%B6%E5%86%85%E7%BB%9F%E8%AE%A1%E7%BB%93%E6%9E%9C%EF%BC%8C%E5%B0%86%E5%90%84%E5%85%83%E7%B4%A0%E5%A1%AB%E5%85%A5%20res%0A%20%20%20%20res%20%3D%20%5B0%5D%20*%20n%0A%20%20%20%20for%20i%20in%20range%28n%20-%201,%20-1,%20-1%29%3A%0A%20%20%20%20%20%20%20%20d%20%3D%20digit%28nums%5Bi%5D,%20exp%29%0A%20%20%20%20%20%20%20%20j%20%3D%20counter%5Bd%5D%20-%201%20%20%23%20%E8%8E%B7%E5%8F%96%20d%20%E5%9C%A8%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E7%B4%A2%E5%BC%95%20j%0A%20%20%20%20%20%20%20%20res%5Bj%5D%20%3D%20nums%5Bi%5D%20%20%23%20%E5%B0%86%E5%BD%93%E5%89%8D%E5%85%83%E7%B4%A0%E5%A1%AB%E5%85%A5%E7%B4%A2%E5%BC%95%20j%0A%20%20%20%20%20%20%20%20counter%5Bd%5D%20-%3D%201%20%20%23%20%E5%B0%86%20d%20%E7%9A%84%E6%95%B0%E9%87%8F%E5%87%8F%201%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E7%BB%93%E6%9E%9C%E8%A6%86%E7%9B%96%E5%8E%9F%E6%95%B0%E7%BB%84%20nums%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20res%5Bi%5D%0A%0Adef%20radix_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E6%95%B0%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%EF%BC%8C%E7%94%A8%E4%BA%8E%E5%88%A4%E6%96%AD%E6%9C%80%E5%A4%A7%E4%BD%8D%E6%95%B0%0A%20%20%20%20m%20%3D%20max%28nums%29%0A%20%20%20%20%23%20%E6%8C%89%E7%85%A7%E4%BB%8E%E4%BD%8E%E4%BD%8D%E5%88%B0%E9%AB%98%E4%BD%8D%E7%9A%84%E9%A1%BA%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20exp%20%3D%201%0A%20%20%20%20while%20exp%20%3C%3D%20m%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%AF%B9%E6%95%B0%E7%BB%84%E5%85%83%E7%B4%A0%E7%9A%84%E7%AC%AC%20k%20%E4%BD%8D%E6%89%A7%E8%A1%8C%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%0A%20%20%20%20%20%20%20%20%23%20k%20%3D%201%20-%3E%20exp%20%3D%201%0A%20%20%20%20%20%20%20%20%23%20k%20%3D%202%20-%3E%20exp%20%3D%2010%0A%20%20%20%20%20%20%20%20%23%20%E5%8D%B3%20exp%20%3D%2010%5E%28k-1%29%0A%20%20%20%20%20%20%20%20counting_sort_digit%28nums,%20exp%29%0A%20%20%20%20%20%20%20%20exp%20*%3D%2010%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%9F%BA%E6%95%B0%E6%8E%92%E5%BA%8F%0A%20%20%20%20nums%20%3D%20%5B%0A%20%20%20%20%20%20%20%20105,%0A%20%20%20%20%20%20%20%20356,%0A%20%20%20%20%20%20%20%20428,%0A%20%20%20%20%20%20%20%20348,%0A%20%20%20%20%20%20%20%20818,%0A%20%20%20%20%5D%0A%20%20%20%20radix_sort%28nums%29%0A%20%20%20%20print%28%22%E5%9F%BA%E6%95%B0%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=6&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">全螢幕觀看 ></a></div>
!!! question "為什麼從最低位開始排序?"
在連續的排序輪次中,後一輪排序會覆蓋前一輪排序的結果。舉例來說,如果第一輪排序結果 $a < b$ ,而第二輪排序結果 $a > b$ ,那麼第二輪的結果將取代第一輪的結果。由於數字的高位優先順序高於低位,因此應該先排序低位再排序高位。
## 11.10.2 &nbsp; 演算法特性
相較於計數排序,基數排序適用於數值範圍較大的情況,**但前提是資料必須可以表示為固定位數的格式,且位數不能過大**。例如,浮點數不適合使用基數排序,因為其位數 $k$ 過大,可能導致時間複雜度 $O(nk) \gg O(n^2)$ 。
- **時間複雜度為 $O(nk)$**:設資料量為 $n$、資料為 $d$ 進位制、最大位數為 $k$ ,則對某一位執行計數排序使用 $O(n + d)$ 時間,排序所有 $k$ 位使用 $O((n + d)k)$ 時間。通常情況下,$d$ 和 $k$ 都相對較小,時間複雜度趨向 $O(n)$ 。
- **空間複雜度為 $O(n + d)$、非原地排序**:與計數排序相同,基數排序需要藉助長度為 $n$ 和 $d$ 的陣列 `res` 和 `counter` 。
- **穩定排序**:當計數排序穩定時,基數排序也穩定;當計數排序不穩定時,基數排序無法保證得到正確的排序結果。
@@ -0,0 +1,328 @@
---
comments: true
---
# 11.2 &nbsp; 選擇排序
<u>選擇排序(selection sort</u>的工作原理非常簡單:開啟一個迴圈,每輪從未排序區間選擇最小的元素,將其放到已排序區間的末尾。
設陣列的長度為 $n$ ,選擇排序的演算法流程如圖 11-2 所示。
1. 初始狀態下,所有元素未排序,即未排序(索引)區間為 $[0, n-1]$ 。
2. 選取區間 $[0, n-1]$ 中的最小元素,將其與索引 $0$ 處的元素交換。完成後,陣列前 1 個元素已排序。
3. 選取區間 $[1, n-1]$ 中的最小元素,將其與索引 $1$ 處的元素交換。完成後,陣列前 2 個元素已排序。
4. 以此類推。經過 $n - 1$ 輪選擇與交換後,陣列前 $n - 1$ 個元素已排序。
5. 僅剩的一個元素必定是最大元素,無須排序,因此陣列排序完成。
=== "<1>"
![選擇排序步驟](selection_sort.assets/selection_sort_step1.png){ class="animation-figure" }
=== "<2>"
![selection_sort_step2](selection_sort.assets/selection_sort_step2.png){ class="animation-figure" }
=== "<3>"
![selection_sort_step3](selection_sort.assets/selection_sort_step3.png){ class="animation-figure" }
=== "<4>"
![selection_sort_step4](selection_sort.assets/selection_sort_step4.png){ class="animation-figure" }
=== "<5>"
![selection_sort_step5](selection_sort.assets/selection_sort_step5.png){ class="animation-figure" }
=== "<6>"
![selection_sort_step6](selection_sort.assets/selection_sort_step6.png){ class="animation-figure" }
=== "<7>"
![selection_sort_step7](selection_sort.assets/selection_sort_step7.png){ class="animation-figure" }
=== "<8>"
![selection_sort_step8](selection_sort.assets/selection_sort_step8.png){ class="animation-figure" }
=== "<9>"
![selection_sort_step9](selection_sort.assets/selection_sort_step9.png){ class="animation-figure" }
=== "<10>"
![selection_sort_step10](selection_sort.assets/selection_sort_step10.png){ class="animation-figure" }
=== "<11>"
![selection_sort_step11](selection_sort.assets/selection_sort_step11.png){ class="animation-figure" }
<p align="center"> 圖 11-2 &nbsp; 選擇排序步驟 </p>
在程式碼中,我們用 $k$ 來記錄未排序區間內的最小元素:
=== "Python"
```python title="selection_sort.py"
def selection_sort(nums: list[int]):
"""選擇排序"""
n = len(nums)
# 外迴圈:未排序區間為 [i, n-1]
for i in range(n - 1):
# 內迴圈:找到未排序區間內的最小元素
k = i
for j in range(i + 1, n):
if nums[j] < nums[k]:
k = j # 記錄最小元素的索引
# 將該最小元素與未排序區間的首個元素交換
nums[i], nums[k] = nums[k], nums[i]
```
=== "C++"
```cpp title="selection_sort.cpp"
/* 選擇排序 */
void selectionSort(vector<int> &nums) {
int n = nums.size();
// 外迴圈:未排序區間為 [i, n-1]
for (int i = 0; i < n - 1; i++) {
// 內迴圈:找到未排序區間內的最小元素
int k = i;
for (int j = i + 1; j < n; j++) {
if (nums[j] < nums[k])
k = j; // 記錄最小元素的索引
}
// 將該最小元素與未排序區間的首個元素交換
swap(nums[i], nums[k]);
}
}
```
=== "Java"
```java title="selection_sort.java"
/* 選擇排序 */
void selectionSort(int[] nums) {
int n = nums.length;
// 外迴圈:未排序區間為 [i, n-1]
for (int i = 0; i < n - 1; i++) {
// 內迴圈:找到未排序區間內的最小元素
int k = i;
for (int j = i + 1; j < n; j++) {
if (nums[j] < nums[k])
k = j; // 記錄最小元素的索引
}
// 將該最小元素與未排序區間的首個元素交換
int temp = nums[i];
nums[i] = nums[k];
nums[k] = temp;
}
}
```
=== "C#"
```csharp title="selection_sort.cs"
/* 選擇排序 */
void SelectionSort(int[] nums) {
int n = nums.Length;
// 外迴圈:未排序區間為 [i, n-1]
for (int i = 0; i < n - 1; i++) {
// 內迴圈:找到未排序區間內的最小元素
int k = i;
for (int j = i + 1; j < n; j++) {
if (nums[j] < nums[k])
k = j; // 記錄最小元素的索引
}
// 將該最小元素與未排序區間的首個元素交換
(nums[k], nums[i]) = (nums[i], nums[k]);
}
}
```
=== "Go"
```go title="selection_sort.go"
/* 選擇排序 */
func selectionSort(nums []int) {
n := len(nums)
// 外迴圈:未排序區間為 [i, n-1]
for i := 0; i < n-1; i++ {
// 內迴圈:找到未排序區間內的最小元素
k := i
for j := i + 1; j < n; j++ {
if nums[j] < nums[k] {
// 記錄最小元素的索引
k = j
}
}
// 將該最小元素與未排序區間的首個元素交換
nums[i], nums[k] = nums[k], nums[i]
}
}
```
=== "Swift"
```swift title="selection_sort.swift"
/* 選擇排序 */
func selectionSort(nums: inout [Int]) {
// 外迴圈:未排序區間為 [i, n-1]
for i in nums.indices.dropLast() {
// 內迴圈:找到未排序區間內的最小元素
var k = i
for j in nums.indices.dropFirst(i + 1) {
if nums[j] < nums[k] {
k = j // 記錄最小元素的索引
}
}
// 將該最小元素與未排序區間的首個元素交換
nums.swapAt(i, k)
}
}
```
=== "JS"
```javascript title="selection_sort.js"
/* 選擇排序 */
function selectionSort(nums) {
let n = nums.length;
// 外迴圈:未排序區間為 [i, n-1]
for (let i = 0; i < n - 1; i++) {
// 內迴圈:找到未排序區間內的最小元素
let k = i;
for (let j = i + 1; j < n; j++) {
if (nums[j] < nums[k]) {
k = j; // 記錄最小元素的索引
}
}
// 將該最小元素與未排序區間的首個元素交換
[nums[i], nums[k]] = [nums[k], nums[i]];
}
}
```
=== "TS"
```typescript title="selection_sort.ts"
/* 選擇排序 */
function selectionSort(nums: number[]): void {
let n = nums.length;
// 外迴圈:未排序區間為 [i, n-1]
for (let i = 0; i < n - 1; i++) {
// 內迴圈:找到未排序區間內的最小元素
let k = i;
for (let j = i + 1; j < n; j++) {
if (nums[j] < nums[k]) {
k = j; // 記錄最小元素的索引
}
}
// 將該最小元素與未排序區間的首個元素交換
[nums[i], nums[k]] = [nums[k], nums[i]];
}
}
```
=== "Dart"
```dart title="selection_sort.dart"
/* 選擇排序 */
void selectionSort(List<int> nums) {
int n = nums.length;
// 外迴圈:未排序區間為 [i, n-1]
for (int i = 0; i < n - 1; i++) {
// 內迴圈:找到未排序區間內的最小元素
int k = i;
for (int j = i + 1; j < n; j++) {
if (nums[j] < nums[k]) k = j; // 記錄最小元素的索引
}
// 將該最小元素與未排序區間的首個元素交換
int temp = nums[i];
nums[i] = nums[k];
nums[k] = temp;
}
}
```
=== "Rust"
```rust title="selection_sort.rs"
/* 選擇排序 */
fn selection_sort(nums: &mut [i32]) {
if nums.is_empty() {
return;
}
let n = nums.len();
// 外迴圈:未排序區間為 [i, n-1]
for i in 0..n - 1 {
// 內迴圈:找到未排序區間內的最小元素
let mut k = i;
for j in i + 1..n {
if nums[j] < nums[k] {
k = j; // 記錄最小元素的索引
}
}
// 將該最小元素與未排序區間的首個元素交換
nums.swap(i, k);
}
}
```
=== "C"
```c title="selection_sort.c"
/* 選擇排序 */
void selectionSort(int nums[], int n) {
// 外迴圈:未排序區間為 [i, n-1]
for (int i = 0; i < n - 1; i++) {
// 內迴圈:找到未排序區間內的最小元素
int k = i;
for (int j = i + 1; j < n; j++) {
if (nums[j] < nums[k])
k = j; // 記錄最小元素的索引
}
// 將該最小元素與未排序區間的首個元素交換
int temp = nums[i];
nums[i] = nums[k];
nums[k] = temp;
}
}
```
=== "Kotlin"
```kotlin title="selection_sort.kt"
/* 選擇排序 */
fun selectionSort(nums: IntArray) {
val n = nums.size
// 外迴圈:未排序區間為 [i, n-1]
for (i in 0..<n - 1) {
var k = i
// 內迴圈:找到未排序區間內的最小元素
for (j in i + 1..<n) {
if (nums[j] < nums[k]) k = j // 記錄最小元素的索引
}
// 將該最小元素與未排序區間的首個元素交換
nums[i] = nums[k].also { nums[k] = nums[i] }
}
}
```
=== "Ruby"
```ruby title="selection_sort.rb"
[class]{}-[func]{selection_sort}
```
=== "Zig"
```zig title="selection_sort.zig"
[class]{}-[func]{selectionSort}
```
??? pythontutor "視覺化執行"
<div style="height: 531px; width: 100%;"><iframe class="pythontutor-iframe" src="https://pythontutor.com/iframe-embed.html#code=def%20selection_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E5%BE%AA%E7%8E%AF%EF%BC%9A%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5Bi,%20n-1%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%BE%AA%E7%8E%AF%EF%BC%9A%E6%89%BE%E5%88%B0%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E5%86%85%E7%9A%84%E6%9C%80%E5%B0%8F%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20k%20%3D%20i%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%20%2B%201,%20n%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3C%20nums%5Bk%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20k%20%3D%20j%20%20%23%20%E8%AE%B0%E5%BD%95%E6%9C%80%E5%B0%8F%E5%85%83%E7%B4%A0%E7%9A%84%E7%B4%A2%E5%BC%95%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%86%E8%AF%A5%E6%9C%80%E5%B0%8F%E5%85%83%E7%B4%A0%E4%B8%8E%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E7%9A%84%E9%A6%96%E4%B8%AA%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%0A%20%20%20%20%20%20%20%20nums%5Bi%5D,%20nums%5Bk%5D%20%3D%20nums%5Bk%5D,%20nums%5Bi%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4,%201,%203,%201,%205,%202%5D%0A%20%20%20%20selection_sort%28nums%29%0A%20%20%20%20print%28%22%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&codeDivHeight=472&codeDivWidth=350&cumulative=false&curInstr=4&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe></div>
<div style="margin-top: 5px;"><a href="https://pythontutor.com/iframe-embed.html#code=def%20selection_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E5%BE%AA%E7%8E%AF%EF%BC%9A%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5Bi,%20n-1%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%BE%AA%E7%8E%AF%EF%BC%9A%E6%89%BE%E5%88%B0%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E5%86%85%E7%9A%84%E6%9C%80%E5%B0%8F%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20k%20%3D%20i%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%20%2B%201,%20n%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3C%20nums%5Bk%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20k%20%3D%20j%20%20%23%20%E8%AE%B0%E5%BD%95%E6%9C%80%E5%B0%8F%E5%85%83%E7%B4%A0%E7%9A%84%E7%B4%A2%E5%BC%95%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%86%E8%AF%A5%E6%9C%80%E5%B0%8F%E5%85%83%E7%B4%A0%E4%B8%8E%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E7%9A%84%E9%A6%96%E4%B8%AA%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%0A%20%20%20%20%20%20%20%20nums%5Bi%5D,%20nums%5Bk%5D%20%3D%20nums%5Bk%5D,%20nums%5Bi%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4,%201,%203,%201,%205,%202%5D%0A%20%20%20%20selection_sort%28nums%29%0A%20%20%20%20print%28%22%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=4&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">全螢幕觀看 ></a></div>
## 11.2.1 &nbsp; 演算法特性
- **時間複雜度為 $O(n^2)$、非自適應排序**:外迴圈共 $n - 1$ 輪,第一輪的未排序區間長度為 $n$ ,最後一輪的未排序區間長度為 $2$ ,即各輪外迴圈分別包含 $n$、$n - 1$、$\dots$、$3$、$2$ 輪內迴圈,求和為 $\frac{(n - 1)(n + 2)}{2}$ 。
- **空間複雜度為 $O(1)$、原地排序**:指標 $i$ 和 $j$ 使用常數大小的額外空間。
- **非穩定排序**:如圖 11-3 所示,元素 `nums[i]` 有可能被交換至與其相等的元素的右邊,導致兩者的相對順序發生改變。
![選擇排序非穩定示例](selection_sort.assets/selection_sort_instability.png){ class="animation-figure" }
<p align="center"> 圖 11-3 &nbsp; 選擇排序非穩定示例 </p>
@@ -0,0 +1,54 @@
---
comments: true
---
# 11.1 &nbsp; 排序演算法
<u>排序演算法(sorting algorithm</u>用於對一組資料按照特定順序進行排列。排序演算法有著廣泛的應用,因為有序資料通常能夠被更高效地查詢、分析和處理。
如圖 11-1 所示,排序演算法中的資料型別可以是整數、浮點數、字元或字串等。排序的判斷規則可根據需求設定,如數字大小、字元 ASCII 碼順序或自定義規則。
![資料型別和判斷規則示例](sorting_algorithm.assets/sorting_examples.png){ class="animation-figure" }
<p align="center"> 圖 11-1 &nbsp; 資料型別和判斷規則示例 </p>
## 11.1.1 &nbsp; 評價維度
**執行效率**:我們期望排序演算法的時間複雜度儘量低,且總體操作數量較少(時間複雜度中的常數項變小)。對於大資料量的情況,執行效率顯得尤為重要。
**就地性**:顧名思義,<u>原地排序</u>透過在原陣列上直接操作實現排序,無須藉助額外的輔助陣列,從而節省記憶體。通常情況下,原地排序的資料搬運操作較少,執行速度也更快。
**穩定性**<u>穩定排序</u>在完成排序後,相等元素在陣列中的相對順序不發生改變。
穩定排序是多級排序場景的必要條件。假設我們有一個儲存學生資訊的表格,第 1 列和第 2 列分別是姓名和年齡。在這種情況下,<u>非穩定排序</u>可能導致輸入資料的有序性喪失:
```shell
# 輸入資料是按照姓名排序好的
# (name, age)
('A', 19)
('B', 18)
('C', 21)
('D', 19)
('E', 23)
# 假設使用非穩定排序演算法按年齡排序串列,
# 結果中 ('D', 19) 和 ('A', 19) 的相對位置改變,
# 輸入資料按姓名排序的性質丟失
('B', 18)
('D', 19)
('A', 19)
('C', 21)
('E', 23)
```
**自適應性**<u>自適應排序</u>的時間複雜度會受輸入資料的影響,即最佳時間複雜度、最差時間複雜度、平均時間複雜度並不完全相等。
自適應性需要根據具體情況來評估。如果最差時間複雜度差於平均時間複雜度,說明排序演算法在某些資料下效能可能劣化,因此被視為負面屬性;而如果最佳時間複雜度優於平均時間複雜度,則被視為正面屬性。
**是否基於比較**<u>基於比較的排序</u>依賴比較運算子($<$、$=$、$>$)來判斷元素的相對順序,從而排序整個陣列,理論最優時間複雜度為 $O(n \log n)$ 。而<u>非比較排序</u>不使用比較運算子,時間複雜度可達 $O(n)$ ,但其通用性相對較差。
## 11.1.2 &nbsp; 理想排序演算法
**執行快、原地、穩定、正向自適應、通用性好**。顯然,迄今為止尚未發現兼具以上所有特性的排序演算法。因此,在選擇排序演算法時,需要根據具體的資料特點和問題需求來決定。
接下來,我們將共同學習各種排序演算法,並基於上述評價維度對各個排序演算法的優缺點進行分析。
+53
View File
@@ -0,0 +1,53 @@
---
comments: true
---
# 11.11 &nbsp; 小結
### 1. &nbsp; 重點回顧
- 泡沫排序透過交換相鄰元素來實現排序。透過新增一個標誌位來實現提前返回,我們可以將泡沫排序的最佳時間複雜度最佳化到 $O(n)$ 。
- 插入排序每輪將未排序區間內的元素插入到已排序區間的正確位置,從而完成排序。雖然插入排序的時間複雜度為 $O(n^2)$ ,但由於單元操作相對較少,因此在小資料量的排序任務中非常受歡迎。
- 快速排序基於哨兵劃分操作實現排序。在哨兵劃分中,有可能每次都選取到最差的基準數,導致時間複雜度劣化至 $O(n^2)$ 。引入中位數基準數或隨機基準數可以降低這種劣化的機率。尾遞迴方法可以有效地減少遞迴深度,將空間複雜度最佳化到 $O(\log n)$ 。
- 合併排序包括劃分和合並兩個階段,典型地體現了分治策略。在合併排序中,排序陣列需要建立輔助陣列,空間複雜度為 $O(n)$ ;然而排序鏈結串列的空間複雜度可以最佳化至 $O(1)$ 。
- 桶排序包含三個步驟:資料分桶、桶內排序和合並結果。它同樣體現了分治策略,適用於資料體量很大的情況。桶排序的關鍵在於對資料進行平均分配。
- 計數排序是桶排序的一個特例,它透過統計資料出現的次數來實現排序。計數排序適用於資料量大但資料範圍有限的情況,並且要求資料能夠轉換為正整數。
- 基數排序透過逐位排序來實現資料排序,要求資料能夠表示為固定位數的數字。
- 總的來說,我們希望找到一種排序演算法,具有高效率、穩定、原地以及正向自適應性等優點。然而,正如其他資料結構和演算法一樣,沒有一種排序演算法能夠同時滿足所有這些條件。在實際應用中,我們需要根據資料的特性來選擇合適的排序演算法。
- 圖 11-19 對比了主流排序演算法的效率、穩定性、就地性和自適應性等。
![排序演算法對比](summary.assets/sorting_algorithms_comparison.png){ class="animation-figure" }
<p align="center"> 圖 11-19 &nbsp; 排序演算法對比 </p>
### 2. &nbsp; Q & A
**Q**:排序演算法穩定性在什麼情況下是必需的?
在現實中,我們有可能基於物件的某個屬性進行排序。例如,學生有姓名和身高兩個屬性,我們希望實現一個多級排序:先按照姓名進行排序,得到 `(A, 180) (B, 185) (C, 170) (D, 170)` ;再對身高進行排序。由於排序演算法不穩定,因此可能得到 `(D, 170) (C, 170) (A, 180) (B, 185)`
可以發現,學生 D 和 C 的位置發生了交換,姓名的有序性被破壞了,而這是我們不希望看到的。
**Q**:哨兵劃分中“從右往左查詢”與“從左往右查詢”的順序可以交換嗎?
不行,當我們以最左端元素為基準數時,必須先“從右往左查詢”再“從左往右查詢”。這個結論有些反直覺,我們來剖析一下原因。
哨兵劃分 `partition()` 的最後一步是交換 `nums[left]``nums[i]` 。完成交換後,基準數左邊的元素都 `<=` 基準數,**這就要求最後一步交換前 `nums[left] >= nums[i]` 必須成立**。假設我們先“從左往右查詢”,那麼如果找不到比基準數更大的元素,**則會在 `i == j` 時跳出迴圈,此時可能 `nums[j] == nums[i] > nums[left]`**。也就是說,此時最後一步交換操作會把一個比基準數更大的元素交換至陣列最左端,導致哨兵劃分失敗。
舉個例子,給定陣列 `[0, 0, 0, 0, 1]` ,如果先“從左向右查詢”,哨兵劃分後陣列為 `[1, 0, 0, 0, 0]` ,這個結果是不正確的。
再深入思考一下,如果我們選擇 `nums[right]` 為基準數,那麼正好反過來,必須先“從左往右查詢”。
**Q**:關於尾遞迴最佳化,為什麼選短的陣列能保證遞迴深度不超過 $\log n$ ?
遞迴深度就是當前未返回的遞迴方法的數量。每輪哨兵劃分我們將原陣列劃分為兩個子陣列。在尾遞迴最佳化後,向下遞迴的子陣列長度最大為原陣列長度的一半。假設最差情況,一直為一半長度,那麼最終的遞迴深度就是 $\log n$ 。
回顧原始的快速排序,我們有可能會連續地遞迴長度較大的陣列,最差情況下為 $n$、$n - 1$、$\dots$、$2$、$1$ ,遞迴深度為 $n$ 。尾遞迴最佳化可以避免這種情況出現。
**Q**:當陣列中所有元素都相等時,快速排序的時間複雜度是 $O(n^2)$ 嗎?該如何處理這種退化情況?
是的。對於這種情況,可以考慮透過哨兵劃分將陣列劃分為三個部分:小於、等於、大於基準數。僅向下遞迴小於和大於的兩部分。在該方法下,輸入元素全部相等的陣列,僅一輪哨兵劃分即可完成排序。
**Q**:桶排序的最差時間複雜度為什麼是 $O(n^2)$ ?
最差情況下,所有元素被分至同一個桶中。如果我們採用一個 $O(n^2)$ 演算法來排序這些元素,則時間複雜度為 $O(n^2)$ 。