This commit is contained in:
krahets
2026-04-03 18:46:15 +08:00
parent 377736b1bd
commit 9d21ca86b0
352 changed files with 46563 additions and 11262 deletions
+390 -67
View File
@@ -4,12 +4,12 @@ comments: true
# 11.3   バブルソート
<u>バブルソート</u>は、隣接する要素を継続的に比較し交換することで動作します。このプロセスは泡が底から上に上昇するようなものなので、「バブルソート」と名付けられました
<u>バブルソートbubble sort</u>は、隣接する要素を繰り返し比較し交換することで整列を行います。この過程が泡のように下から上へ浮かび上がることから、バブルソートと呼ばれます
図に示すように、バブリングプロセスは要素交換を使用してシミュレートできます配列の左端から開始して右に移動し、隣接する要素の各ペアを比較します。左の要素が右の要素より大きい場合は、それらを交換します。横断後、最大要素は配列の右端にバブルアップします。
次の図に示すように、バブル処理は要素交換操作によってシミュレートできます配列の最も左の端から右へ走査し、隣接する要素の大小を順に比較して、「左要素 > 右要素」であれば両者を交換します。走査が終わると、最大要素は配列の最も右端へ移動します。
=== "<1>"
![Simulating bubble process using element swap](bubble_sort.assets/bubble_operation_step1.png){ class="animation-figure" }
![要素の交換操作でバブル処理をシミュレート](bubble_sort.assets/bubble_operation_step1.png){ class="animation-figure" }
=== "<2>"
![bubble_operation_step2](bubble_sort.assets/bubble_operation_step2.png){ class="animation-figure" }
@@ -29,22 +29,22 @@ comments: true
=== "<7>"
![bubble_operation_step7](bubble_sort.assets/bubble_operation_step7.png){ class="animation-figure" }
<p align="center"> 図 11-4 &nbsp; Simulating bubble process using element swap </p>
<p align="center"> 図 11-4 &nbsp; 要素の交換操作でバブル処理をシミュレート </p>
## 11.3.1 &nbsp; アルゴリズムプロセス
## 11.3.1 &nbsp; アルゴリズムの流れ
配列の長さを$n$とします。バブルソートのステップは下図に示されます:
配列の長さを $n$ とすると、バブルソートの手順は次の図のとおりです。
1. まず、$n$個の要素に対して1回の「バブル」パスを実行し、**最大要素を正しい位置交換します**。
2. 次に、残りの$n - 1$個の要素に対して「バブル」パスを実行し、**2番目に大きい要素を正しい位置交換します**。
3. この方法で続行します;$n - 1$回のパスの後、**最大$n - 1$個の要素が正しい位置に移動されます**。
4.りの唯一の要素は**必ず**最小であるため、**さらなる**ソートは必要ありません。この時点で、配列ソートされます。
1. まず、$n$ 個の要素に対して「バブル処理」を行い、**配列中の最大要素を正しい位置交換します**。
2. 次に、残りの $n - 1$ 個の要素に対して「バブル処理」を行い、**2 番目に大きい要素を正しい位置交換します**。
3. このようにして、$n - 1$ 回の「バブル処理」を終えると、**大きいほうから $n - 1$ 個の要素がすべて正しい位置へ交換されます**。
4.った 1 つの要素は必ず最小要素なので、並べ替える必要はなく、これで配列ソートが完了します。
![Bubble sort process](bubble_sort.assets/bubble_sort_overview.png){ class="animation-figure" }
![バブルソートの流れ](bubble_sort.assets/bubble_sort_overview.png){ class="animation-figure" }
<p align="center"> 図 11-5 &nbsp; Bubble sort process </p>
<p align="center"> 図 11-5 &nbsp; バブルソートの流れ </p>
コード例は以下の通りです
コード例は次のとおりです
=== "Python"
@@ -52,9 +52,9 @@ comments: true
def bubble_sort(nums: list[int]):
"""バブルソート"""
n = len(nums)
# 外側のループ:未ソート範囲は [0, i]
# 外側のループ:未ソート区間は [0, i]
for i in range(n - 1, 0, -1):
# 内側のループ:未ソート範囲 [0, i] の最大要素を範囲の右端に移動
# 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
for j in range(i):
if nums[j] > nums[j + 1]:
# nums[j] と nums[j + 1] を交換
@@ -66,13 +66,13 @@ comments: true
```cpp title="bubble_sort.cpp"
/* バブルソート */
void bubbleSort(vector<int> &nums) {
// 外側ループ:未ソート範囲は[0, i]
// 外側ループ:未ソート区間は [0, i]
for (int i = nums.size() - 1; i > 0; i--) {
// 内側ループ:未ソート範囲[0, i]の最大要素を範囲の右端交換
// 内側ループ:未ソート区間 [0, i] の最大要素をその区間の最右端交換
for (int j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// nums[j]nums[j + 1]を交換
// ここではstdswapを使用
// nums[j]nums[j + 1] を交換する
// ここでは std::swap() 関数を使用する
swap(nums[j], nums[j + 1]);
}
}
@@ -85,9 +85,9 @@ comments: true
```java title="bubble_sort.java"
/* バブルソート */
void bubbleSort(int[] nums) {
// 外側ループ: 未ソート範囲は [0, i]
// 外側ループ未ソート区間は [0, i]
for (int i = nums.length - 1; i > 0; i--) {
// 内側ループ: 未ソート範囲 [0, i] の最大要素を範囲の右端交換
// 内側ループ未ソート区間 [0, i] の最大要素をその区間の最右端交換
for (int j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// nums[j] と nums[j + 1] を交換
@@ -103,107 +103,242 @@ comments: true
=== "C#"
```csharp title="bubble_sort.cs"
[class]{bubble_sort}-[func]{BubbleSort}
/* バブルソート */
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"
[class]{}-[func]{bubbleSort}
/* バブルソート */
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"
[class]{}-[func]{bubbleSort}
/* バブルソート */
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"
[class]{}-[func]{bubbleSort}
/* バブルソート */
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"
[class]{}-[func]{bubbleSort}
/* バブルソート */
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"
[class]{}-[func]{bubbleSort}
/* バブルソート */
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"
[class]{}-[func]{bubble_sort}
/* バブルソート */
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] を交換
nums.swap(j, j + 1);
}
}
}
}
```
=== "C"
```c title="bubble_sort.c"
[class]{}-[func]{bubbleSort}
/* バブルソート */
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"
[class]{}-[func]{bubbleSort}
/* バブルソート */
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] を交換
val temp = nums[j]
nums[j] = nums[j + 1]
nums[j + 1] = temp
}
}
}
}
```
=== "Ruby"
```ruby title="bubble_sort.rb"
[class]{}-[func]{bubble_sort}
### バブルソート ###
def bubble_sort(nums)
n = nums.length
# 外側のループ:未ソート区間は [0, i]
for i in (n - 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] = nums[j + 1], nums[j]
end
end
end
end
```
??? pythontutor "コードの可視化"
<div style="height: 459px; 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%E3%83%90%E3%83%96%E3%83%AB%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E5%81%B4%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%9C%AA%E3%82%BD%E3%83%BC%E3%83%88%E5%8C%BA%E9%96%93%E3%81%AF%20%5B0%2C%20i%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%81%B4%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%9C%AA%E3%82%BD%E3%83%BC%E3%83%88%E5%8C%BA%E9%96%93%20%5B0%2C%20i%5D%20%E3%81%AE%E6%9C%80%E5%A4%A7%E8%A6%81%E7%B4%A0%E3%82%92%E3%81%9D%E3%81%AE%E5%8C%BA%E9%96%93%E3%81%AE%E6%9C%80%E5%8F%B3%E7%AB%AF%E3%81%B8%E4%BA%A4%E6%8F%9B%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%20nums%5Bj%5D%20%E3%81%A8%20nums%5Bj%20%2B%201%5D%20%E3%82%92%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D%2C%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%20%2B%201%5D%2C%20nums%5Bj%5D%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20bubble_sort%28nums%29%0A%20%20%20%20print%28%22%E3%83%90%E3%83%96%E3%83%AB%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%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%E3%83%90%E3%83%96%E3%83%AB%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E5%81%B4%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%9C%AA%E3%82%BD%E3%83%BC%E3%83%88%E5%8C%BA%E9%96%93%E3%81%AF%20%5B0%2C%20i%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%81%B4%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%9C%AA%E3%82%BD%E3%83%BC%E3%83%88%E5%8C%BA%E9%96%93%20%5B0%2C%20i%5D%20%E3%81%AE%E6%9C%80%E5%A4%A7%E8%A6%81%E7%B4%A0%E3%82%92%E3%81%9D%E3%81%AE%E5%8C%BA%E9%96%93%E3%81%AE%E6%9C%80%E5%8F%B3%E7%AB%AF%E3%81%B8%E4%BA%A4%E6%8F%9B%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%20nums%5Bj%5D%20%E3%81%A8%20nums%5Bj%20%2B%201%5D%20%E3%82%92%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D%2C%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%20%2B%201%5D%2C%20nums%5Bj%5D%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20bubble_sort%28nums%29%0A%20%20%20%20print%28%22%E3%83%90%E3%83%96%E3%83%AB%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%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`変数を追加できます;パスで交換が行われない場合は、フラグを設定して早期に戻ります。
ある回の「バブル処理」で交換操作が一度も行われなければ、配列はすでにソート済みであり、結果をそのまま返せることがわかります。したがって、この状況を検出するためのフラグ `flag` を追加し、発生した時点で直ちに返すようにできます。
この最適化があっても、バブルソートの最悪時間計算量と平均時間計算量は$O(n^2)$のままです。ただし、入力配列がすでにソートされている場合、最良ケース時間計算量は$O(n)$まで低くなる可能性があります。
最適化も、バブルソートの最悪時間計算量と平均時間計算量は依然として $O(n^2)$ です。ただし、入力配列が完全に整列済みであれば、最良時間計算量は $O(n)$ に達します。
=== "Python"
```python title="bubble_sort.py"
def bubble_sort_with_flag(nums: list[int]):
"""バブルソート(フラグによる最適化)"""
"""バブルソート(フラグ最適化)"""
n = len(nums)
# 外側のループ:未ソート範囲は [0, i]
# 外側のループ:未ソート区間は [0, i]
for i in range(n - 1, 0, -1):
flag = False # フラグを初期化
# 内側のループ:未ソート範囲 [0, i] の最大要素を範囲の右端に移動
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 # 要素を交換したことを記録
flag = True # 交換する要素を記録
if not flag:
break # この回の「バブリング」で要素交換されなかった場合、終了
break # このバブル処理で要素交換が一度もなければそのまま終了
```
=== "C++"
```cpp title="bubble_sort.cpp"
/* バブルソート(フラグ最適化*/
/* バブルソート(フラグ最適化) */
void bubbleSortWithFlag(vector<int> &nums) {
// 外側ループ:未ソート範囲は[0, i]
// 外側ループ:未ソート区間は [0, i]
for (int i = nums.size() - 1; i > 0; i--) {
bool flag = false; // フラグを初期化
// 内側ループ:未ソート範囲[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]を交換
// ここではstdswapを使用
// nums[j]nums[j + 1] を交換する
// ここでは std::swap() 関数を使用する
swap(nums[j], nums[j + 1]);
flag = true; // 交換された要素を記録
flag = true; // 交換する要素を記録
}
}
if (!flag)
break; // この回の「バブリング」で要素交換されなかった場合、終了
break; // このバブル処理で要素交換が一度もなければそのまま終了
}
}
```
@@ -211,23 +346,23 @@ comments: true
=== "Java"
```java title="bubble_sort.java"
/* バブルソート(フラグによる最適化) */
/* バブルソート(フラグ最適化) */
void bubbleSortWithFlag(int[] nums) {
// 外側ループ: 未ソート範囲は [0, i]
// 外側ループ未ソート区間は [0, i]
for (int i = nums.length - 1; i > 0; i--) {
boolean flag = false; // フラグを初期化
// 内側ループ: 未ソート範囲 [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; // 交換された要素を記録
flag = true; // 交換する要素を記録
}
}
if (!flag)
break; // このバブリング」ラウンドで要素交換されなかった場合、終了
break; // このバブル処理で要素交換が一度もなければそのまま終了
}
}
```
@@ -235,65 +370,253 @@ comments: true
=== "C#"
```csharp title="bubble_sort.cs"
[class]{bubble_sort}-[func]{BubbleSortWithFlag}
/* バブルソート(フラグ最適化) */
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"
[class]{}-[func]{bubbleSortWithFlag}
/* バブルソート(フラグ最適化) */
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"
[class]{}-[func]{bubbleSortWithFlag}
/* バブルソート(フラグ最適化) */
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"
[class]{}-[func]{bubbleSortWithFlag}
/* バブルソート(フラグ最適化) */
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"
[class]{}-[func]{bubbleSortWithFlag}
/* バブルソート(フラグ最適化) */
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"
[class]{}-[func]{bubbleSortWithFlag}
/* バブルソート(フラグ最適化) */
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"
[class]{}-[func]{bubble_sort_with_flag}
/* バブルソート(フラグ最適化) */
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] を交換
nums.swap(j, j + 1);
flag = true; // 交換する要素を記録
}
}
if !flag {
break; // このバブル処理で要素交換が一度もなければそのまま終了
};
}
}
```
=== "C"
```c title="bubble_sort.c"
[class]{}-[func]{bubbleSortWithFlag}
/* バブルソート(フラグ最適化) */
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"
[class]{}-[func]{bubbleSortWithFlag}
/* バブルソート(フラグ最適化) */
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] を交換
val temp = nums[j]
nums[j] = nums[j + 1]
nums[j + 1] = temp
flag = true // 交換する要素を記録
}
}
if (!flag) break // このバブル処理で要素交換が一度もなければそのまま終了
}
}
```
=== "Ruby"
```ruby title="bubble_sort.rb"
[class]{}-[func]{bubble_sort_with_flag}
### バブルソート ###
def bubble_sort(nums)
n = nums.length
# 外側のループ:未ソート区間は [0, i]
for i in (n - 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] = nums[j + 1], nums[j]
end
end
end
end
# ## バブルソート(フラグ最適化)###
def bubble_sort_with_flag(nums)
n = nums.length
# 外側のループ:未ソート区間は [0, i]
for i in (n - 1).downto(1)
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] = nums[j + 1], nums[j]
flag = true # 交換する要素を記録
end
end
break unless flag # このバブル処理で要素交換が一度もなければそのまま終了
end
end
```
## 11.3.3 &nbsp; アルゴリズムの特性
??? pythontutor "コードの可視化"
- **$O(n^2)$の時間計算量、適応ソート。** 各「バブリング」ラウンドは長さ$n - 1$、$n - 2$、$\dots$、$2$、$1$の配列セグメントを横断し、合計は$(n - 1) n / 2$となります。`flag`最適化により、配列がすでにソートされている場合、最良ケース時間計算量は$O(n)$に達する可能性があります。
- **$O(1)$の空間計算量、インプレースソート。** ポインタ$i$と$j$によって定数量の追加空間のみが使用されます。
- **安定ソート。** 等しい要素は「バブリング」中に交換されないため、元の順序が保持され、これは安定ソートになります。
<div style="height: 531px; 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%E3%83%90%E3%83%96%E3%83%AB%E3%82%BD%E3%83%BC%E3%83%88%EF%BC%88%E3%83%95%E3%83%A9%E3%82%B0%E6%9C%80%E9%81%A9%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%81%B4%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%9C%AA%E3%82%BD%E3%83%BC%E3%83%88%E5%8C%BA%E9%96%93%E3%81%AF%20%5B0%2C%20i%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20flag%20%3D%20False%20%20%23%20%E3%83%95%E3%83%A9%E3%82%B0%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%81%B4%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%9C%AA%E3%82%BD%E3%83%BC%E3%83%88%E5%8C%BA%E9%96%93%20%5B0%2C%20i%5D%20%E3%81%AE%E6%9C%80%E5%A4%A7%E8%A6%81%E7%B4%A0%E3%82%92%E3%81%9D%E3%81%AE%E5%8C%BA%E9%96%93%E3%81%AE%E6%9C%80%E5%8F%B3%E7%AB%AF%E3%81%B8%E4%BA%A4%E6%8F%9B%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%20nums%5Bj%5D%20%E3%81%A8%20nums%5Bj%20%2B%201%5D%20%E3%82%92%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D%2C%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%20%2B%201%5D%2C%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%E4%BA%A4%E6%8F%9B%E3%81%99%E3%82%8B%E8%A6%81%E7%B4%A0%E3%82%92%E8%A8%98%E9%8C%B2%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%E3%81%93%E3%81%AE%E3%83%90%E3%83%96%E3%83%AB%E5%87%A6%E7%90%86%E3%81%A7%E8%A6%81%E7%B4%A0%E4%BA%A4%E6%8F%9B%E3%81%8C%E4%B8%80%E5%BA%A6%E3%82%82%E3%81%AA%E3%81%91%E3%82%8C%E3%81%B0%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E7%B5%82%E4%BA%86%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20bubble_sort_with_flag%28nums%29%0A%20%20%20%20print%28%22%E3%83%90%E3%83%96%E3%83%AB%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%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%E3%83%90%E3%83%96%E3%83%AB%E3%82%BD%E3%83%BC%E3%83%88%EF%BC%88%E3%83%95%E3%83%A9%E3%82%B0%E6%9C%80%E9%81%A9%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%81%B4%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%9C%AA%E3%82%BD%E3%83%BC%E3%83%88%E5%8C%BA%E9%96%93%E3%81%AF%20%5B0%2C%20i%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20flag%20%3D%20False%20%20%23%20%E3%83%95%E3%83%A9%E3%82%B0%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%81%B4%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%9C%AA%E3%82%BD%E3%83%BC%E3%83%88%E5%8C%BA%E9%96%93%20%5B0%2C%20i%5D%20%E3%81%AE%E6%9C%80%E5%A4%A7%E8%A6%81%E7%B4%A0%E3%82%92%E3%81%9D%E3%81%AE%E5%8C%BA%E9%96%93%E3%81%AE%E6%9C%80%E5%8F%B3%E7%AB%AF%E3%81%B8%E4%BA%A4%E6%8F%9B%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%20nums%5Bj%5D%20%E3%81%A8%20nums%5Bj%20%2B%201%5D%20%E3%82%92%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D%2C%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%20%2B%201%5D%2C%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%E4%BA%A4%E6%8F%9B%E3%81%99%E3%82%8B%E8%A6%81%E7%B4%A0%E3%82%92%E8%A8%98%E9%8C%B2%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%E3%81%93%E3%81%AE%E3%83%90%E3%83%96%E3%83%AB%E5%87%A6%E7%90%86%E3%81%A7%E8%A6%81%E7%B4%A0%E4%BA%A4%E6%8F%9B%E3%81%8C%E4%B8%80%E5%BA%A6%E3%82%82%E3%81%AA%E3%81%91%E3%82%8C%E3%81%B0%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E7%B5%82%E4%BA%86%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20bubble_sort_with_flag%28nums%29%0A%20%20%20%20print%28%22%E3%83%90%E3%83%96%E3%83%AB%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%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$ は定数サイズの追加領域しか使用しません。
- **安定ソート**:「バブル処理」では等しい要素に出会っても交換しないためです。
+325 -54
View File
@@ -4,43 +4,43 @@ comments: true
# 11.8 &nbsp; バケットソート
前述のソートアルゴリズムはすべて「比較ベースのソートアルゴリズム」で、値を比較することで要素をソートします。このようなソートアルゴリズムは $O(n \log n)$ より良い時間計算量を持つことはできません。次に、線形時間計算量を達成できるいくつかの「非比較ソートアルゴリズム」について議論します。
前述のいくつかのソートアルゴリズムは、いずれも「比較ベースのソートアルゴリズム」に属し、要素間の大小を比較することで整列を実現します。この種のソートアルゴリズムの時間計算量は $O(n \log n)$ を超えられません。続いて、時間計算量が線形オーダーに達しうる「非比較ソートアルゴリズム」をいくつか見ていきます。
<u>バケットソート</u>は分割統治戦略の典型的な応用です。一連の順序付けられたバケットを設定し、各バケットがデータ範囲を含み、入力データをこれらのバケット均等に分散させることで動作します。そして、各バケット内のデータを個別にソートします。最後に、すべてのバケットからのソート済みデータを順次マージして最終結果を生成します。
<u>バケットソートbucket sort</u>は分割統治戦略の典型的な応用です。大小関係をもつ複数のバケットを用意し、各バケットがあるデータ範囲に対応するようにして、データを各バケット均等に分します。その後、各バケット内でそれぞれソートを行い、最後にバケットの順序に従ってすべてのデータを結合します。
## 11.8.1 &nbsp; アルゴリズムの過程
## 11.8.1 &nbsp; アルゴリズムの流れ
長さ $n$ の配列で、$[0, 1)$ の範囲の浮動小数点数を考えてみます。バケットソートの過程は以下の図に示されています。
長さ $n$ の配列を考え、その要素は範囲 $[0, 1)$ の浮動小数点数であるとします。バケットソートの流れを以下の図に示ます。
1. $k$ 個のバケットを初期化し、$n$ 個の要素をこれらの $k$ 個のバケットに分散させます。
2. 各バケットを個別にソートします(プログラミング言語の組み込みソート関数を使用)。
3. 最小から最大のバケットの順序で結果をマージします。
1. $k$ 個のバケットを初期化し、$n$ 個の要素を $k$ 個のバケットに分配します。
2. 各バケットに対してそれぞれソートを実行します(ここではプログラミング言語の組み込みソート関数を用います)。
3. バケットを小さい順にたどって結果を結合します。
![バケットソートアルゴリズムの過程](bucket_sort.assets/bucket_sort_overview.png){ class="animation-figure" }
![バケットソートの流れ](bucket_sort.assets/bucket_sort_overview.png){ class="animation-figure" }
<p align="center"> 図 11-13 &nbsp; バケットソートアルゴリズムの過程 </p>
<p align="center"> 図 11-13 &nbsp; バケットソートの流れ </p>
コードは以下のりです:
コードは以下のとおりです:
=== "Python"
```python title="bucket_sort.py"
def bucket_sort(nums: list[float]):
"""バケットソート"""
# k = n/2 個のバケットを初期化、各バケットに平均2個の要素を配置することを期待
# k = n/2 個のバケットを初期化、各バケットに 2 要素ずつ割り当てる想定とする
k = len(nums) // 2
buckets = [[] for _ in range(k)]
# 1. 配列要素を各バケットに分散
# 1. 配列要素を各バケットに振り分ける
for num in nums:
# 入力データ範囲は [0, 1)、num * k を使用してインデックス範囲 [0, k-1] にマッピング
# 入力データ範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する
i = int(num * k)
# num をバケット i に追加
buckets[i].append(num)
# 2. 各バケットをソート
# 2. 各バケットをソートする
for bucket in buckets:
# 組み込みソート関数を使用、他のソートアルゴリズムに置き換えることも可能
# 組み込みソート関数を使う。他のソートアルゴリズムに置き換えてもよい
bucket.sort()
# 3. バケットを走査して結果をマージ
# 3. バケットを走査して結果を結合
i = 0
for bucket in buckets:
for num in bucket:
@@ -53,22 +53,22 @@ comments: true
```cpp title="bucket_sort.cpp"
/* バケットソート */
void bucketSort(vector<float> &nums) {
// k = n/2個のバケットを初期化、各バケットに2つの要素を割り当てることを期待
// k = n/2 個のバケットを初期化、各バケットに 2 要素ずつ割り当てる想定とする
int k = nums.size() / 2;
vector<vector<float>> buckets(k);
// 1. 配列要素を各バケットに分配
// 1. 配列要素を各バケットに振り分ける
for (float num : nums) {
// 入力データ範囲は[0, 1)、num * kを使用してインデックス範囲[0, k-1]にマップ
// 入力データ範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する
int i = num * k;
// bucket_idxバケットに数値を追加
// num をバケット bucket_idx追加
buckets[i].push_back(num);
}
// 2. 各バケットをソート
// 2. 各バケットをソートする
for (vector<float> &bucket : buckets) {
// 組み込みソート関数を使用、他のソートアルゴリズムに置き換えることも可能
// 組み込みソート関数を使う。他のソートアルゴリズムに置き換えてもよい
sort(bucket.begin(), bucket.end());
}
// 3. バケットを走査して結果をマージ
// 3. バケットを走査して結果を結合
int i = 0;
for (vector<float> &bucket : buckets) {
for (float num : bucket) {
@@ -83,25 +83,25 @@ comments: true
```java title="bucket_sort.java"
/* バケットソート */
void bucketSort(float[] nums) {
// k = n/2 個のバケットを初期化、各バケットに期待される要素数は 2 個
// 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. 配列要素を各バケットに分散
// 1. 配列要素を各バケットに振り分ける
for (float num : nums) {
// 入力データ範囲は [0, 1)、num * k を使ってインデックス範囲 [0, k-1] にマッピング
// 入力データ範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する
int i = (int) (num * k);
// num をバケット i に追加
buckets.get(i).add(num);
}
// 2. 各バケットをソート
// 2. 各バケットをソートする
for (List<Float> bucket : buckets) {
// 組み込みソート関数を使用、他のソートアルゴリズムに置き換えることも可能
// 組み込みソート関数を使う。他のソートアルゴリズムに置き換えてもよい
Collections.sort(bucket);
}
// 3. バケットを走査して結果をマージ
// 3. バケットを走査して結果を結合
int i = 0;
for (List<Float> bucket : buckets) {
for (float num : bucket) {
@@ -114,87 +114,358 @@ comments: true
=== "C#"
```csharp title="bucket_sort.cs"
[class]{bucket_sort}-[func]{BucketSort}
/* バケットソート */
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"
[class]{}-[func]{bucketSort}
/* バケットソート */
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"
[class]{}-[func]{bucketSort}
/* バケットソート */
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"
[class]{}-[func]{bucketSort}
/* バケットソート */
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"
[class]{}-[func]{bucketSort}
/* バケットソート */
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"
[class]{}-[func]{bucketSort}
/* バケットソート */
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"
[class]{}-[func]{bucket_sort}
/* バケットソート */
fn bucket_sort(nums: &mut [f64]) {
// k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする
let k = nums.len() / 2;
let mut buckets = vec![vec![]; k];
// 1. 配列要素を各バケットに振り分ける
for &num in nums.iter() {
// 入力データの範囲は [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 buckets.iter() {
for &num in bucket.iter() {
nums[i] = num;
i += 1;
}
}
}
```
=== "C"
```c title="bucket_sort.c"
[class]{}-[func]{bucketSort}
/* バケットソート */
void bucketSort(float nums[], int n) {
int k = n / 2; // k = n/2 個のバケットを初期化する
int *sizes = malloc(k * sizeof(int)); // 各バケットのサイズを記録する
float **buckets = malloc(k * sizeof(float *)); // 動的配列の配列(バケット)
// 各バケットに十分な容量を事前確保する
for (int i = 0; i < k; ++i) {
buckets[i] = (float *)malloc(n * sizeof(float));
sizes[i] = 0;
}
// 1. 配列要素を各バケットに振り分ける
for (int i = 0; i < n; ++i) {
int idx = (int)(nums[i] * k);
buckets[idx][sizes[idx]++] = nums[i];
}
// 2. 各バケットをソートする
for (int i = 0; i < k; ++i) {
qsort(buckets[i], sizes[i], sizeof(float), compare);
}
// 3. ソート済みのバケットを結合する
int idx = 0;
for (int i = 0; i < k; ++i) {
for (int j = 0; j < sizes[i]; ++j) {
nums[idx++] = buckets[i][j];
}
// メモリを解放する
free(buckets[i]);
}
}
```
=== "Kotlin"
```kotlin title="bucket_sort.kt"
[class]{}-[func]{bucketSort}
/* バケットソート */
fun bucketSort(nums: FloatArray) {
// k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする
val k = nums.size / 2
val buckets = mutableListOf<MutableList<Float>>()
for (i in 0..<k) {
buckets.add(mutableListOf())
}
// 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}
### バケットソート ###
def bucket_sort(nums)
# k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする
k = nums.length / 2
buckets = Array.new(k) { [] }
# 1. 配列要素を各バケットに振り分ける
nums.each do |num|
# 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する
i = (num * k).to_i
# num をバケット i に追加
buckets[i] << num
end
# 2. 各バケットをソートする
buckets.each do |bucket|
# 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい
bucket.sort!
end
# 3. バケットを走査して結果を結合
i = 0
buckets.each do |bucket|
bucket.each do |num|
nums[i] = num
i += 1
end
end
end
```
## 11.8.2 &nbsp; アルゴリズムの特徴
??? pythontutor "コードの可視化"
バケットソートは非常に大きなデータセットの処理に適しています。例えば、入力データに100万個の要素が含まれ、システムメモリの制限によりすべてのデータを同時にロードできない場合、データを1,000個のバケットに分割し、各バケットを個別にソートしてから結果をマージできます。
<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%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20%23%20k%20%3D%20n%2F2%20%E5%80%8B%E3%81%AE%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%97%E3%80%81%E5%90%84%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%81%AB%202%20%E8%A6%81%E7%B4%A0%E3%81%9A%E3%81%A4%E5%89%B2%E3%82%8A%E5%BD%93%E3%81%A6%E3%82%8B%E6%83%B3%E5%AE%9A%E3%81%A8%E3%81%99%E3%82%8B%0A%20%20%20%20k%20%3D%20len%28nums%29%20%2F%2F%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%E9%85%8D%E5%88%97%E8%A6%81%E7%B4%A0%E3%82%92%E5%90%84%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%81%AB%E6%8C%AF%E3%82%8A%E5%88%86%E3%81%91%E3%82%8B%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E7%AF%84%E5%9B%B2%E3%81%AF%20%5B0%2C%201%29%20%E3%81%A7%E3%81%82%E3%82%8A%E3%80%81num%20%2A%20k%20%E3%82%92%E7%94%A8%E3%81%84%E3%81%A6%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E7%AF%84%E5%9B%B2%20%5B0%2C%20k-1%5D%20%E3%81%AB%E5%86%99%E5%83%8F%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20i%20%3D%20int%28num%20%2A%20k%29%0A%20%20%20%20%20%20%20%20%23%20num%20%E3%82%92%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%20i%20%E3%81%AB%E8%BF%BD%E5%8A%A0%0A%20%20%20%20%20%20%20%20buckets%5Bi%5D.append%28num%29%0A%20%20%20%20%23%202.%20%E5%90%84%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%82%92%E3%82%BD%E3%83%BC%E3%83%88%E3%81%99%E3%82%8B%0A%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF%E3%81%AE%E3%82%BD%E3%83%BC%E3%83%88%E9%96%A2%E6%95%B0%E3%82%92%E4%BD%BF%E3%81%86%E3%80%82%E4%BB%96%E3%81%AE%E3%82%BD%E3%83%BC%E3%83%88%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0%E3%81%AB%E7%BD%AE%E3%81%8D%E6%8F%9B%E3%81%88%E3%81%A6%E3%82%82%E3%82%88%E3%81%84%0A%20%20%20%20%20%20%20%20bucket.sort%28%29%0A%20%20%20%20%23%203.%20%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%82%92%E8%B5%B0%E6%9F%BB%E3%81%97%E3%81%A6%E7%B5%90%E6%9E%9C%E3%82%92%E7%B5%90%E5%90%88%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%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AF%E7%AF%84%E5%9B%B2%20%5B0%2C%201%29%20%E3%81%AE%E6%B5%AE%E5%8B%95%E5%B0%8F%E6%95%B0%E7%82%B9%E6%95%B0%E3%81%A8%E3%81%99%E3%82%8B%0A%20%20%20%20nums%20%3D%20%5B0.49%2C%200.96%2C%200.82%2C%200.09%2C%200.57%2C%200.43%2C%200.91%2C%200.75%2C%200.15%2C%200.37%5D%0A%20%20%20%20bucket_sort%28nums%29%0A%20%20%20%20print%28%22%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%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%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20%23%20k%20%3D%20n%2F2%20%E5%80%8B%E3%81%AE%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%97%E3%80%81%E5%90%84%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%81%AB%202%20%E8%A6%81%E7%B4%A0%E3%81%9A%E3%81%A4%E5%89%B2%E3%82%8A%E5%BD%93%E3%81%A6%E3%82%8B%E6%83%B3%E5%AE%9A%E3%81%A8%E3%81%99%E3%82%8B%0A%20%20%20%20k%20%3D%20len%28nums%29%20%2F%2F%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%E9%85%8D%E5%88%97%E8%A6%81%E7%B4%A0%E3%82%92%E5%90%84%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%81%AB%E6%8C%AF%E3%82%8A%E5%88%86%E3%81%91%E3%82%8B%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E7%AF%84%E5%9B%B2%E3%81%AF%20%5B0%2C%201%29%20%E3%81%A7%E3%81%82%E3%82%8A%E3%80%81num%20%2A%20k%20%E3%82%92%E7%94%A8%E3%81%84%E3%81%A6%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E7%AF%84%E5%9B%B2%20%5B0%2C%20k-1%5D%20%E3%81%AB%E5%86%99%E5%83%8F%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20i%20%3D%20int%28num%20%2A%20k%29%0A%20%20%20%20%20%20%20%20%23%20num%20%E3%82%92%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%20i%20%E3%81%AB%E8%BF%BD%E5%8A%A0%0A%20%20%20%20%20%20%20%20buckets%5Bi%5D.append%28num%29%0A%20%20%20%20%23%202.%20%E5%90%84%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%82%92%E3%82%BD%E3%83%BC%E3%83%88%E3%81%99%E3%82%8B%0A%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF%E3%81%AE%E3%82%BD%E3%83%BC%E3%83%88%E9%96%A2%E6%95%B0%E3%82%92%E4%BD%BF%E3%81%86%E3%80%82%E4%BB%96%E3%81%AE%E3%82%BD%E3%83%BC%E3%83%88%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0%E3%81%AB%E7%BD%AE%E3%81%8D%E6%8F%9B%E3%81%88%E3%81%A6%E3%82%82%E3%82%88%E3%81%84%0A%20%20%20%20%20%20%20%20bucket.sort%28%29%0A%20%20%20%20%23%203.%20%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%82%92%E8%B5%B0%E6%9F%BB%E3%81%97%E3%81%A6%E7%B5%90%E6%9E%9C%E3%82%92%E7%B5%90%E5%90%88%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%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AF%E7%AF%84%E5%9B%B2%20%5B0%2C%201%29%20%E3%81%AE%E6%B5%AE%E5%8B%95%E5%B0%8F%E6%95%B0%E7%82%B9%E6%95%B0%E3%81%A8%E3%81%99%E3%82%8B%0A%20%20%20%20nums%20%3D%20%5B0.49%2C%200.96%2C%200.82%2C%200.09%2C%200.57%2C%200.43%2C%200.91%2C%200.75%2C%200.15%2C%200.37%5D%0A%20%20%20%20bucket_sort%28nums%29%0A%20%20%20%20print%28%22%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%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>
- **時間計算量は $O(n + k)$**:要素がバケット間で均等に分散されていると仮定すると、各バケット内の要素数は $n/k$ です。単一のバケットのソートに $O(n/k \log(n/k))$ 時間がかかると仮定すると、すべてのバケットのソートに $O(n \log(n/k))$ 時間がかかります。**バケット数 $k$ が比較的大きいとき、時間計算量は $O(n)$ に近づきます**。結果のマージには、すべてのバケットと要素を走査する必要があり、$O(n + k)$ 時間がかかります。最悪の場合、すべてのデータが単一のバケットに分散され、そのバケットのソートには $O(n^2)$ 時間がかかります。
- **空間計算量は $O(n + k)$、非インプレースソート**:$k$ 個のバケットと合計 $n$ 個の要素のための追加スペースが必要です。
- バケットソートが安定かどうかは、各バケット内で使用されるソートアルゴリズムが安定かどうかに依存します。
## 11.8.2 &nbsp; アルゴリズムの特性
## 11.8.3 &nbsp; 均等分散を達成する方法
バケットソートは、非常に大規模なデータの処理に適しています。たとえば、入力データに 100 万個の要素が含まれ、空間の制約によりシステムメモリへすべてのデータを一度に読み込めない場合です。このとき、データを 1000 個のバケットに分け、それぞれのバケットを個別にソートしてから、最後に結果を結合できます。
バケットソートの理論的時間計算量は $O(n)$ に達することができます。**重要なことは、すべてのバケットに要素を均等に分散させることです**。実世界のデータはしばしば均一に分散されていないからです。例えば、eBayのすべての商品を価格範囲で10個のバケットに均等に分散させたいとします。しかし、商品価格の分散は均等でない可能性があり、100ドル未満の商品が多く、500ドル以上の商品が少ないかもしれません。価格範囲を均等に10分割すると、各バケットの商品数の差が大きくなります。
- **時間計算量は $O(n + k)$** :要素が各バケット内に平均的に分布していると仮定すると、各バケット内の要素数は $\frac{n}{k}$ です。1 つのバケットをソートするのに $O(\frac{n}{k} \log\frac{n}{k})$ の時間がかかるなら、すべてのバケットのソートには $O(n \log\frac{n}{k})$ の時間がかかります。**バケット数 $k$ が十分大きいとき、時間計算量は $O(n)$ に近づきます**。結果を結合する際には、すべてのバケットと要素を走査する必要があり、$O(n + k)$ の時間を要します。最悪の場合、すべてのデータが 1 つのバケットに割り当てられ、そのバケットのソートに $O(n^2)$ の時間がかかります。
- **空間計算量は $O(n + k)$、非インプレースソート**:$k$ 個のバケットと合計 $n$ 個の要素ぶんの追加領域が必要です。
- バケットソートが安定かどうかは、バケット内要素のソートに用いるアルゴリズムが安定かどうかに依存します。
均等分散を達成するために、最初におおよその境界を設定して、データを3つのバケットに大まかに分割できます。**分散が完了した後、より多くのアイテムを持つバケットをさらに3つのバケットに分割し、すべてのバケットの要素数がほぼ等しくなるまで続けます**。
## 11.8.3 &nbsp; 均等な分配を実現するには
以下の図に示すように、この方法は本質的に再帰木を構築し、葉ノードの要素数ができるだけ均等になることを目指します。もちろん、各ラウンドでデータを3つのバケットに分割する必要はありません - 分割戦略はデータの独特な特性に適応的に調整できます。
バケットソートの時間計算量は理論上 $O(n)$ に達しますが、**鍵は要素を各バケットへ均等に分配すること** にあります。実際のデータは均一に分布していないことが多いからです。たとえば、Taobao 上のすべての商品を価格帯ごとに 10 個のバケットへ均等に分けたいとしても、商品の価格分布は偏っており、100 元未満は非常に多く、1000 元超は非常に少ないかもしれません。価格区間を単純に 10 等分すると、各バケットの商品数には大きな差が生じます。
![バケットの再帰的分割](bucket_sort.assets/scatter_in_buckets_recursively.png){ class="animation-figure" }
均等な分配を実現するために、まず大まかな境界線を設定し、データをひとまず 3 個のバケットに粗く振り分けます。**分配後は、商品数の多いバケットをさらに 3 個のバケットに分割し、すべてのバケット内の要素数がおおむね等しくなるまでこれを続けます**。
<p align="center"> 図 11-14 &nbsp; バケットの再帰的分割 </p>
以下の図に示すように、この方法の本質は再帰木を構築することにあり、目標は葉ノードの値をできるだけ均等にすることです。もちろん、毎回データを 3 個のバケットに分割する必要はなく、具体的な分け方はデータの特徴に応じて柔軟に選べます。
商品価格の確率分布を事前に知っている場合、**データの確率分布に基づいて各バケットの価格境界を設定できます**。データ分布を具体的に計算する必要は必ずしもなく、代わりに確率モデルを使用してデータ特性に基づいて近似できることに注意してください。
![再帰的にバケットを分割](bucket_sort.assets/scatter_in_buckets_recursively.png){ class="animation-figure" }
以下の図に示すように、商品価格が正規分布に従うと仮定すると、バケット間でアイテムの分散のバランスを取るために合理的な価格区間を定義できます。
<p align="center"> 図 11-14 &nbsp; 再帰的にバケットを分割 </p>
![確率分布に基づバケット分割](bucket_sort.assets/scatter_in_buckets_distribution.png){ class="animation-figure" }
商品価格の確率分布をあらかじめ把握しているなら、**データの確率分布に基づいて各バケットの価格境界を設定できます**。なお、データ分布は必ずしも特別に統計を取る必要はなく、データの特徴に応じて何らかの確率モデルで近似することもできます。
<p align="center"> 図 11-15 &nbsp; 確率分布に基づくバケット分割 </p>
以下の図に示すように、商品価格が正規分布に従うと仮定すれば、価格区間を合理的に設定でき、それによって商品を各バケットへ均等に分配できます。
![確率分布に基づいてバケットを分割](bucket_sort.assets/scatter_in_buckets_distribution.png){ class="animation-figure" }
<p align="center"> 図 11-15 &nbsp; 確率分布に基づいてバケットを分割 </p>
+605 -93
View File
@@ -4,38 +4,36 @@ comments: true
# 11.9 &nbsp; 計数ソート
<u>計数ソート</u>は要素数をカウントすることでソートを実現し、通常は整数配列に適用されます。
<u>計数ソートcounting sort</u>は要素数を集計することでソートを実現し、通常は整数配列に適用されます。
## 11.9.1 &nbsp; 単な実装
## 11.9.1 &nbsp; 単な実装
簡単な例から始めましょう。長さ $n$ の配列 `nums` が与えられ、すべての要素が「非負整数」である場合、計数ソートの全体的な過程は以下の図に示されています。
まず簡単な例を見てみましょう。長さ $n$ の配列 `nums` が与えられ、その要素はすべて「非負整数」であるとします。計数ソートの全体的な流れを以下の図に示ます。
1. 配列を走査し最大を見つけ、それを $m$ とし、長さ $m + 1$ の補助配列 `counter` を作成します。
2. **`counter`使用し`nums` 内の各数の出現回数をカウントします**。ここで `counter[num]` は数 `num` の出現回数に対応します。カウント方法は簡単で、`nums` を走査し(現在の数を `num` とする)、各ラウンド`counter[num]` を $1$ 増やします。
3. **`counter` のインデックスは自然に順序けられているため、すべての数は本質的にすでにソートされています**。次に、`counter` を走査し、出現順に `nums` を昇順で埋めます。
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" }
![計数ソートの流れ](counting_sort.assets/counting_sort_overview.png){ class="animation-figure" }
<p align="center"> 図 11-16 &nbsp; 計数ソートの過程 </p>
<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. 各数字の出現回数を統計
# 簡易版。オブジェクトのソートには使ない
# 1. 配列の最大要素 m を求める
m = max(nums)
# 2. 各数値の出現回数を数える
# counter[num] は num の出現回数を表す
counter = [0] * (m + 1)
for num in nums:
counter[num] += 1
# 3. counter を走査し、各要素を元の配列 nums に埋め戻し
# 3. counter を走査し、各要素を元の配列 nums に書き戻す
i = 0
for num in range(m + 1):
for _ in range(counter[num]):
@@ -46,21 +44,21 @@ comments: true
=== "C++"
```cpp title="counting_sort.cpp"
/* カウントソート */
// 簡単な実装、オブジェクトのソートには使用できない
/* 計数ソート */
// 簡易実装のため、オブジェクトのソートには使ない
void countingSortNaive(vector<int> &nums) {
// 1. 配列の最大要素mを統計
// 1. 配列の最大要素 m を求める
int m = 0;
for (int num : nums) {
m = max(m, num);
}
// 2. 各数の出現回数を統計
// counter[num]numの出現回数を表す
// 2. 各数の出現回数を数える
// counter[num]num の出現回数を表す
vector<int> counter(m + 1, 0);
for (int num : nums) {
counter[num]++;
}
// 3. counterを走査し、各要素を元の配列nums戻す
// 3. counter を走査し、各要素を元の配列 nums に書き戻す
int i = 0;
for (int num = 0; num < m + 1; num++) {
for (int j = 0; j < counter[num]; j++, i++) {
@@ -74,20 +72,20 @@ comments: true
```java title="counting_sort.java"
/* 計数ソート */
// 簡単な実装、オブジェクトのソートには使用できない
// 簡易実装のため、オブジェクトのソートには使ない
void countingSortNaive(int[] nums) {
// 1. 配列の最大要素 m を統計
// 1. 配列の最大要素 m を求める
int m = 0;
for (int num : nums) {
m = Math.max(m, num);
}
// 2. 各数の出現回数を統計
// 2. 各数の出現回数を数える
// counter[num] は num の出現回数を表す
int[] counter = new int[m + 1];
for (int num : nums) {
counter[num]++;
}
// 3. counter を走査し、各要素を元の配列 nums に戻す
// 3. counter を走査し、各要素を元の配列 nums に書き戻す
int i = 0;
for (int num = 0; num < m + 1; num++) {
for (int j = 0; j < counter[num]; j++, i++) {
@@ -100,86 +98,297 @@ comments: true
=== "C#"
```csharp title="counting_sort.cs"
[class]{counting_sort}-[func]{CountingSortNaive}
/* 計数ソート */
// 簡易実装のため、オブジェクトのソートには使えない
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"
[class]{}-[func]{countingSortNaive}
/* 計数ソート */
// 簡易実装のため、オブジェクトのソートには使えない
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"
[class]{}-[func]{countingSortNaive}
/* 計数ソート */
// 簡易実装のため、オブジェクトのソートには使えない
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"
[class]{}-[func]{countingSortNaive}
/* 計数ソート */
// 簡易実装のため、オブジェクトのソートには使えない
function countingSortNaive(nums) {
// 1. 配列の最大要素 m を求める
let m = Math.max(...nums);
// 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"
[class]{}-[func]{countingSortNaive}
/* 計数ソート */
// 簡易実装のため、オブジェクトのソートには使えない
function countingSortNaive(nums: number[]): void {
// 1. 配列の最大要素 m を求める
let m: number = Math.max(...nums);
// 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"
[class]{}-[func]{countingSortNaive}
/* 計数ソート */
// 簡易実装のため、オブジェクトのソートには使えない
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"
[class]{}-[func]{counting_sort_naive}
/* 計数ソート */
// 簡易実装のため、オブジェクトのソートには使えない
fn counting_sort_naive(nums: &mut [i32]) {
// 1. 配列の最大要素 m を求める
let m = *nums.iter().max().unwrap();
// 2. 各数値の出現回数を数える
// counter[num] は num の出現回数を表す
let mut counter = vec![0; m as usize + 1];
for &num in nums.iter() {
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"
[class]{}-[func]{countingSortNaive}
/* 計数ソート */
// 簡易実装のため、オブジェクトのソートには使えない
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"
[class]{}-[func]{countingSortNaive}
/* 計数ソート */
// 簡易実装のため、オブジェクトのソートには使えない
fun countingSortNaive(nums: IntArray) {
// 1. 配列の最大要素 m を求める
var m = 0
for (num in nums) {
m = max(m, num)
}
// 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}
### 計数ソート ###
def counting_sort_naive(nums)
# 簡易版。オブジェクトのソートには使えない
# 1. 配列の最大要素 m を求める
m = 0
nums.each { |num| m = [m, num].max }
# 2. 各数値の出現回数を数える
# counter[num] は num の出現回数を表す
counter = Array.new(m + 1, 0)
nums.each { |num| counter[num] += 1 }
# 3. counter を走査し、各要素を元の配列 nums に書き戻す
i = 0
for num in 0...(m + 1)
(0...counter[num]).each do
nums[i] = num
i += 1
end
end
end
```
??? 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%A8%88%E6%95%B0%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20%23%20%E7%B0%A1%E6%98%93%E7%89%88%E3%80%82%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E3%82%BD%E3%83%BC%E3%83%88%E3%81%AB%E3%81%AF%E4%BD%BF%E3%81%88%E3%81%AA%E3%81%84%0A%20%20%20%20%23%201.%20%E9%85%8D%E5%88%97%E3%81%AE%E6%9C%80%E5%A4%A7%E8%A6%81%E7%B4%A0%20m%20%E3%82%92%E6%B1%82%E3%82%81%E3%82%8B%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%2C%20num%29%0A%20%20%20%20%23%202.%20%E5%90%84%E6%95%B0%E5%80%A4%E3%81%AE%E5%87%BA%E7%8F%BE%E5%9B%9E%E6%95%B0%E3%82%92%E6%95%B0%E3%81%88%E3%82%8B%0A%20%20%20%20%23%20counter%5Bnum%5D%20%E3%81%AF%20num%20%E3%81%AE%E5%87%BA%E7%8F%BE%E5%9B%9E%E6%95%B0%E3%82%92%E8%A1%A8%E3%81%99%0A%20%20%20%20counter%20%3D%20%5B0%5D%20%2A%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.%20counter%20%E3%82%92%E8%B5%B0%E6%9F%BB%E3%81%97%E3%80%81%E5%90%84%E8%A6%81%E7%B4%A0%E3%82%92%E5%85%83%E3%81%AE%E9%85%8D%E5%88%97%20nums%20%E3%81%AB%E6%9B%B8%E3%81%8D%E6%88%BB%E3%81%99%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%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%200%2C%201%2C%202%2C%200%2C%204%2C%200%2C%202%2C%202%2C%204%5D%0A%20%20%20%20counting_sort_naive%28nums%29%0A%20%20%20%20print%28f%22%E8%A8%88%E6%95%B0%E3%82%BD%E3%83%BC%E3%83%88%EF%BC%88%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AF%E3%82%BD%E3%83%BC%E3%83%88%E4%B8%8D%E5%8F%AF%EF%BC%89%E5%AE%8C%E4%BA%86%E5%BE%8C%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%A8%88%E6%95%B0%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20%23%20%E7%B0%A1%E6%98%93%E7%89%88%E3%80%82%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E3%82%BD%E3%83%BC%E3%83%88%E3%81%AB%E3%81%AF%E4%BD%BF%E3%81%88%E3%81%AA%E3%81%84%0A%20%20%20%20%23%201.%20%E9%85%8D%E5%88%97%E3%81%AE%E6%9C%80%E5%A4%A7%E8%A6%81%E7%B4%A0%20m%20%E3%82%92%E6%B1%82%E3%82%81%E3%82%8B%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%2C%20num%29%0A%20%20%20%20%23%202.%20%E5%90%84%E6%95%B0%E5%80%A4%E3%81%AE%E5%87%BA%E7%8F%BE%E5%9B%9E%E6%95%B0%E3%82%92%E6%95%B0%E3%81%88%E3%82%8B%0A%20%20%20%20%23%20counter%5Bnum%5D%20%E3%81%AF%20num%20%E3%81%AE%E5%87%BA%E7%8F%BE%E5%9B%9E%E6%95%B0%E3%82%92%E8%A1%A8%E3%81%99%0A%20%20%20%20counter%20%3D%20%5B0%5D%20%2A%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.%20counter%20%E3%82%92%E8%B5%B0%E6%9F%BB%E3%81%97%E3%80%81%E5%90%84%E8%A6%81%E7%B4%A0%E3%82%92%E5%85%83%E3%81%AE%E9%85%8D%E5%88%97%20nums%20%E3%81%AB%E6%9B%B8%E3%81%8D%E6%88%BB%E3%81%99%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%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%200%2C%201%2C%202%2C%200%2C%204%2C%200%2C%202%2C%202%2C%204%5D%0A%20%20%20%20counting_sort_naive%28nums%29%0A%20%20%20%20print%28f%22%E8%A8%88%E6%95%B0%E3%82%BD%E3%83%BC%E3%83%88%EF%BC%88%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AF%E3%82%BD%E3%83%BC%E3%83%88%E4%B8%8D%E5%8F%AF%EF%BC%89%E5%AE%8C%E4%BA%86%E5%BE%8C%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` の各インデックスをバケットと考え、カウントの過程を要素を対応するバケットに分散させることと考えることができます。本質的に、計数ソートは整数データのためのバケットソートの特別なケースです。
バケットソートの観点から見ると、計数ソートにおける計数配列 `counter` の各インデックスを 1 つのバケットとみなし、個数を数える過程を要素を対応するバケットへ振り分ける操作とみなせます。本質的に、計数ソートは整数データにおけるバケットソートの特殊な一例です。
## 11.9.2 &nbsp; 完全な実装
注意深い読者は気付くかもしれませんが、**入力データがオブジェクト場合、上記の手順 `3.` は無効です**。入力データが商品オブジェクトで価格(クラスメンバ変数)商品をソートしたいとします。しかし上記のアルゴリズムは結果としてソート済みの価格のみを提供できます。
注意深い読者なら、**入力データがオブジェクトである場合、上記の手順 `3.` は機能しない**ことに気づくかもしれません。入力データが商品オブジェクトであり、商品価格(クラスメンバ変数)に基づいて商品をソートしたいとします。しかし上記のアルゴリズムが返せるのは価格のソート結果だけです。
では、元のデータのソート結果をどのように取得できるでしょうかまず`counter` の「前置和」を計算します。名前が示すように、インデックス `i` での前置和 `prefix[i]` は、配列の最初の `i` の要素の和に等しいです:
では、元のデータのソート結果を得るにはどうすればよいのでしょうかまず `counter` の「累積和」を計算します。名前のとおり、インデックス `i` における累積和 `prefix[i]` は、配列の先頭から `i` 番目までの要素の和に等しくなります:
$$
\text{prefix}[i] = \sum_{j=0}^i \text{counter[j]}
$$
**前置和には明確な意味があります。`prefix[num] - 1` は結果配列 `res` における要素 `num` の最後の出現のインデックスを表します**。この情報は重要で、各要素が結果配列のどに現れるべきかを教えてくれます。次に、元の配列 `nums` 各要素 `num` を逆順で走査し、各反復で以下の2つの手順を実行します。
**累積和には明確な意味があり`prefix[num] - 1` は要素 `num` が結果配列 `res` に最後に現れるインデックスを表します**。この情報は非常に重要で、各要素が結果配列のどの位置に現れるべきかを示してくれます。続いて元の配列 `nums` を逆順に走査し、各要素 `num` に対して各反復で次の 2 つの手順を行います。
1. インデックス `prefix[num] - 1` で配列 `res` に `num` を埋めます。
2. 前置和 `prefix[num]` を $1$ 減らし、`num` を配置する次のインデックスを取得します。
1. `num` を配列 `res` のインデックス `prefix[num] - 1` に格納します。
2. 累積和 `prefix[num]` を $1$ 減らし、次に `num` を配置するインデックスをます。
走査、配列 `res` にソートされた結果が含まれ、最後に `res` 元の配列 `nums` を置き換えます。完全な計数ソートの過程は以下の図に示されています。
走査が完了すると、配列 `res` にソート済みの結果が格納されます。最後に `res` 元の配列 `nums` を上書きすれば完了です。以下の図は完全な計数ソートの流れを示しています。
=== "<1>"
![計数ソートの過程](counting_sort.assets/counting_sort_step1.png){ class="animation-figure" }
![計数ソートの手順](counting_sort.assets/counting_sort_step1.png){ class="animation-figure" }
=== "<2>"
![counting_sort_step2](counting_sort.assets/counting_sort_step2.png){ class="animation-figure" }
@@ -202,36 +411,36 @@ $$
=== "<8>"
![counting_sort_step8](counting_sort.assets/counting_sort_step8.png){ class="animation-figure" }
<p align="center"> 図 11-17 &nbsp; 計数ソートの過程 </p>
<p align="center"> 図 11-17 &nbsp; 計数ソートの手順 </p>
計数ソートの実装コードは以下のりです:
計数ソートの実装コードは以下のとおりです:
=== "Python"
```python title="counting_sort.py"
def counting_sort(nums: list[int]):
"""計数ソート"""
# 完全な実装、オブジェクトソートが可能で、安定ソート
# 1. 配列の最大要素 m を統計
# 完全版。オブジェクトソートでき、かつ安定ソートである
# 1. 配列の最大要素 m を求める
m = max(nums)
# 2. 各数の出現回数を統計
# 2. 各数の出現回数を数える
# counter[num] は num の出現回数を表す
counter = [0] * (m + 1)
for num in nums:
counter[num] += 1
# 3. counter の前置和を計算し、「出現回数」を「末尾インデックス」に変換
# counter[num]-1 は res において num が最後に出現するインデックス
# 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する
# つまり counter[num]-1 は、num が res に最後に現れるインデックス
for i in range(m):
counter[i + 1] += counter[i]
# 4. nums を逆順に走査し、各要素を結果配列 res に配置
# 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 を上書き
counter[num] -= 1 # 累積和を 1 減らして、次に num を配置するインデックスを得
# 結果配列 res 元の配列 nums を上書きする
for i in range(n):
nums[i] = res[i]
```
@@ -239,35 +448,35 @@ $$
=== "C++"
```cpp title="counting_sort.cpp"
/* カウントソート */
// 完全な実装、オブジェクトソートが可能で安定ソート
/* 計数ソート */
// 完全な実装、オブジェクトソートでき、かつ安定ソートである
void countingSort(vector<int> &nums) {
// 1. 配列の最大要素mを統計
// 1. 配列の最大要素 m を求める
int m = 0;
for (int num : nums) {
m = max(m, num);
}
// 2. 各数の出現回数を統計
// counter[num]numの出現回数を表す
// 2. 各数の出現回数を数える
// counter[num]num の出現回数を表す
vector<int> counter(m + 1, 0);
for (int num : nums) {
counter[num]++;
}
// 3. counterの前缀和を計算し、「出現回数」を「末尾インデックス」に変換
// counter[num]-1はnumがresで現れる最後のインデックス
// 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する
// つまり counter[num]-1 は、num が res に最後に現れるインデックス
for (int i = 0; i < m; i++) {
counter[i + 1] += counter[i];
}
// 4. numsを逆順走査し、各要素を結果配列resに配置
// 結果を記録する配列resを初期化
// 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[counter[num] - 1] = num; // num を対応するインデックスに配置
counter[num]--; // 累積和を 1 減らして、次に num を配置するインデックスを得
}
// 結果配列resで元の配列numsを上書き
// 結果配列 res で元の配列 nums を上書きする
nums = res;
}
```
@@ -276,34 +485,34 @@ $$
```java title="counting_sort.java"
/* 計数ソート */
// 完全な実装、オブジェクトをソートでき、安定ソート
// 完全な実装、オブジェクトをソートでき、かつ安定ソートである
void countingSort(int[] nums) {
// 1. 配列の最大要素 m を統計
// 1. 配列の最大要素 m を求める
int m = 0;
for (int num : nums) {
m = Math.max(m, num);
}
// 2. 各数の出現回数を統計
// 2. 各数の出現回数を数える
// counter[num] は num の出現回数を表す
int[] counter = new int[m + 1];
for (int num : nums) {
counter[num]++;
}
// 3. counter の累積和を計算し、「出現回数」を「尾インデックス」に変換
// counter[num]-1 は res 内で num が出現する最後のインデックス
// 3. counter の累積和を求めて、「出現回数」を「尾インデックス」に変換する
// つまり counter[num]-1 は、num が res に最後に現れるインデックス
for (int i = 0; i < m; i++) {
counter[i + 1] += counter[i];
}
// 4. nums を逆順に走査し、各要素を結果配列 res に配置
// 結果を記録する配列 res を初期化
// 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 を配置する次のインデックスを
counter[num]--; // 累積和を 1 減らして、次に num を配置するインデックスを得
}
// 結果配列 res を使って元の配列 nums を上書き
// 結果配列 res 元の配列 nums を上書きする
for (int i = 0; i < n; i++) {
nums[i] = res[i];
}
@@ -313,73 +522,376 @@ $$
=== "C#"
```csharp title="counting_sort.cs"
[class]{counting_sort}-[func]{CountingSort}
/* 計数ソート */
// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである
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"
[class]{}-[func]{countingSort}
/* 計数ソート */
// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである
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"
[class]{}-[func]{countingSort}
/* 計数ソート */
// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである
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"
[class]{}-[func]{countingSort}
/* 計数ソート */
// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである
function countingSort(nums) {
// 1. 配列の最大要素 m を求める
let m = Math.max(...nums);
// 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"
[class]{}-[func]{countingSort}
/* 計数ソート */
// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである
function countingSort(nums: number[]): void {
// 1. 配列の最大要素 m を求める
let m: number = Math.max(...nums);
// 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"
[class]{}-[func]{countingSort}
/* 計数ソート */
// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである
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 は、res において _num が最後に出現する位置のインデックスである
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"
[class]{}-[func]{counting_sort}
/* 計数ソート */
// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである
fn counting_sort(nums: &mut [i32]) {
// 1. 配列の最大要素 m を求める
let m = *nums.iter().max().unwrap() as usize;
// 2. 各数値の出現回数を数える
// counter[num] は num の出現回数を表す
let mut counter = vec![0; m + 1];
for &num in nums.iter() {
counter[num as usize] += 1;
}
// 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する
// つまり counter[num]-1 は、num が res に最後に現れるインデックス
for i in 0..m {
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 を上書きする
nums.copy_from_slice(&res)
}
```
=== "C"
```c title="counting_sort.c"
[class]{}-[func]{countingSort}
/* 計数ソート */
// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである
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(res);
free(counter);
}
```
=== "Kotlin"
```kotlin title="counting_sort.kt"
[class]{}-[func]{countingSort}
/* 計数ソート */
// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである
fun countingSort(nums: IntArray) {
// 1. 配列の最大要素 m を求める
var m = 0
for (num in nums) {
m = max(m, num)
}
// 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}
### 計数ソート ###
def counting_sort(nums)
# 完全版。オブジェクトをソートでき、かつ安定ソートである
# 1. 配列の最大要素 m を求める
m = nums.max
# 2. 各数値の出現回数を数える
# counter[num] は num の出現回数を表す
counter = Array.new(m + 1, 0)
nums.each { |num| counter[num] += 1 }
# 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する
# つまり counter[num]-1 は、num が res に最後に現れるインデックス
(0...m).each { |i| counter[i + 1] += counter[i] }
# 4. nums を逆順に走査し、各要素を結果配列 res に格納する
# 結果を記録するための配列 res を初期化する
n = nums.length
res = Array.new(n, 0)
(n - 1).downto(0).each do |i|
num = nums[i]
res[counter[num] - 1] = num # num を対応するインデックスに配置
counter[num] -= 1 # 累積和を 1 減らして、次に num を配置するインデックスを得る
end
# 結果配列 res で元の配列 nums を上書きする
(0...n).each { |i| nums[i] = res[i] }
end
```
## 11.9.3 &nbsp; アルゴリズムの特徴
??? pythontutor "コードの可視化"
- **時間計算量は $O(n + m)$、非適応ソート**`nums` と `counter` の走査が含まれ、どちらも線形時間を使用します。一般的に、$n \gg m$ であり、時間計算量は $O(n)$ に近づきます。
- **空間計算量は $O(n + m)$、非インプレースソート**:長さ $n$ の配列 `res` と長さ $m$ の配列 `counter` をそれぞれ使用します。
- **安定ソート**:要素が「右から左」の順序で `res` に埋められるため、`nums` の走査を逆順にすることで、等しい要素間の相対位置の変化を防ぎ、安定したソートを実現できます。実際、`nums` を順番に走査しても正しいソート結果を生成できますが、結果は不安定です。
<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%A8%88%E6%95%B0%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20%23%20%E5%AE%8C%E5%85%A8%E7%89%88%E3%80%82%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%82%92%E3%82%BD%E3%83%BC%E3%83%88%E3%81%A7%E3%81%8D%E3%80%81%E3%81%8B%E3%81%A4%E5%AE%89%E5%AE%9A%E3%82%BD%E3%83%BC%E3%83%88%E3%81%A7%E3%81%82%E3%82%8B%0A%20%20%20%20%23%201.%20%E9%85%8D%E5%88%97%E3%81%AE%E6%9C%80%E5%A4%A7%E8%A6%81%E7%B4%A0%20m%20%E3%82%92%E6%B1%82%E3%82%81%E3%82%8B%0A%20%20%20%20m%20%3D%20max%28nums%29%0A%20%20%20%20%23%202.%20%E5%90%84%E6%95%B0%E5%80%A4%E3%81%AE%E5%87%BA%E7%8F%BE%E5%9B%9E%E6%95%B0%E3%82%92%E6%95%B0%E3%81%88%E3%82%8B%0A%20%20%20%20%23%20counter%5Bnum%5D%20%E3%81%AF%20num%20%E3%81%AE%E5%87%BA%E7%8F%BE%E5%9B%9E%E6%95%B0%E3%82%92%E8%A1%A8%E3%81%99%0A%20%20%20%20counter%20%3D%20%5B0%5D%20%2A%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.%20counter%20%E3%81%AE%E7%B4%AF%E7%A9%8D%E5%92%8C%E3%82%92%E6%B1%82%E3%82%81%E3%81%A6%E3%80%81%E3%80%8C%E5%87%BA%E7%8F%BE%E5%9B%9E%E6%95%B0%E3%80%8D%E3%82%92%E3%80%8C%E6%9C%AB%E5%B0%BE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%80%8D%E3%81%AB%E5%A4%89%E6%8F%9B%E3%81%99%E3%82%8B%0A%20%20%20%20%23%20%E3%81%A4%E3%81%BE%E3%82%8A%20counter%5Bnum%5D-1%20%E3%81%AF%E3%80%81num%20%E3%81%8C%20res%20%E3%81%AB%E6%9C%80%E5%BE%8C%E3%81%AB%E7%8F%BE%E3%82%8C%E3%82%8B%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%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.%20nums%20%E3%82%92%E9%80%86%E9%A0%86%E3%81%AB%E8%B5%B0%E6%9F%BB%E3%81%97%E3%80%81%E5%90%84%E8%A6%81%E7%B4%A0%E3%82%92%E7%B5%90%E6%9E%9C%E9%85%8D%E5%88%97%20res%20%E3%81%AB%E6%A0%BC%E7%B4%8D%E3%81%99%E3%82%8B%0A%20%20%20%20%23%20%E7%B5%90%E6%9E%9C%E3%82%92%E8%A8%98%E9%8C%B2%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AE%E9%85%8D%E5%88%97%20res%20%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20res%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%20-1%2C%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%20num%20%E3%82%92%E5%AF%BE%E5%BF%9C%E3%81%99%E3%82%8B%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%AB%E9%85%8D%E7%BD%AE%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20-%3D%201%20%20%23%20%E7%B4%AF%E7%A9%8D%E5%92%8C%E3%82%92%201%20%E6%B8%9B%E3%82%89%E3%81%97%E3%81%A6%E3%80%81%E6%AC%A1%E3%81%AB%20num%20%E3%82%92%E9%85%8D%E7%BD%AE%E3%81%99%E3%82%8B%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E5%BE%97%E3%82%8B%0A%20%20%20%20%23%20%E7%B5%90%E6%9E%9C%E9%85%8D%E5%88%97%20res%20%E3%81%A7%E5%85%83%E3%81%AE%E9%85%8D%E5%88%97%20nums%20%E3%82%92%E4%B8%8A%E6%9B%B8%E3%81%8D%E3%81%99%E3%82%8B%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%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%200%2C%201%2C%202%2C%200%2C%204%2C%200%2C%202%2C%202%2C%204%5D%0A%20%20%20%20counting_sort%28nums%29%0A%20%20%20%20print%28f%22%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%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%A8%88%E6%95%B0%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20%23%20%E5%AE%8C%E5%85%A8%E7%89%88%E3%80%82%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%82%92%E3%82%BD%E3%83%BC%E3%83%88%E3%81%A7%E3%81%8D%E3%80%81%E3%81%8B%E3%81%A4%E5%AE%89%E5%AE%9A%E3%82%BD%E3%83%BC%E3%83%88%E3%81%A7%E3%81%82%E3%82%8B%0A%20%20%20%20%23%201.%20%E9%85%8D%E5%88%97%E3%81%AE%E6%9C%80%E5%A4%A7%E8%A6%81%E7%B4%A0%20m%20%E3%82%92%E6%B1%82%E3%82%81%E3%82%8B%0A%20%20%20%20m%20%3D%20max%28nums%29%0A%20%20%20%20%23%202.%20%E5%90%84%E6%95%B0%E5%80%A4%E3%81%AE%E5%87%BA%E7%8F%BE%E5%9B%9E%E6%95%B0%E3%82%92%E6%95%B0%E3%81%88%E3%82%8B%0A%20%20%20%20%23%20counter%5Bnum%5D%20%E3%81%AF%20num%20%E3%81%AE%E5%87%BA%E7%8F%BE%E5%9B%9E%E6%95%B0%E3%82%92%E8%A1%A8%E3%81%99%0A%20%20%20%20counter%20%3D%20%5B0%5D%20%2A%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.%20counter%20%E3%81%AE%E7%B4%AF%E7%A9%8D%E5%92%8C%E3%82%92%E6%B1%82%E3%82%81%E3%81%A6%E3%80%81%E3%80%8C%E5%87%BA%E7%8F%BE%E5%9B%9E%E6%95%B0%E3%80%8D%E3%82%92%E3%80%8C%E6%9C%AB%E5%B0%BE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%80%8D%E3%81%AB%E5%A4%89%E6%8F%9B%E3%81%99%E3%82%8B%0A%20%20%20%20%23%20%E3%81%A4%E3%81%BE%E3%82%8A%20counter%5Bnum%5D-1%20%E3%81%AF%E3%80%81num%20%E3%81%8C%20res%20%E3%81%AB%E6%9C%80%E5%BE%8C%E3%81%AB%E7%8F%BE%E3%82%8C%E3%82%8B%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%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.%20nums%20%E3%82%92%E9%80%86%E9%A0%86%E3%81%AB%E8%B5%B0%E6%9F%BB%E3%81%97%E3%80%81%E5%90%84%E8%A6%81%E7%B4%A0%E3%82%92%E7%B5%90%E6%9E%9C%E9%85%8D%E5%88%97%20res%20%E3%81%AB%E6%A0%BC%E7%B4%8D%E3%81%99%E3%82%8B%0A%20%20%20%20%23%20%E7%B5%90%E6%9E%9C%E3%82%92%E8%A8%98%E9%8C%B2%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AE%E9%85%8D%E5%88%97%20res%20%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20res%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%20-1%2C%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%20num%20%E3%82%92%E5%AF%BE%E5%BF%9C%E3%81%99%E3%82%8B%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%AB%E9%85%8D%E7%BD%AE%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20-%3D%201%20%20%23%20%E7%B4%AF%E7%A9%8D%E5%92%8C%E3%82%92%201%20%E6%B8%9B%E3%82%89%E3%81%97%E3%81%A6%E3%80%81%E6%AC%A1%E3%81%AB%20num%20%E3%82%92%E9%85%8D%E7%BD%AE%E3%81%99%E3%82%8B%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E5%BE%97%E3%82%8B%0A%20%20%20%20%23%20%E7%B5%90%E6%9E%9C%E9%85%8D%E5%88%97%20res%20%E3%81%A7%E5%85%83%E3%81%AE%E9%85%8D%E5%88%97%20nums%20%E3%82%92%E4%B8%8A%E6%9B%B8%E3%81%8D%E3%81%99%E3%82%8B%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%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%200%2C%201%2C%202%2C%200%2C%204%2C%200%2C%202%2C%202%2C%204%5D%0A%20%20%20%20counting_sort%28nums%29%0A%20%20%20%20print%28f%22%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%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.4 &nbsp; 制限事項
## 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)$ ソートアルゴリズムより遅い可能性があります。
ここまで読むと、計数ソートは非常に巧妙で、個数を数えるだけで効率的なソートを実現できると感じるかもしれません。しかし、計数ソートを利用するための前提条件は比較的厳格です。
**計数ソートは非負整数にしか適用できません**。ほかの型のデータに適用したい場合は、それらのデータを非負整数に変換でき、かつ変換の過程で要素間の相対的な大小関係が変わらないことを保証する必要があります。たとえば、負数を含む整数配列に対しては、すべての数値に定数を加えて正数へ変換し、ソート後に元へ戻すことができます。
**計数ソートはデータ量が多く、値域が小さい場合に適しています**。たとえば上記の例では $m$ が大きすぎてはならず、そうでないと過剰な空間を消費します。また、$n \ll m$ のとき、計数ソートは $O(m)$ 時間を要するため、$O(n \log n)$ のソートアルゴリズムより遅くなる可能性があります。
+414 -65
View File
@@ -6,30 +6,30 @@ comments: true
!!! tip
この節を読む前に、「ヒープ」の章を必ず完了させてください。
節を読む前に、「ヒープ」の章を学習済みであることを確認してください。
<u>ヒープソート</u>は、ヒープデータ構造に基づ効率的なソートアルゴリズムです。すでに学習した「ヒープ構築」と「要素の抽出」操作を使用してヒープソートを実できます。
<u>ヒープソートheap sort</u>は、ヒープデータ構造に基づいて実装される効率的なソートアルゴリズムです。すでに学んだ「ヒープ構築操作」と「要素の取り出し操作」を利用してヒープソートを実できます。
1. 配列を入力し最小ヒープを構築します。ここで、最小要素ヒープの頂上に位置します。
2. 継続的に抽出操作を実行し、出された要素を順記録して、最小から最大までのソート済みリストを取得します。
1. 配列を入力し最小ヒープを構築すると、このとき最小要素ヒープの頂点にあります。
2. 取り出し操作を繰り返し実行し、取り出された要素を順記録すれば、昇順に並んだ列が得られます。
の方法は実現可能ですが、ポップされた要素を格納するため追加の配列が必要で、やや空間を消費します。実際には、通常より優雅な実装を使用します。
上の方法でも実行できますが、取り出した要素を保存するため追加の配列が必要となり、空間をやや無駄にします。実際には、通常より洗練された実装方法を用います。
## 11.7.1 &nbsp; アルゴリズムの流れ
配列の長さを $n$ とすると、ヒープソートの過程は以下の通りです。
配列の長さを $n$ とすると、ヒープソートの流れは次図のとおりです。
1. 配列を入力し最大ヒープを構築します。この手順の後、最大要素ヒープの頂上に位置します。
2. ヒープの頂上要素(最初の要素)とヒープの底部要素(最後の要素)を交換します。この交換後、ヒープの長さ $1$ 減し、ソート済み要素の数を $1$ 増します。
3. ヒープの頂上から開始して、上から下へのsift-down操作を実行します。sift-downの後、ヒープの性質が復元されます。
4. 手順 `2.` `3.` を繰り返します。$n - 1$ ラウンドループして、配列のソートを完了します。
1. 配列を入力し最大ヒープを構築します。完了後、最大要素ヒープの頂点にあります。
2. ヒープ頂点の要素(最初の要素)とヒープ末尾の要素(最後の要素)を交換します。交換後、ヒープの長さ $1$ 減し、整列済み要素数は $1$ 増します。
3. ヒープ頂点の要素から始めて、上から下へヒープ化操作(sift downを実行します。ヒープ化が完了すると、ヒープの性質が回復します。
4. `2.` ステップと第 `3.` ステップを繰り返し実行します。これを $n - 1$ 回繰り返すと、配列の整列が完了します。
!!! tip
実際、要素抽出操作も手順 `2.` `3.` を含み、抽出された要素をヒープから削除する追加の手順があります。
実際には、要素の取り出し操作にも第 `2.` ステップと第 `3.` ステップが含まれており、要素を取り出す処理が 1 つ加わるだけです。
=== "<1>"
![ヒープソートの過程](heap_sort.assets/heap_sort_step1.png){ class="animation-figure" }
![ヒープソートの手順](heap_sort.assets/heap_sort_step1.png){ class="animation-figure" }
=== "<2>"
![heap_sort_step2](heap_sort.assets/heap_sort_step2.png){ class="animation-figure" }
@@ -64,17 +64,17 @@ comments: true
=== "<12>"
![heap_sort_step12](heap_sort.assets/heap_sort_step12.png){ class="animation-figure" }
<p align="center"> 図 11-12 &nbsp; ヒープソートの過程 </p>
<p align="center"> 図 11-12 &nbsp; ヒープソートの手順 </p>
コード実装では、「ヒープ」の章からのsift-down関数 `sift_down()` を使用しました。最大要素が抽出されるにつれてヒープの長さが減少するため、`sift_down()` 関数に長さパラメータ $n$ を追加し、ヒープの現在の有効を指定する必要があることに注意することが重要です。コードは以下のりです
コード実装では、「ヒープ」の章と同じ上から下へのヒープ化 `sift_down()` 関数を使用します。注意すべき点として、ヒープの長さは最大要素を取り出すたびに短くなるため、`sift_down()` 関数に長さパラメータ $n$ を追加し、ヒープの現在の有効な長さを指定する必要があります。コードは以下のとおりです
=== "Python"
```python title="heap_sort.py"
def sift_down(nums: list[int], n: int, i: int):
"""ヒープの長さ nノード i から上から下へヒープ化を開始"""
"""ヒープの長さ nノード i から下方向にヒープ化"""
while True:
# i、l、r の中で最大のノードを判定し、ma とする
# ノード i, l, r のうち値が最大のノードを ma とする
l = 2 * i + 1
r = 2 * i + 2
ma = i
@@ -82,34 +82,34 @@ comments: true
ma = l
if r < n and nums[r] > nums[ma]:
ma = r
# ノード i が最大または l、r のインデックスが範囲外の場合、さらなるヒープ化は不要、ループを抜ける
# ノード i が最大またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける
if ma == i:
break
# 2つのノードを交換
# 2 つのノードを交換
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 回繰り返す
# ヒープから最大要素を取り出し、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から上から下へヒープ化を開始 */
/* ヒープの長さは n。ノード i から下方向にヒープ化 */
void siftDown(vector<int> &nums, int n, int i) {
while (true) {
// i、l、r の中で最大のノードを決定し、maとして記録
// ノード i, l, r のうち値が最大のノードを ma とする
int l = 2 * i + 1;
int r = 2 * i + 2;
int ma = i;
@@ -117,28 +117,28 @@ comments: true
ma = l;
if (r < n && nums[r] > nums[ma])
ma = r;
// ノードiが最大か、インデックスl、rが境界外の場合、それ以上のヒープ化は不要で終了
// ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける
if (ma == i) {
break;
}
// つのノードを交換
// 2 つのノードを交換
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回繰り返す
// ヒープから最大要素を取り出し、n-1 回繰り返す
for (int i = nums.size() - 1; i > 0; --i) {
// ルートノードを最右葉ノード交換(最初の要素を最後の要素交換)
// 根ノードと最も右の葉ノード交換(先頭要素と末尾要素交換)
swap(nums[0], nums[i]);
// ルートノードから上から下へヒープ化を開始
// 根ノードを起点に、上から下へヒープ化
siftDown(nums, i, 0);
}
}
@@ -147,10 +147,10 @@ comments: true
=== "Java"
```java title="heap_sort.java"
/* ヒープの長さは nノード i から上から下へヒープ化開始 */
/* ヒープの長さは nノード i から下方向にヒープ化 */
void siftDown(int[] nums, int n, int i) {
while (true) {
// i, l, r の中で最大のノードを判定し、ma とする
// ノード i, l, r のうち値が最大のノードを ma とする
int l = 2 * i + 1;
int r = 2 * i + 2;
int ma = i;
@@ -158,31 +158,31 @@ comments: true
ma = l;
if (r < n && nums[r] > nums[ma])
ma = r;
// ノード i が最大、またはインデックス l, r が範囲外の場合、さらなるヒープ化は不要、ブレーク
// ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける
if (ma == i)
break;
// 2つのノードを交換
// 2 つのノードを交換
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 回繰り返
// ヒープから最大要素を取り出し、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);
}
}
@@ -191,85 +191,434 @@ comments: true
=== "C#"
```csharp title="heap_sort.cs"
[class]{heap_sort}-[func]{SiftDown}
/* ヒープの長さは 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;
// 2 つのノードを交換
(nums[ma], nums[i]) = (nums[i], nums[ma]);
// ループで上から下へヒープ化
i = ma;
}
}
[class]{heap_sort}-[func]{HeapSort}
/* ヒープソート */
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"
[class]{}-[func]{siftDown}
/* ヒープの長さは 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
}
// 2 つのノードを交換
(*nums)[i], (*nums)[ma] = (*nums)[ma], (*nums)[i]
// ループで上から下へヒープ化
i = ma
}
}
[class]{}-[func]{heapSort}
/* ヒープソート */
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"
[class]{}-[func]{siftDown}
/* ヒープの長さは 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
}
// 2 つのノードを交換
nums.swapAt(i, ma)
// ループで上から下へヒープ化
i = ma
}
}
[class]{}-[func]{heapSort}
/* ヒープソート */
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"
[class]{}-[func]{siftDown}
/* ヒープの長さは 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;
}
// 2 つのノードを交換
[nums[i], nums[ma]] = [nums[ma], nums[i]];
// ループで上から下へヒープ化
i = ma;
}
}
[class]{}-[func]{heapSort}
/* ヒープソート */
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"
[class]{}-[func]{siftDown}
/* ヒープの長さは 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;
}
// 2 つのノードを交換
[nums[i], nums[ma]] = [nums[ma], nums[i]];
// ループで上から下へヒープ化
i = ma;
}
}
[class]{}-[func]{heapSort}
/* ヒープソート */
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"
[class]{}-[func]{siftDown}
/* ヒープの長さは 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;
// 2 つのノードを交換
int temp = nums[i];
nums[i] = nums[ma];
nums[ma] = temp;
// ループで上から下へヒープ化
i = ma;
}
}
[class]{}-[func]{heapSort}
/* ヒープソート */
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"
[class]{}-[func]{sift_down}
/* ヒープの長さは 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;
}
// 2 つのノードを交換
nums.swap(i, ma);
// ループで上から下へヒープ化
i = ma;
}
}
[class]{}-[func]{heap_sort}
/* ヒープソート */
fn heap_sort(nums: &mut [i32]) {
// ヒープ構築:葉ノード以外のすべてのノードをヒープ化する
for i in (0..nums.len() / 2).rev() {
sift_down(nums, nums.len(), i);
}
// ヒープから最大要素を取り出し、n-1 回繰り返す
for i in (1..nums.len()).rev() {
// 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換)
nums.swap(0, i);
// 根ノードを起点に、上から下へヒープ化
sift_down(nums, i, 0);
}
}
```
=== "C"
```c title="heap_sort.c"
[class]{}-[func]{siftDown}
/* ヒープの長さは 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;
}
// 2 つのノードを交換
int temp = nums[i];
nums[i] = nums[ma];
nums[ma] = temp;
// ループで上から下へヒープ化
i = ma;
}
}
[class]{}-[func]{heapSort}
/* ヒープソート */
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"
[class]{}-[func]{siftDown}
/* ヒープの長さは 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
// 2 つのノードを交換
val temp = nums[i]
nums[i] = nums[ma]
nums[ma] = temp
// ループで上から下へヒープ化
i = ma
}
}
[class]{}-[func]{heapSort}
/* ヒープソート */
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) {
// 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換)
val temp = nums[0]
nums[0] = nums[i]
nums[i] = temp
// 根ノードを起点に、上から下へヒープ化
siftDown(nums, i, 0)
}
}
```
=== "Ruby"
```ruby title="heap_sort.rb"
[class]{}-[func]{sift_down}
### ヒープ長 n で、ノード i から上から下へヒープ化 ###
def sift_down(nums, n, i)
while true
# ノード i, l, r のうち値が最大のノードを ma とする
l = 2 * i + 1
r = 2 * i + 2
ma = i
ma = l if l < n && nums[l] > nums[ma]
ma = r if r < n && nums[r] > nums[ma]
# ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける
break if ma == i
# 2 つのノードを交換
nums[i], nums[ma] = nums[ma], nums[i]
# ループで上から下へヒープ化
i = ma
end
end
[class]{}-[func]{heap_sort}
### ヒープソート ###
def heap_sort(nums)
# ヒープ構築:葉ノード以外のすべてのノードをヒープ化する
(nums.length / 2 - 1).downto(0) do |i|
sift_down(nums, nums.length, i)
end
# ヒープから最大要素を取り出し、n-1 回繰り返す
(nums.length - 1).downto(1) do |i|
# 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換)
nums[0], nums[i] = nums[i], nums[0]
# 根ノードを起点に、上から下へヒープ化
sift_down(nums, i, 0)
end
end
```
## 11.7.2 &nbsp; アルゴリズムの特徴
??? pythontutor "コードの可視化"
- **時間計算量は $O(n \log n)$、非適応ソート**:ヒープの構築は $O(n)$ 時間を使用します。ヒープから最大要素を抽出するには $O(\log n)$ 時間がかかり、$n - 1$ ラウンドループします。
- **空間計算量は $O(1)$、インプレースソート**:いくつかのポインタ変数が $O(1)$ 空間を使用します。要素の交換とヒープ化操作は元の配列で実行されます。
- **非安定ソート**:ヒープの頂上と底部要素の交換中に、等しい要素の相対位置が変わる可能性があります。
<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%2C%20n%3A%20int%2C%20i%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E3%83%92%E3%83%BC%E3%83%97%E3%81%AE%E9%95%B7%E3%81%95%E3%81%AF%20n%E3%80%82%E3%83%8E%E3%83%BC%E3%83%89%20i%20%E3%81%8B%E3%82%89%E4%B8%8B%E6%96%B9%E5%90%91%E3%81%AB%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%22%22%22%0A%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%20i%2C%20l%2C%20r%20%E3%81%AE%E3%81%86%E3%81%A1%E5%80%A4%E3%81%8C%E6%9C%80%E5%A4%A7%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%20ma%20%E3%81%A8%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20l%20%3D%202%20%2A%20i%20%2B%201%0A%20%20%20%20%20%20%20%20r%20%3D%202%20%2A%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%E3%83%8E%E3%83%BC%E3%83%89%20i%20%E3%81%8C%E6%9C%80%E5%A4%A7%E3%80%81%E3%81%BE%E3%81%9F%E3%81%AF%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20l%2C%20r%20%E3%81%8C%E7%AF%84%E5%9B%B2%E5%A4%96%E3%81%AA%E3%82%89%E3%80%81%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%E3%81%AF%E4%B8%8D%E8%A6%81%E3%81%AA%E3%81%AE%E3%81%A7%E6%8A%9C%E3%81%91%E3%82%8B%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%202%20%E3%81%A4%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bma%5D%20%3D%20nums%5Bma%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20%23%20%E3%83%AB%E3%83%BC%E3%83%97%E3%81%A7%E4%B8%8A%E3%81%8B%E3%82%89%E4%B8%8B%E3%81%B8%E3%83%92%E3%83%BC%E3%83%97%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%E3%83%92%E3%83%BC%E3%83%97%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20%23%20%E3%83%92%E3%83%BC%E3%83%97%E6%A7%8B%E7%AF%89%EF%BC%9A%E8%91%89%E3%83%8E%E3%83%BC%E3%83%89%E4%BB%A5%E5%A4%96%E3%81%AE%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20%2F%2F%202%20-%201%2C%20-1%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20sift_down%28nums%2C%20len%28nums%29%2C%20i%29%0A%20%20%20%20%23%20%E3%83%92%E3%83%BC%E3%83%97%E3%81%8B%E3%82%89%E6%9C%80%E5%A4%A7%E8%A6%81%E7%B4%A0%E3%82%92%E5%8F%96%E3%82%8A%E5%87%BA%E3%81%97%E3%80%81n-1%20%E5%9B%9E%E7%B9%B0%E3%82%8A%E8%BF%94%E3%81%99%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%A0%B9%E3%83%8E%E3%83%BC%E3%83%89%E3%81%A8%E6%9C%80%E3%82%82%E5%8F%B3%E3%81%AE%E8%91%89%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E4%BA%A4%E6%8F%9B%EF%BC%88%E5%85%88%E9%A0%AD%E8%A6%81%E7%B4%A0%E3%81%A8%E6%9C%AB%E5%B0%BE%E8%A6%81%E7%B4%A0%E3%82%92%E4%BA%A4%E6%8F%9B%EF%BC%89%0A%20%20%20%20%20%20%20%20nums%5B0%5D%2C%20nums%5Bi%5D%20%3D%20nums%5Bi%5D%2C%20nums%5B0%5D%0A%20%20%20%20%20%20%20%20%23%20%E6%A0%B9%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E8%B5%B7%E7%82%B9%E3%81%AB%E3%80%81%E4%B8%8A%E3%81%8B%E3%82%89%E4%B8%8B%E3%81%B8%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%0A%20%20%20%20%20%20%20%20sift_down%28nums%2C%20i%2C%200%29%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20heap_sort%28nums%29%0A%20%20%20%20print%28%22%E3%83%92%E3%83%BC%E3%83%97%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%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%2C%20n%3A%20int%2C%20i%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E3%83%92%E3%83%BC%E3%83%97%E3%81%AE%E9%95%B7%E3%81%95%E3%81%AF%20n%E3%80%82%E3%83%8E%E3%83%BC%E3%83%89%20i%20%E3%81%8B%E3%82%89%E4%B8%8B%E6%96%B9%E5%90%91%E3%81%AB%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%22%22%22%0A%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%20i%2C%20l%2C%20r%20%E3%81%AE%E3%81%86%E3%81%A1%E5%80%A4%E3%81%8C%E6%9C%80%E5%A4%A7%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%20ma%20%E3%81%A8%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20l%20%3D%202%20%2A%20i%20%2B%201%0A%20%20%20%20%20%20%20%20r%20%3D%202%20%2A%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%E3%83%8E%E3%83%BC%E3%83%89%20i%20%E3%81%8C%E6%9C%80%E5%A4%A7%E3%80%81%E3%81%BE%E3%81%9F%E3%81%AF%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20l%2C%20r%20%E3%81%8C%E7%AF%84%E5%9B%B2%E5%A4%96%E3%81%AA%E3%82%89%E3%80%81%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%E3%81%AF%E4%B8%8D%E8%A6%81%E3%81%AA%E3%81%AE%E3%81%A7%E6%8A%9C%E3%81%91%E3%82%8B%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%202%20%E3%81%A4%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bma%5D%20%3D%20nums%5Bma%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20%23%20%E3%83%AB%E3%83%BC%E3%83%97%E3%81%A7%E4%B8%8A%E3%81%8B%E3%82%89%E4%B8%8B%E3%81%B8%E3%83%92%E3%83%BC%E3%83%97%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%E3%83%92%E3%83%BC%E3%83%97%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20%23%20%E3%83%92%E3%83%BC%E3%83%97%E6%A7%8B%E7%AF%89%EF%BC%9A%E8%91%89%E3%83%8E%E3%83%BC%E3%83%89%E4%BB%A5%E5%A4%96%E3%81%AE%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20%2F%2F%202%20-%201%2C%20-1%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20sift_down%28nums%2C%20len%28nums%29%2C%20i%29%0A%20%20%20%20%23%20%E3%83%92%E3%83%BC%E3%83%97%E3%81%8B%E3%82%89%E6%9C%80%E5%A4%A7%E8%A6%81%E7%B4%A0%E3%82%92%E5%8F%96%E3%82%8A%E5%87%BA%E3%81%97%E3%80%81n-1%20%E5%9B%9E%E7%B9%B0%E3%82%8A%E8%BF%94%E3%81%99%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%A0%B9%E3%83%8E%E3%83%BC%E3%83%89%E3%81%A8%E6%9C%80%E3%82%82%E5%8F%B3%E3%81%AE%E8%91%89%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E4%BA%A4%E6%8F%9B%EF%BC%88%E5%85%88%E9%A0%AD%E8%A6%81%E7%B4%A0%E3%81%A8%E6%9C%AB%E5%B0%BE%E8%A6%81%E7%B4%A0%E3%82%92%E4%BA%A4%E6%8F%9B%EF%BC%89%0A%20%20%20%20%20%20%20%20nums%5B0%5D%2C%20nums%5Bi%5D%20%3D%20nums%5Bi%5D%2C%20nums%5B0%5D%0A%20%20%20%20%20%20%20%20%23%20%E6%A0%B9%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E8%B5%B7%E7%82%B9%E3%81%AB%E3%80%81%E4%B8%8A%E3%81%8B%E3%82%89%E4%B8%8B%E3%81%B8%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%0A%20%20%20%20%20%20%20%20sift_down%28nums%2C%20i%2C%200%29%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20heap_sort%28nums%29%0A%20%20%20%20print%28%22%E3%83%92%E3%83%BC%E3%83%97%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%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)$ です。要素の交換とヒープ化操作はいずれも元の配列上で行われます。
- **非安定ソート**:ヒープ頂点の要素とヒープ末尾の要素を交換する際、等しい要素どうしの相対位置が変化する可能性があります。
+3 -3
View File
@@ -5,13 +5,13 @@ icon: material/sort-ascending
# 第 11 章 &nbsp; ソート
![Sorting](../assets/covers/chapter_sorting.jpg){ class="cover-image" }
![ソート](../assets/covers/chapter_sorting.jpg){ class="cover-image" }
!!! abstract
ソートは混沌を秩序変える魔法の鍵のようなもので、データをより効率的に理解し処理することを可能にします。
ソートは混沌を秩序へと変える魔法の鍵のようなものであり、データをより効率的に理解し処理することを可能にします。
単純な昇順であろうと複雑なカテゴリ配列であろうと、ソートはデータの調和美を明らかにします。
単純な昇順であれ、複雑な分類配列であ、ソートはデータの調和のとれた美しさを私たちに示してくれます。
## 章の内容
+181 -47
View File
@@ -4,45 +4,45 @@ comments: true
# 11.4 &nbsp; 挿入ソート
<u>挿入ソート</u>は、トランプのデッキを手動でソートするプロセスによく似た動作をするシンプルなソートアルゴリズムです。
<u>挿入ソートinsertion sort</u>は単純なソートアルゴリズムであり、その動作原理は手作業でトランプの山を整える過程と非常によく似ています。
具体的には、未ソート区間からベース要素を選択し、その左側のソート済み区間の要素と比較して、要素を正しい位置に挿入します。
具体的には、未ソート区間から基準要素を 1 つ選び、その要素を左側の整列済み区間の要素と 1 つずつ比較し、正しい位置に挿入します。
下図は、要素が配列に挿入される方法を示しています。ベース要素を`base`とすると、ターゲットインデックスから`base`までのすべての要素を右に1つずつシフトし、その後`base`をターゲットインデックスに割り当てる必要があります。
以下の図は、配列に要素を挿入する操作の流れを示しています。基準要素を `base` とすると、目的のインデックスから `base` までのすべての要素を 1 つずつ右に移動し、その後 `base` を目的のインデックスに代入する必要があります。
![Single insertion operation](insertion_sort.assets/insertion_operation.png){ class="animation-figure" }
![1 回の挿入操作](insertion_sort.assets/insertion_operation.png){ class="animation-figure" }
<p align="center"> 図 11-6 &nbsp; Single insertion operation </p>
<p align="center"> 図 11-6 &nbsp; 1 回の挿入操作 </p>
## 11.4.1 &nbsp; アルゴリズムプロセス
## 11.4.1 &nbsp; アルゴリズムの流れ
挿入ソート全体的なプロセスは下図に示されます。
挿入ソート全体の流れを以下の図に示ます。
1. 配列の最初の要素をソート済みとみなします。
2. 2番目の要素を`base`として選択し、正しい位置に挿入して、**最初の2つの要素をソート済みにします**
3. 3番目の要素を`base`として選択し、正しい位置に挿入して、**最初の3つの要素をソート済みにします**
4. この方法で続行し、最後の反復では、最後の要素を`base`として取り、正しい位置に挿入した後、**すべての要素がソートされます**
1. 初期状態では、配列の 1 番目の要素はすでに整列済みです。
2. 配列の 2 番目の要素を `base` として選、正しい位置に挿入すると、**配列の先頭 2 要素が整列済み**になります
3. 3 番目の要素を `base` として選、正しい位置に挿入すると、**配列の先頭 3 要素が整列済み**になります
4. このように繰り返し、最後のラウンドで最後の要素を `base` として選んで正しい位置に挿入すると、**すべての要素が整列済み**になります
![Insertion sort process](insertion_sort.assets/insertion_sort_overview.png){ class="animation-figure" }
![挿入ソートの流れ](insertion_sort.assets/insertion_sort_overview.png){ class="animation-figure" }
<p align="center"> 図 11-7 &nbsp; Insertion sort process </p>
<p align="center"> 図 11-7 &nbsp; 挿入ソートの流れ </p>
コード例は以下のりです
コード例は以下のとおりです
=== "Python"
```python title="insertion_sort.py"
def insertion_sort(nums: list[int]):
"""挿入ソート"""
# 外側ループ:ソート済み範囲は [0, i-1]
# 外側ループ:整列済み区間は [0, i-1]
for i in range(1, len(nums)):
base = nums[i]
j = i - 1
# 内側ループbase をソート済み範囲 [0, i-1] の正しい位置に挿入
# 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する
while j >= 0 and nums[j] > base:
nums[j + 1] = nums[j] # nums[j] を右に1つ移動
nums[j + 1] = nums[j] # nums[j] を 1 つ右へ移動する
j -= 1
nums[j + 1] = base # base を正しい位置に代入
nums[j + 1] = base # base を正しい位置に配置する
```
=== "C++"
@@ -50,15 +50,15 @@ comments: true
```cpp title="insertion_sort.cpp"
/* 挿入ソート */
void insertionSort(vector<int> &nums) {
// 外側ループ:ソート済み範囲は[0, i-1]
// 外側ループ:整列済み区間は [0, i-1]
for (int i = 1; i < nums.size(); i++) {
int base = nums[i], j = i - 1;
// 内側ループbaseをソート済み範囲[0, i-1]の正しい位置に挿入
// 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する
while (j >= 0 && nums[j] > base) {
nums[j + 1] = nums[j]; // nums[j]を一つ右移動
nums[j + 1] = nums[j]; // nums[j] を 1 つ右移動する
j--;
}
nums[j + 1] = base; // baseを正しい位置に代入
nums[j + 1] = base; // base を正しい位置に配置する
}
}
```
@@ -68,15 +68,15 @@ comments: true
```java title="insertion_sort.java"
/* 挿入ソート */
void insertionSort(int[] nums) {
// 外側ループ: ソート済み範囲は [0, i-1]
// 外側ループ:整列済み区間は [0, i-1]
for (int i = 1; i < nums.length; i++) {
int base = nums[i], j = i - 1;
// 内側ループ: base をソート済み範囲 [0, i-1] の正しい位置に挿入
// 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する
while (j >= 0 && nums[j] > base) {
nums[j + 1] = nums[j]; // nums[j] を右に1つ移動
nums[j + 1] = nums[j]; // nums[j] を 1 つ右へ移動する
j--;
}
nums[j + 1] = base; // base を正しい位置に代入
nums[j + 1] = base; // base を正しい位置に配置する
}
}
```
@@ -84,79 +84,213 @@ comments: true
=== "C#"
```csharp title="insertion_sort.cs"
[class]{insertion_sort}-[func]{InsertionSort}
/* 挿入ソート */
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] を 1 つ右へ移動する
j--;
}
nums[j + 1] = bas; // base を正しい位置に配置する
}
}
```
=== "Go"
```go title="insertion_sort.go"
[class]{}-[func]{insertionSort}
/* 挿入ソート */
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] を 1 つ右へ移動する
j--
}
nums[j+1] = base // base を正しい位置に配置する
}
}
```
=== "Swift"
```swift title="insertion_sort.swift"
[class]{}-[func]{insertionSort}
/* 挿入ソート */
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] を 1 つ右へ移動する
j -= 1
}
nums[j + 1] = base // base を正しい位置に配置する
}
}
```
=== "JS"
```javascript title="insertion_sort.js"
[class]{}-[func]{insertionSort}
/* 挿入ソート */
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] を 1 つ右へ移動する
j--;
}
nums[j + 1] = base; // base を正しい位置に配置する
}
}
```
=== "TS"
```typescript title="insertion_sort.ts"
[class]{}-[func]{insertionSort}
/* 挿入ソート */
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] を 1 つ右へ移動する
j--;
}
nums[j + 1] = base; // base を正しい位置に配置する
}
}
```
=== "Dart"
```dart title="insertion_sort.dart"
[class]{}-[func]{insertionSort}
/* 挿入ソート */
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] を 1 つ右へ移動する
j--;
}
nums[j + 1] = base; // base を正しい位置に配置する
}
}
```
=== "Rust"
```rust title="insertion_sort.rs"
[class]{}-[func]{insertion_sort}
/* 挿入ソート */
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] を 1 つ右へ移動する
j -= 1;
}
nums[(j + 1) as usize] = base; // base を正しい位置に配置する
}
}
```
=== "C"
```c title="insertion_sort.c"
[class]{}-[func]{insertionSort}
/* 挿入ソート */
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] を 1 つ右へ移動する
nums[j + 1] = nums[j];
j--;
}
// base を正しい位置に配置する
nums[j + 1] = base;
}
}
```
=== "Kotlin"
```kotlin title="insertion_sort.kt"
[class]{}-[func]{insertionSort}
/* 挿入ソート */
fun insertionSort(nums: IntArray) {
// 外側ループ: ソート済み要素は 1, 2, ..., n
for (i in nums.indices) {
val base = nums[i]
var j = i - 1
// 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する
while (j >= 0 && nums[j] > base) {
nums[j + 1] = nums[j] // nums[j] を 1 つ右へ移動する
j--
}
nums[j + 1] = base // base を正しい位置に配置する
}
}
```
=== "Ruby"
```ruby title="insertion_sort.rb"
[class]{}-[func]{insertion_sort}
### 挿入ソート ###
def insertion_sort(nums)
n = nums.length
# 外側ループ:整列済み区間は [0, i-1]
for i in 1...n
base = nums[i]
j = i - 1
# 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する
while j >= 0 && nums[j] > base
nums[j + 1] = nums[j] # nums[j] を 1 つ右へ移動する
j -= 1
end
nums[j + 1] = base # base を正しい位置に配置する
end
end
```
## 11.4.2 &nbsp; アルゴリズムの特性
??? pythontutor "コードの可視化"
- **時間計算量は$O(n^2)$、適応ソート**:最悪の場合、各挿入操作には$n - 1$、$n-2$、...、$2$、$1$のループが必要で、合計は$(n - 1) n / 2$となり、時間計算量は$O(n^2)$です。順序付きデータの場合、挿入操作は早期に終了します。入力配列が完全に順序付けられている場合、挿入ソートは最良時間計算量$O(n)$を実現します。
- **空間計算量は$O(1)$、インプレースソート**:ポインタ$i$と$j$は定数量の追加空間を使用します。
- **安定ソート**:挿入操作中、等しい要素の右側に要素を挿入し、順序を変更しません。
<div style="height: 495px; 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%8C%BF%E5%85%A5%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20%23%20%E5%A4%96%E5%81%B4%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%95%B4%E5%88%97%E6%B8%88%E3%81%BF%E5%8C%BA%E9%96%93%E3%81%AF%20%5B0%2C%20i-1%5D%0A%20%20%20%20for%20i%20in%20range%281%2C%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%81%B4%E3%83%AB%E3%83%BC%E3%83%97%3A%20base%20%E3%82%92%E3%82%BD%E3%83%BC%E3%83%88%E6%B8%88%E3%81%BF%E5%8C%BA%E9%96%93%20%5B0%2C%20i-1%5D%20%E3%81%AE%E6%AD%A3%E3%81%97%E3%81%84%E4%BD%8D%E7%BD%AE%E3%81%AB%E6%8C%BF%E5%85%A5%E3%81%99%E3%82%8B%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%20nums%5Bj%5D%20%E3%82%92%201%20%E3%81%A4%E5%8F%B3%E3%81%B8%E7%A7%BB%E5%8B%95%E3%81%99%E3%82%8B%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%20base%20%E3%82%92%E6%AD%A3%E3%81%97%E3%81%84%E4%BD%8D%E7%BD%AE%E3%81%AB%E9%85%8D%E7%BD%AE%E3%81%99%E3%82%8B%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20insertion_sort%28nums%29%0A%20%20%20%20print%28%22%E6%8C%BF%E5%85%A5%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%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%8C%BF%E5%85%A5%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20%23%20%E5%A4%96%E5%81%B4%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%95%B4%E5%88%97%E6%B8%88%E3%81%BF%E5%8C%BA%E9%96%93%E3%81%AF%20%5B0%2C%20i-1%5D%0A%20%20%20%20for%20i%20in%20range%281%2C%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%81%B4%E3%83%AB%E3%83%BC%E3%83%97%3A%20base%20%E3%82%92%E3%82%BD%E3%83%BC%E3%83%88%E6%B8%88%E3%81%BF%E5%8C%BA%E9%96%93%20%5B0%2C%20i-1%5D%20%E3%81%AE%E6%AD%A3%E3%81%97%E3%81%84%E4%BD%8D%E7%BD%AE%E3%81%AB%E6%8C%BF%E5%85%A5%E3%81%99%E3%82%8B%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%20nums%5Bj%5D%20%E3%82%92%201%20%E3%81%A4%E5%8F%B3%E3%81%B8%E7%A7%BB%E5%8B%95%E3%81%99%E3%82%8B%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%20base%20%E3%82%92%E6%AD%A3%E3%81%97%E3%81%84%E4%BD%8D%E7%BD%AE%E3%81%AB%E9%85%8D%E7%BD%AE%E3%81%99%E3%82%8B%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20insertion_sort%28nums%29%0A%20%20%20%20print%28%22%E6%8C%BF%E5%85%A5%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%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^2)$ であり、これから学ぶクイックソートの時間計算量は $O(n \log n)$ です。挿入ソートの時間計算量のほうが大きいにもかかわらず、**データ量が小さい場合には、挿入ソートのほうが通常は高速**です
この結論は線形探索と二分探索の結論と似ています。時間計算量が$O(n \log n)$分割統治戦略に基づくクイックソートなどのアルゴリズムは、多くの場合より多くの単位操作を含みます。小さな入力サイズでは、$n^2$$n \log n$の数値は近く、計算量が支配的でなく、ラウンドあたりの単位操作数が決定的な役割を果たします。
この結論は線形探索と二分探索の適用条件に関する結論と似ています。クイックソートのような $O(n \log n)$ のアルゴリズムは分割統治に基づくソートアルゴリズムであり、一般により多くの基本演算を含みます。一方、データ量が小さい場合は、$n^2$$n \log n$ の値は比較的近く、計算量が支配的でなくなり、各ラウンドにおける基本演算の回数が決定的な役割を果たします。
実際、多くのプログラミング言語(Javaなど)は、組み込みソート関数で挿入ソートを使用しています。一般的なアプローチは:長い配列に対しては、クイックソートなどの分割統治戦略に基づくソートアルゴリズムを使用し、短い配列に対しては挿入ソートを直接使用します。
実際、多くのプログラミング言語(たとえば Java)の組み込みソート関数で挿入ソートが採用されており、その大まかな考え方は次のとおりです。長い配列にはクイックソートなどの分割統治に基づくソートアルゴリズムを使、短い配列には直接挿入ソートを使います。
バブルソート、選択ソート、挿入ソートはすべて時間計算量$O(n^2)$を持ちますが、実際には、**挿入ソートはバブルソートや選択ソートよりも一般的に使用されます**。主な理由は以下の通りです。
バブルソート、選択ソート、挿入ソートはいずれも時間計算量$O(n^2)$すが、実際には、**挿入ソートはバブルソートや選択ソートよりもはるかに高い頻度で使われます**。主な理由は次のとおりです。
- バブルソートは要素交換に基づき、一時変数の使用が必要で、3つの単位操作を含みます;挿入ソートは要素代入に基づき、1つの単位操作のみが必要です。したがって、**バブルソートの計算オーバーヘッドは一般的に挿入ソートより高くなります**。
- 選択ソートの時間計算量は常に$O(n^2)$です。**部分的に順序付けられたデータのセットが与えられた場合、挿入ソートは通常選択ソートより効率的です**
- 選択ソートは安定で、マルチレベルソートに適用できません。
- バブルソートは要素交換によって実装され、1 つの一時変数を必要とするため、合計で 3 回の基本演算が関わります。これに対して、挿入ソートは要素代入に基づいており、必要な基本演算は 1 回だけです。したがって、**バブルソートの計算コストは通常、挿入ソートより高くなります**。
- 選択ソートの時間計算量はどのような場合でも $O(n^2)$ です。**部分的に整列されたデータが与えられた場合、挿入ソートは通常選択ソートより効率的**です。
- 選択ソートは安定ではないため、多段ソートに適用できません。
+489 -79
View File
@@ -4,26 +4,26 @@ comments: true
# 11.6 &nbsp; マージソート
<u>マージソート</u>は分割統治戦略に基づくソートアルゴリズムで、下図に示す「分割」と「マージ」フェーズを含みます。
<u>マージソートmerge sort</u>は分割統治戦略に基づくソートアルゴリズムであり、以下の図に示す「分割」と「マージ」の段階から構成されます。
1. **分割フェーズ**中点から配列を再帰的に分割し、長い配列のソート問題をより短い配列変換します。
2. **マージフェーズ**サブ配列の長さが1になったときに分割を停止し、その後マージを開始します。2つの短いソート済み配列を連続的により長いソート済み配列にマージし、プロセスが完了するまで続行します。
1. **分割段階**再帰によって配列を中点で繰り返し分割し、長い配列のソート問題を短い配列のソート問題へ変換します。
2. **マージ段階**部分配列の長さが 1 になったら分割を終了し、マージを開始して、左右 2 つの短いソート済み配列をより長いソート済み配列へと繰り返しマージしていきます。
![The divide and merge phases of merge sort](merge_sort.assets/merge_sort_overview.png){ class="animation-figure" }
![マージソートの分割とマージの段階](merge_sort.assets/merge_sort_overview.png){ class="animation-figure" }
<p align="center"> 図 11-10 &nbsp; The divide and merge phases of merge sort </p>
<p align="center"> 図 11-10 &nbsp; マージソートの分割とマージの段階 </p>
## 11.6.1 &nbsp; アルゴリズムワークフロー
## 11.6.1 &nbsp; アルゴリズムの流れ
図に示すように、「分割フェーズ」は中点から配列を上から下に2つのサブ配列に再帰的に分割します。
以下の図に示すように、「分割段階」では配列を上から下へ再帰的に中点で 2 つの部分配列へ分割します。
1. 中点`mid`を計算し、左サブ配列(区間`[left, mid]`)と右サブ配列(区間`[mid + 1, right]`)を再帰的に分割します。
2. サブ配列の長さが1になるまでステップ`1.`を再帰的に行し、その後停止します。
1. 配列の中点 `mid` を計算し、左部分配列(区間 `[left, mid]` )と右部分配列(区間 `[mid + 1, right]` )を再帰的に分割します。
2. 手順 `1.` を再帰的に行し、部分配列区間の長さが 1 になった時点で終了します。
「マージフェーズ」は左と右のサブ配列を下から上にソート済み配列に結合します。重要なのは、マージが長さ1のサブ配列から開始され、マージフェーズ中に各サブ配列がソートされることです。
「マージ段階」では左部分配列と右部分配列を下から上へとマージし、1 つのソート済み配列にします。長さ 1 の部分配列からマージを始めるため、この段階の各部分配列はすでに整列されています。
=== "<1>"
![Merge sort process](merge_sort.assets/merge_sort_step1.png){ class="animation-figure" }
![マージソートの手順](merge_sort.assets/merge_sort_step1.png){ class="animation-figure" }
=== "<2>"
![merge_sort_step2](merge_sort.assets/merge_sort_step2.png){ class="animation-figure" }
@@ -52,26 +52,26 @@ comments: true
=== "<10>"
![merge_sort_step10](merge_sort.assets/merge_sort_step10.png){ class="animation-figure" }
<p align="center"> 図 11-11 &nbsp; Merge sort process </p>
<p align="center"> 図 11-11 &nbsp; マージソートの手順 </p>
マージソートの再帰順序は二分木の後順横断と一致ることが観察できます。
観察すると、マージソートの再帰順序は二分木の後順走査と一致していることがわかります。
- **後順横断**:まず左のサブツリーを再帰的に横断し、次に右のサブツリーを横断し、最後にルートノードを処理します。
- **マージソート**:まず左のサブ配列を再帰的に処理し、次に右のサブ配列を処理し、最後にマージを実行します。
- **後順走査**:まず左部分木を再帰し、次に右部分木を再帰し、最後にノードを処理します。
- **マージソート**:まず左部分配列を再帰し、次に右部分配列を再帰し、最後にマージを処理します。
マージソートの実装以下のコードに示されます。`nums`マージされる区間は`[left, right]``tmp`の対応する区間は`[0, right - left]`であることに注意してください
マージソートの実装以下のコードに示ます。注意として、`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 を作成してマージ結果を格納
"""左部分配列と右部分配列をマージ"""
# 左部分配列区間は [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]
@@ -80,7 +80,7 @@ comments: true
tmp[k] = nums[j]
j += 1
k += 1
# 残った左右サブ配列の要素を一時配列にコピー
# 左右の部分配列の残り要素を一時配列にコピーする
while i <= mid:
tmp[k] = nums[i]
i += 1
@@ -89,7 +89,7 @@ comments: true
tmp[k] = nums[j]
j += 1
k += 1
# 一時配列 tmp の要素を元の配列 nums の対応する区間にコピーバック
# 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする
for k in range(0, len(tmp)):
nums[left + k] = tmp[k]
@@ -97,40 +97,40 @@ comments: true
"""マージソート"""
# 終了条件
if left >= right:
return # サブ配列の長さが1のときに再帰を終了
# 分割段階
mid = left + (right - left) // 2 # 中点を計算
merge_sort(nums, left, mid) # 左サブ配列を再帰的に処理
merge_sort(nums, mid + 1, 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を作成
// 左部分配列の区間は [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の対応する区間にコピー
// 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする
for (k = 0; k < tmp.size(); k++) {
nums[left + k] = tmp[k];
}
@@ -140,12 +140,12 @@ comments: true
void mergeSort(vector<int> &nums, int left, int right) {
// 終了条件
if (left >= right)
return; // サブ配列の長さが1の時、再帰を終了
// 分割段階
return; // 部分配列の長さが 1 になったら再帰を終了
// 分割フェーズ
int mid = left + (right - left) / 2; // 中点を計算
mergeSort(nums, left, mid); // 左サブ配列を再帰的に処理
mergeSort(nums, mid + 1, right); // 右サブ配列を再帰的に処理
// マージ段階
mergeSort(nums, left, mid); // 左部分配列を再帰処理
mergeSort(nums, mid + 1, right); // 右部分配列を再帰処理
// マージフェーズ
merge(nums, left, mid, right);
}
```
@@ -155,26 +155,26 @@ comments: true
```java title="merge_sort.java"
/* 左部分配列と右部分配列をマージ */
void merge(int[] nums, int left, int mid, int right) {
// 左部分配列区間は [left, mid]、右部分配列区間は [mid+1, right]
// 一時配列 tmp を作成してマージ結果を格納
// 左部分配列区間は [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 の対応する区間にコピーバック
// 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする
for (k = 0; k < tmp.length; k++) {
nums[left + k] = tmp[k];
}
@@ -184,12 +184,12 @@ comments: true
void mergeSort(int[] nums, int left, int right) {
// 終了条件
if (left >= right)
return; // 部分配列の長さが 1 のとき再帰を終了
// 分割段階
return; // 部分配列の長さが 1 になったら再帰を終了
// 分割フェーズ
int mid = left + (right - left) / 2; // 中点を計算
mergeSort(nums, left, mid); // 左部分配列を再帰的に処理
mergeSort(nums, mid + 1, right); // 右部分配列を再帰的に処理
// マージ段階
mergeSort(nums, left, mid); // 左部分配列を再帰処理
mergeSort(nums, mid + 1, right); // 右部分配列を再帰処理
// マージフェーズ
merge(nums, left, mid, right);
}
```
@@ -197,94 +197,504 @@ comments: true
=== "C#"
```csharp title="merge_sort.cs"
[class]{merge_sort}-[func]{Merge}
/* 左部分配列と右部分配列をマージ */
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];
}
}
[class]{merge_sort}-[func]{MergeSort}
/* マージソート */
void MergeSort(int[] nums, int left, int right) {
// 終了条件
if (left >= right) return; // 部分配列の長さが 1 になったら再帰を終了
// 分割フェーズ
int mid = left + (right - left) / 2; // 中点を計算
MergeSort(nums, left, mid); // 左部分配列を再帰処理
MergeSort(nums, mid + 1, right); // 右部分配列を再帰処理
// マージフェーズ
Merge(nums, left, mid, right);
}
```
=== "Go"
```go title="merge_sort.go"
[class]{}-[func]{merge}
/* 左部分配列と右部分配列をマージ */
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]
}
}
[class]{}-[func]{mergeSort}
/* マージソート */
func mergeSort(nums []int, left, right int) {
// 終了条件
if left >= right {
return
}
// 分割フェーズ
mid := left + (right - left) / 2
mergeSort(nums, left, mid)
mergeSort(nums, mid+1, right)
// マージフェーズ
merge(nums, left, mid, right)
}
```
=== "Swift"
```swift title="merge_sort.swift"
[class]{}-[func]{merge}
/* 左部分配列と右部分配列をマージ */
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]
}
}
[class]{}-[func]{mergeSort}
/* マージソート */
func mergeSort(nums: inout [Int], left: Int, right: Int) {
// 終了条件
if left >= right { // 部分配列の長さが 1 になったら再帰を終了
return
}
// 分割フェーズ
let mid = left + (right - left) / 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"
[class]{}-[func]{merge}
/* 左部分配列と右部分配列をマージ */
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];
}
}
[class]{}-[func]{mergeSort}
/* マージソート */
function mergeSort(nums, left, right) {
// 終了条件
if (left >= right) return; // 部分配列の長さが 1 になったら再帰を終了
// 分割フェーズ
let mid = Math.floor(left + (right - left) / 2); // 中点を計算
mergeSort(nums, left, mid); // 左部分配列を再帰処理
mergeSort(nums, mid + 1, right); // 右部分配列を再帰処理
// マージフェーズ
merge(nums, left, mid, right);
}
```
=== "TS"
```typescript title="merge_sort.ts"
[class]{}-[func]{merge}
/* 左部分配列と右部分配列をマージ */
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];
}
}
[class]{}-[func]{mergeSort}
/* マージソート */
function mergeSort(nums: number[], left: number, right: number): void {
// 終了条件
if (left >= right) return; // 部分配列の長さが 1 になったら再帰を終了
// 分割フェーズ
let mid = Math.floor(left + (right - left) / 2); // 中点を計算
mergeSort(nums, left, mid); // 左部分配列を再帰処理
mergeSort(nums, mid + 1, right); // 右部分配列を再帰処理
// マージフェーズ
merge(nums, left, mid, right);
}
```
=== "Dart"
```dart title="merge_sort.dart"
[class]{}-[func]{merge}
/* 左部分配列と右部分配列をマージ */
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];
}
}
[class]{}-[func]{mergeSort}
/* マージソート */
void mergeSort(List<int> nums, int left, int right) {
// 終了条件
if (left >= right) return; // 部分配列の長さが 1 になったら再帰を終了
// 分割フェーズ
int mid = left + (right - left) ~/ 2; // 中点を計算
mergeSort(nums, left, mid); // 左部分配列を再帰処理
mergeSort(nums, mid + 1, right); // 右部分配列を再帰処理
// マージフェーズ
merge(nums, left, mid, right);
}
```
=== "Rust"
```rust title="merge_sort.rs"
[class]{}-[func]{merge}
/* 左部分配列と右部分配列をマージ */
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];
}
}
[class]{}-[func]{merge_sort}
/* マージソート */
fn merge_sort(nums: &mut [i32], left: usize, right: usize) {
// 終了条件
if left >= right {
return; // 部分配列の長さが 1 になったら再帰を終了
}
// 分割フェーズ
let mid = left + (right - left) / 2; // 中点を計算
merge_sort(nums, left, mid); // 左部分配列を再帰処理
merge_sort(nums, mid + 1, right); // 右部分配列を再帰処理
// マージフェーズ
merge(nums, left, mid, right);
}
```
=== "C"
```c title="merge_sort.c"
[class]{}-[func]{merge}
/* 左部分配列と右部分配列をマージ */
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);
}
[class]{}-[func]{mergeSort}
/* マージソート */
void mergeSort(int *nums, int left, int right) {
// 終了条件
if (left >= right)
return; // 部分配列の長さが 1 になったら再帰を終了
// 分割フェーズ
int mid = left + (right - left) / 2; // 中点を計算
mergeSort(nums, left, mid); // 左部分配列を再帰処理
mergeSort(nums, mid + 1, right); // 右部分配列を再帰処理
// マージフェーズ
merge(nums, left, mid, right);
}
```
=== "Kotlin"
```kotlin title="merge_sort.kt"
[class]{}-[func]{merge}
/* 左部分配列と右部分配列をマージ */
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]
}
}
[class]{}-[func]{mergeSort}
/* マージソート */
fun mergeSort(nums: IntArray, left: Int, right: Int) {
// 終了条件
if (left >= right) return // 部分配列の長さが 1 になったら再帰を終了
// 分割フェーズ
val mid = left + (right - left) / 2 // 中点を計算
mergeSort(nums, left, mid) // 左部分配列を再帰処理
mergeSort(nums, mid + 1, right) // 右部分配列を再帰処理
// マージフェーズ
merge(nums, left, mid, right)
}
```
=== "Ruby"
```ruby title="merge_sort.rb"
[class]{}-[func]{merge}
### 左部分配列と右部分配列をマージ ###
def merge(nums, left, mid, right)
# 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right]
# マージ結果を格納する一時配列 tmp を作成
tmp = Array.new(right - left + 1, 0)
# 左右の部分配列の開始インデックスを初期化する
i, j, 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
end
k += 1
end
# 左右の部分配列の残り要素を一時配列にコピーする
while i <= mid
tmp[k] = nums[i]
i += 1
k += 1
end
while j <= right
tmp[k] = nums[j]
j += 1
k += 1
end
# 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする
(0...tmp.length).each do |k|
nums[left + k] = tmp[k]
end
end
[class]{}-[func]{merge_sort}
### マージソート ###
def merge_sort(nums, left, right)
# 終了条件
# 部分配列の長さが 1 になったら再帰を終了する
return if left >= right
# 分割フェーズ
mid = left + (right - left) / 2 # 中点を計算
merge_sort(nums, left, mid) # 左部分配列を再帰処理
merge_sort(nums, mid + 1, right) # 右部分配列を再帰処理
# マージフェーズ
merge(nums, left, mid, right)
end
```
??? 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%2C%20left%3A%20int%2C%20mid%3A%20int%2C%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%B7%A6%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%A8%E5%8F%B3%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%82%92%E3%83%9E%E3%83%BC%E3%82%B8%22%22%22%0A%20%20%20%20%23%20%E5%B7%A6%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E5%8C%BA%E9%96%93%E3%81%AF%20%5Bleft%2C%20mid%5D%E3%80%81%E5%8F%B3%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E5%8C%BA%E9%96%93%E3%81%AF%20%5Bmid%2B1%2C%20right%5D%0A%20%20%20%20%23%20%E3%83%9E%E3%83%BC%E3%82%B8%E7%B5%90%E6%9E%9C%E3%82%92%E6%A0%BC%E7%B4%8D%E3%81%99%E3%82%8B%E4%B8%80%E6%99%82%E9%85%8D%E5%88%97%20tmp%20%E3%82%92%E4%BD%9C%E6%88%90%0A%20%20%20%20tmp%20%3D%20%5B0%5D%20%2A%20%28right%20-%20left%20%2B%201%29%0A%20%20%20%20%23%20%E5%B7%A6%E5%8F%B3%E3%81%AE%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E9%96%8B%E5%A7%8B%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20i%2C%20j%2C%20k%20%3D%20left%2C%20mid%20%2B%201%2C%200%0A%20%20%20%20%23%20%E5%B7%A6%E5%8F%B3%E3%81%AE%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AB%E3%81%BE%E3%81%A0%E8%A6%81%E7%B4%A0%E3%81%8C%E3%81%82%E3%82%8B%E9%96%93%E3%81%AF%E6%AF%94%E8%BC%83%E3%81%97%E3%80%81%E5%B0%8F%E3%81%95%E3%81%84%E3%81%BB%E3%81%86%E3%82%92%E4%B8%80%E6%99%82%E9%85%8D%E5%88%97%E3%81%AB%E3%82%B3%E3%83%94%E3%83%BC%E3%81%99%E3%82%8B%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%B7%A6%E5%8F%B3%E3%81%AE%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E6%AE%8B%E3%82%8A%E8%A6%81%E7%B4%A0%E3%82%92%E4%B8%80%E6%99%82%E9%85%8D%E5%88%97%E3%81%AB%E3%82%B3%E3%83%94%E3%83%BC%E3%81%99%E3%82%8B%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%E4%B8%80%E6%99%82%E9%85%8D%E5%88%97%20tmp%20%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E5%85%83%E3%81%AE%E9%85%8D%E5%88%97%20nums%20%E3%81%AE%E5%AF%BE%E5%BF%9C%E5%8C%BA%E9%96%93%E3%81%AB%E3%82%B3%E3%83%94%E3%83%BC%E3%81%99%E3%82%8B%0A%20%20%20%20for%20k%20in%20range%280%2C%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%2C%20left%3A%20int%2C%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E3%83%9E%E3%83%BC%E3%82%B8%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E4%BA%86%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%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E9%95%B7%E3%81%95%E3%81%8C%201%20%E3%81%AB%E3%81%AA%E3%81%A3%E3%81%9F%E3%82%89%E5%86%8D%E5%B8%B0%E3%82%92%E7%B5%82%E4%BA%86%0A%20%20%20%20%23%20%E5%88%86%E5%89%B2%E3%83%95%E3%82%A7%E3%83%BC%E3%82%BA%0A%20%20%20%20mid%20%3D%20%28left%20%2B%20right%29%20%2F%2F%202%20%20%23%20%E4%B8%AD%E7%82%B9%E3%82%92%E8%A8%88%E7%AE%97%0A%20%20%20%20merge_sort%28nums%2C%20left%2C%20mid%29%20%20%23%20%E5%B7%A6%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%82%92%E5%86%8D%E5%B8%B0%E5%87%A6%E7%90%86%0A%20%20%20%20merge_sort%28nums%2C%20mid%20%2B%201%2C%20right%29%20%20%23%20%E5%8F%B3%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%82%92%E5%86%8D%E5%B8%B0%E5%87%A6%E7%90%86%0A%20%20%20%20%23%20%E3%83%9E%E3%83%BC%E3%82%B8%E3%83%95%E3%82%A7%E3%83%BC%E3%82%BA%0A%20%20%20%20merge%28nums%2C%20left%2C%20mid%2C%20right%29%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B7%2C%203%2C%202%2C%206%2C%200%2C%201%2C%205%2C%204%5D%0A%20%20%20%20merge_sort%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E3%83%9E%E3%83%BC%E3%82%B8%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%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%2C%20left%3A%20int%2C%20mid%3A%20int%2C%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%B7%A6%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%A8%E5%8F%B3%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%82%92%E3%83%9E%E3%83%BC%E3%82%B8%22%22%22%0A%20%20%20%20%23%20%E5%B7%A6%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E5%8C%BA%E9%96%93%E3%81%AF%20%5Bleft%2C%20mid%5D%E3%80%81%E5%8F%B3%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E5%8C%BA%E9%96%93%E3%81%AF%20%5Bmid%2B1%2C%20right%5D%0A%20%20%20%20%23%20%E3%83%9E%E3%83%BC%E3%82%B8%E7%B5%90%E6%9E%9C%E3%82%92%E6%A0%BC%E7%B4%8D%E3%81%99%E3%82%8B%E4%B8%80%E6%99%82%E9%85%8D%E5%88%97%20tmp%20%E3%82%92%E4%BD%9C%E6%88%90%0A%20%20%20%20tmp%20%3D%20%5B0%5D%20%2A%20%28right%20-%20left%20%2B%201%29%0A%20%20%20%20%23%20%E5%B7%A6%E5%8F%B3%E3%81%AE%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E9%96%8B%E5%A7%8B%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20i%2C%20j%2C%20k%20%3D%20left%2C%20mid%20%2B%201%2C%200%0A%20%20%20%20%23%20%E5%B7%A6%E5%8F%B3%E3%81%AE%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AB%E3%81%BE%E3%81%A0%E8%A6%81%E7%B4%A0%E3%81%8C%E3%81%82%E3%82%8B%E9%96%93%E3%81%AF%E6%AF%94%E8%BC%83%E3%81%97%E3%80%81%E5%B0%8F%E3%81%95%E3%81%84%E3%81%BB%E3%81%86%E3%82%92%E4%B8%80%E6%99%82%E9%85%8D%E5%88%97%E3%81%AB%E3%82%B3%E3%83%94%E3%83%BC%E3%81%99%E3%82%8B%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%B7%A6%E5%8F%B3%E3%81%AE%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E6%AE%8B%E3%82%8A%E8%A6%81%E7%B4%A0%E3%82%92%E4%B8%80%E6%99%82%E9%85%8D%E5%88%97%E3%81%AB%E3%82%B3%E3%83%94%E3%83%BC%E3%81%99%E3%82%8B%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%E4%B8%80%E6%99%82%E9%85%8D%E5%88%97%20tmp%20%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E5%85%83%E3%81%AE%E9%85%8D%E5%88%97%20nums%20%E3%81%AE%E5%AF%BE%E5%BF%9C%E5%8C%BA%E9%96%93%E3%81%AB%E3%82%B3%E3%83%94%E3%83%BC%E3%81%99%E3%82%8B%0A%20%20%20%20for%20k%20in%20range%280%2C%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%2C%20left%3A%20int%2C%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E3%83%9E%E3%83%BC%E3%82%B8%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E4%BA%86%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%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E9%95%B7%E3%81%95%E3%81%8C%201%20%E3%81%AB%E3%81%AA%E3%81%A3%E3%81%9F%E3%82%89%E5%86%8D%E5%B8%B0%E3%82%92%E7%B5%82%E4%BA%86%0A%20%20%20%20%23%20%E5%88%86%E5%89%B2%E3%83%95%E3%82%A7%E3%83%BC%E3%82%BA%0A%20%20%20%20mid%20%3D%20%28left%20%2B%20right%29%20%2F%2F%202%20%20%23%20%E4%B8%AD%E7%82%B9%E3%82%92%E8%A8%88%E7%AE%97%0A%20%20%20%20merge_sort%28nums%2C%20left%2C%20mid%29%20%20%23%20%E5%B7%A6%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%82%92%E5%86%8D%E5%B8%B0%E5%87%A6%E7%90%86%0A%20%20%20%20merge_sort%28nums%2C%20mid%20%2B%201%2C%20right%29%20%20%23%20%E5%8F%B3%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%82%92%E5%86%8D%E5%B8%B0%E5%87%A6%E7%90%86%0A%20%20%20%20%23%20%E3%83%9E%E3%83%BC%E3%82%B8%E3%83%95%E3%82%A7%E3%83%BC%E3%82%BA%0A%20%20%20%20merge%28nums%2C%20left%2C%20mid%2C%20right%29%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B7%2C%203%2C%202%2C%206%2C%200%2C%201%2C%205%2C%204%5D%0A%20%20%20%20merge_sort%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E3%83%9E%E3%83%BC%E3%82%B8%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%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)$空間を使用します。
- **安定ソート**:マージプロセス中、等しい要素の順序は変更されません。
- **時間計算量は $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)$最適化できます**。
連結リストに対しては、マージソートは他のソートアルゴリズムと比べて顕著な利点があり**連結リストソート問題の空間計算量を $O(1)$ まで最適化できます**
- **分割フェーズ**:「再帰」の代わりに「反復」を使用して連結リスト分割作業を実行できるため、再帰で使用されるスタックフレーム空間を節約できます。
- **マージフェーズ**:連結リストでは、ノードの挿入と削除操作は参照(ポインタ)を変更することで実現できるため、マージフェーズ2つの短い順序付きリストを1つの長い順序付きリストに結合)中に追加のリストを作成する必要がありません。
- **分割段階**:連結リストの分割は「再帰」の代わりに「反復」で実装できるため、再帰で使用るスタックフレーム領域を省けます。
- **マージ段階**:連結リストでは、ノードの追加や削除は参照(ポインタ)を変更するだけで実現できるため、マージ段階2 つの短いソート済み連結リストを 1 つの長いソート済み連結リストにマージすること)では追加の連結リストを作成する必要がありません。
実装の詳細は比較的複雑で、興味のある読者は関連資料を参照して学習してください。
具体的な実装の詳細は比較的複雑なので、興味のある読者は関連資料を参照して学習してください。
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+194 -35
View File
@@ -4,18 +4,18 @@ comments: true
# 11.2 &nbsp; 選択ソート
<u>選択ソート</u>は非常にシンプルな原理で動作します:各反復で未ソート区間から最小要素を選択し、ソート済みセクションの末尾に移動するループを使用します。
<u>選択ソートselection sort</u>の仕組みは非常に単純です。ループを開始し、各ラウンドで未ソート区間から最小要素を選び、整列済み区間の末尾に配置します。
配列の長さを$n$とすると、選択ソートのステップは下図に示されます。
配列の長さを $n$ とすると、選択ソートの手順は次の図のようになります。
1. 最初に、すべての要素未ソートで、つまり未ソート(インデックス)区間は$[0, n-1]$です。
2. 区間$[0, n-1]$の最小要素を選択し、インデックス$0$の要素と交換します。この後、配列の最初の要素がソートされます。
3. 区間$[1, n-1]$の最小要素を選択し、インデックス$1$の要素と交換します。この後、配列の最初の2つの要素がソートされます。
4.の方法で続行します。$n - 1$ラウンドの選択と交換の後、最初の$n - 1$個の要素がソートされます。
5.りの唯一の要素は結果的に最大要素であり、ソートする必要がないため、配列ソートされます。
1. 初期状態では、すべての要素未ソートであり、未ソート(インデックス)区間は $[0, n-1]$ です。
2. 区間 $[0, n-1]$の最小要素を選、インデックス $0$ の要素と交換します。これにより、配列の先頭 1 要素が整列済みになります。
3. 区間 $[1, n-1]$の最小要素を選、インデックス $1$ の要素と交換します。これにより、配列の先頭 2 要素が整列済みになります。
4.れを繰り返します。$n - 1$の選択と交換を経ると、配列の先頭 $n - 1$ 要素が整列済みになります。
5.った 1 つの要素は必ず最大要素なので、ソートは不要です。これで配列ソートは完了します。
=== "<1>"
![Selection sort process](selection_sort.assets/selection_sort_step1.png){ class="animation-figure" }
![選択ソートの手順](selection_sort.assets/selection_sort_step1.png){ class="animation-figure" }
=== "<2>"
![selection_sort_step2](selection_sort.assets/selection_sort_step2.png){ class="animation-figure" }
@@ -47,9 +47,9 @@ comments: true
=== "<11>"
![selection_sort_step11](selection_sort.assets/selection_sort_step11.png){ class="animation-figure" }
<p align="center"> 図 11-2 &nbsp; Selection sort process </p>
<p align="center"> 図 11-2 &nbsp; 選択ソートの手順 </p>
コードでは、$k$を使用して未ソート区間内の最小要素を記録します
コードでは、$k$ を用いて未ソート区間内の最小要素を記録します
=== "Python"
@@ -57,14 +57,14 @@ comments: true
def selection_sort(nums: list[int]):
"""選択ソート"""
n = len(nums)
# 外側ループ:未ソート範囲は [i, n-1]
# 外側ループ:未整列区間は [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]
```
@@ -74,15 +74,15 @@ comments: true
/* 選択ソート */
void selectionSort(vector<int> &nums) {
int n = nums.size();
// 外側ループ:未ソート範囲は[i, n-1]
// 外側ループ:未整列区間は [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]);
}
}
@@ -94,15 +94,15 @@ comments: true
/* 選択ソート */
void selectionSort(int[] nums) {
int n = nums.length;
// 外側ループ: 未ソート範囲は [i, n-1]
// 外側ループ:未整列区間は [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;
@@ -113,69 +113,228 @@ comments: true
=== "C#"
```csharp title="selection_sort.cs"
[class]{selection_sort}-[func]{SelectionSort}
/* 選択ソート */
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"
[class]{}-[func]{selectionSort}
/* 選択ソート */
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"
[class]{}-[func]{selectionSort}
/* 選択ソート */
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"
[class]{}-[func]{selectionSort}
/* 選択ソート */
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"
[class]{}-[func]{selectionSort}
/* 選択ソート */
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"
[class]{}-[func]{selectionSort}
/* 選択ソート */
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"
[class]{}-[func]{selection_sort}
/* 選択ソート */
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"
[class]{}-[func]{selectionSort}
/* 選択ソート */
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"
[class]{}-[func]{selectionSort}
/* 選択ソート */
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 // 最小要素のインデックスを記録
}
// その最小要素を未整列区間の先頭要素と交換する
val temp = nums[i]
nums[i] = nums[k]
nums[k] = temp
}
}
```
=== "Ruby"
```ruby title="selection_sort.rb"
[class]{}-[func]{selection_sort}
### 選択ソート ###
def selection_sort(nums)
n = nums.length
# 外側ループ:未整列区間は [i, n-1]
for i in 0...(n - 1)
# 内側のループ:未ソート区間の最小要素を見つける
k = i
for j in (i + 1)...n
if nums[j] < nums[k]
k = j # 最小要素のインデックスを記録
end
end
# その最小要素を未整列区間の先頭要素と交換する
nums[i], nums[k] = nums[k], nums[i]
end
end
```
## 11.2.1 &nbsp; アルゴリズムの特性
??? pythontutor "コードの可視化"
- **$O(n^2)$の時間計算量、非適応ソート**:外側ループに$n - 1$回の反復があり、未ソートセクションの長さは最初の反復で$n$から始まり、最後の反復で$2$まで減少します。つまり、各外側ループ反復にはそれぞれ$n$、$n - 1$、$\dots$、$3$、$2$回の内側ループ反復が含まれ、合計は$\frac{(n - 1)(n + 2)}{2}$となります。
- **$O(1)$の空間計算量、インプレースソート**:ポインタ$i$と$j$で定数の追加空間を使用します。
- **非安定ソート**:下図に示すように、要素`nums[i]`は等しい要素の右側に交換される可能性があり、相対順序が変わる原因となります。
<div style="height: 513px; 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%81%B8%E6%8A%9E%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E5%81%B4%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%9C%AA%E6%95%B4%E5%88%97%E5%8C%BA%E9%96%93%E3%81%AF%20%5Bi%2C%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%81%B4%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%9C%AA%E3%82%BD%E3%83%BC%E3%83%88%E5%8C%BA%E9%96%93%E3%81%AE%E6%9C%80%E5%B0%8F%E8%A6%81%E7%B4%A0%E3%82%92%E8%A6%8B%E3%81%A4%E3%81%91%E3%82%8B%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%2C%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%E6%9C%80%E5%B0%8F%E8%A6%81%E7%B4%A0%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20%20%20%20%20%23%20%E3%81%9D%E3%81%AE%E6%9C%80%E5%B0%8F%E8%A6%81%E7%B4%A0%E3%82%92%E6%9C%AA%E6%95%B4%E5%88%97%E5%8C%BA%E9%96%93%E3%81%AE%E5%85%88%E9%A0%AD%E8%A6%81%E7%B4%A0%E3%81%A8%E4%BA%A4%E6%8F%9B%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bk%5D%20%3D%20nums%5Bk%5D%2C%20nums%5Bi%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20selection_sort%28nums%29%0A%20%20%20%20print%28%22%E9%81%B8%E6%8A%9E%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%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%81%B8%E6%8A%9E%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E5%81%B4%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%9C%AA%E6%95%B4%E5%88%97%E5%8C%BA%E9%96%93%E3%81%AF%20%5Bi%2C%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%81%B4%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%9C%AA%E3%82%BD%E3%83%BC%E3%83%88%E5%8C%BA%E9%96%93%E3%81%AE%E6%9C%80%E5%B0%8F%E8%A6%81%E7%B4%A0%E3%82%92%E8%A6%8B%E3%81%A4%E3%81%91%E3%82%8B%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%2C%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%E6%9C%80%E5%B0%8F%E8%A6%81%E7%B4%A0%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20%20%20%20%20%23%20%E3%81%9D%E3%81%AE%E6%9C%80%E5%B0%8F%E8%A6%81%E7%B4%A0%E3%82%92%E6%9C%AA%E6%95%B4%E5%88%97%E5%8C%BA%E9%96%93%E3%81%AE%E5%85%88%E9%A0%AD%E8%A6%81%E7%B4%A0%E3%81%A8%E4%BA%A4%E6%8F%9B%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bk%5D%20%3D%20nums%5Bk%5D%2C%20nums%5Bi%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20selection_sort%28nums%29%0A%20%20%20%20print%28%22%E9%81%B8%E6%8A%9E%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%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>
![Selection sort instability example](selection_sort.assets/selection_sort_instability.png){ class="animation-figure" }
## 11.2.1 &nbsp; アルゴリズムの特徴
<p align="center"> 図 11-3 &nbsp; Selection sort instability example </p>
- **時間計算量は $O(n^2)$、非適応ソート**:外側のループは合計 $n - 1$ 回です。最初のラウンドの未ソート区間の長さは $n$、最後のラウンドでは $2$ であり、各ラウンドの内側のループ回数はそれぞれ $n$、$n - 1$、$\dots$、$3$、$2$ となります。総和は $\frac{(n - 1)(n + 2)}{2}$ です。
- **空間計算量は $O(1)$、インプレースソート**:ポインタ $i$ と $j$ は定数サイズの追加領域しか使用しません。
- **不安定ソート**:次の図のように、要素 `nums[i]` がそれと等しい要素の右側へ交換され、両者の相対的な順序が変わる可能性があります。
![選択ソートの不安定な例](selection_sort.assets/selection_sort_instability.png){ class="animation-figure" }
<p align="center"> 図 11-3 &nbsp; 選択ソートの不安定な例 </p>
+18 -18
View File
@@ -4,36 +4,36 @@ comments: true
# 11.1 &nbsp; ソートアルゴリズム
<u>ソートアルゴリズム</u>は、データセットを特定の順序で配列するために使用されます。ソートアルゴリズムは、順序付けられたデータは通常、より効率的に索、分析、処理できるため、幅広い応用があります。
<u>ソートアルゴリズムsorting algorithm</u>は、データの集合を特定の順序に従って並べ替えるために用いられます。ソートアルゴリズムは幅広く応用されており、整列済みデータは通常、より効率的に索、分析、処理できるためす。
下図に示すように、ソートアルゴリズムデータ型は整数、浮動小数点数、文字、文字列などです。ソート基準は、数値サイズ、文字ASCII順、またはカスタム基準など、要に応じて設定できます。
下図に示すように、ソートアルゴリズムにおけるデータ型は整数、浮動小数点数、文字、文字列などです。ソートの判定規則は、数値の大小、文字ASCII コード順、またはカスタムルールなど、要に応じて設定できます。
![Data types and comparator examples](sorting_algorithm.assets/sorting_examples.png){ class="animation-figure" }
![データ型と判定規則の例](sorting_algorithm.assets/sorting_examples.png){ class="animation-figure" }
<p align="center"> 図 11-1 &nbsp; Data types and comparator examples </p>
<p align="center"> 図 11-1 &nbsp; データ型と判定規則の例 </p>
## 11.1.1 &nbsp; 評価次元
## 11.1.1 &nbsp; 評価
**実行効率**:ソートアルゴリズムの時間計算量できるだけ低いことを期待し、全体的な操作数も少ないこと(時間計算量の定数項を下げる)を望みます。大量データでは、実行効率が特に重要です。
**実行効率**:ソートアルゴリズムの時間計算量できるだけ低く、かつ全体の操作数も少ないこと(時間計算量における定数項が小さいこと)が望まれます。大量データの場合、実行効率はとりわけ重要です。
**インプレース性**名前が示すとおり、<u>インプレースソート</u>は元の配列を直接操作することで実現され、追加のヘルパー配列が不要であるため、メモリを節約ます。一般的に、インプレースソートはデータ移動操作が少なく、高速です。
**インプレース性**その名のとおり、<u>インプレースソート</u>は元の配列を直接操作して並べ替えを行うため、追加の補助配列を必要とせず、メモリを節約できます。通常、インプレースソートはデータ移動操作が少なく、実行速度もより高速です。
**安定性**<u>安定ソート</u>は、ソート後に配列内の等しい要素の相対順序が変わらないことを保証します
**安定性**<u>安定ソート</u>は、並べ替え完了後も、等しい要素の配列内での相対順序が変化しません
安定ソートは、マルチキーソートシナリオにおいて必要条件です。学生情報を格納するテーブルがあり、第1列と第2列がそれぞれ名と年齢であるとします。この場合、<u>不安定ソート</u>入力データの順序を失う可能性があります
安定ソートは多段ソートの場面で必要条件となります。学生情報を保存した表があり、第 1 列と第 2 列がそれぞれ名と年齢であると仮定します。この場合、<u>不安定ソート</u>によって入力データの順序性が失われる可能性があります
```shell
# 入力データは名前でソート済み
# (名前, 年齢)
# 入力データは氏名順にソートされている
# (name, age)
('A', 19)
('B', 18)
('C', 21)
('D', 19)
('E', 23)
# 不安定ソートアルゴリズムを使用してリストを年齢でソートすると仮定すると、
# 結果('D', 19)('A', 19)の相対位置を変更し
# 入力データが名前でソート済みであるという性質が失われる
# 不安定ソートアルゴリズムで年齢順にリストを並べ替えると仮定すると、
# 結果では ('D', 19)('A', 19) の相対位置が変わり
# 入力データが氏名順である性質が失われる
('B', 18)
('D', 19)
('A', 19)
@@ -41,12 +41,12 @@ comments: true
('E', 23)
```
**適応性**<u>適応ソート</u>は入力データ内の既存の順序情報を用して計算負荷を削減し、より最適な時間効率を実現ます。適応ソートアルゴリズムの最良ケース時間計算量は、通常平均ケース時間計算量より優れています。
**適応性**<u>適応ソート</u>は入力データに既に存在する順序情報を用して計算量を減らし、より優れた時間効率を実現できます。適応ソートアルゴリズムの最良時間計算量は、通常平均時間計算量より優れています。
**比較ベースまたは非比較ベース**<u>比較ベースソート</u>は比較演算子($<$、$=$、$>$)に依存して要素の相対順序を定し、配列全体をソートします。理論的最適時間計算量は$O(n \log n)$です。一方、<u>非比較ソート</u>は比較演算子を使用せず、$O(n)$の時間計算量を実現できますが、汎用性は比較的劣ります。
**比較ベースかどうか**<u>比較ベースソート</u>は比較演算子($<$、$=$、$>$)に依存して要素の相対順序を定し、それによって配列全体をソートします。理論上の最良時間計算量は $O(n \log n)$ です。一方、<u>非比較ソート</u>は比較演算子を使用せず、時間計算量は $O(n)$ に達しますが、汎用性は相対的に低くなります。
## 11.1.2 &nbsp; 理想的なソートアルゴリズム
**高速実行、インプレース、安定、適応汎用**。明らかに、これらのすべての特徴を組み合わせたソートアルゴリズムは今日まで見つかっていません。したがって、ソートアルゴリズムを選択する際は、データの特定の特徴と問題の要件に基づいて決定する必要があります。
**高速、インプレース、安定、適応的、高い汎用**。明らかに、これまでのところ、以上のすべての特性を兼ね備えたソートアルゴリズムはまだ見つかっていません。そのため、ソートアルゴリズムを選択する際は、具体的なデータの特徴と問題の要件に応じて判断する必要があります。
次に、さまざまなソートアルゴリズムを一緒に学び、上記の評価次元に基づいてそれぞれの利点と欠点を分析します。
次に、さまざまなソートアルゴリズムを一緒に学び、上記の評価に基づいて各ソートアルゴリズムの長所と短所を分析していきます。
+25 -25
View File
@@ -4,17 +4,17 @@ comments: true
# 11.11 &nbsp; まとめ
### 1. &nbsp; 重要な復習
### 1. &nbsp; 重要なポイントの振り返り
- バブルソートは隣接する要素を交換することで動作します。フラグを追加して早期リターンを可能にすることで、バブルソートの最良ケースの時間計算量を $O(n)$ に最適化できます。
- 挿入ソートは、未ソート区間から要素を取り出してソート済み区間の正しい位置に挿入することで各ラウンドをソートします。挿入ソートの時間計算量は $O(n^2)$ ですが、単位あたりの操作が比較的少ないため、少量のデータのソートで非常に人気があります。
- クイックソートは歩哨分割操作に基づいています。歩哨分割では、常に最悪のピボットを選ぶ可能性があり、時間計算量 $O(n^2)$ 劣化する可能性があります。中央値やランダムピボットを導入することで、そのような劣化の確率を減らすことができます。末尾再帰は再帰の深さを効果的に減らし、空間計算量を $O(\log n)$ に最適化ます。
- マージソートは分割とマージの2つの段階があり、通常分割統治戦略を体現しています。マージソートでは配列のソートには補助配列の作成が必要で、空間計算量は $O(n)$ になります。しかし、リストのソートの空間計算量は $O(1)$ 最適化できます。
- バケットソートは3つの手順から構成されます:データバケットに分散、各バケット内でのソート、バケット順での結果のマージ。これも分割統治戦略を体現し、非常に大きなデータセットに適しています。バケットソートの鍵はデータの均等分散です。
- 計数ソートはバケットソートの変形で、各データポイントの出現回数をカウントすることでソートします。計数ソートは限られた範囲のデータを持つ大きなデータセットに適しており、データを正の整数に変換する必要があります。
- 基数ソートは桁ごとにソートすることでデータを処理し、データが固定の数値として表現される必要があります。
- 全体的に、私たちは高効率、安定、インプレース操作、適応性を持つソートアルゴリズムを求めています。しかし、のデータ構造やアルゴリズムと同様に、これらすべての条件を同時に満たソートアルゴリズムは存在しません。実際の応用では、データの特性に基づいて適切なソートアルゴリズムを選択する必要があります。
- 以下の図は、効率、安定性、インプレース性、適応性の観点から主流のソートアルゴリズムを比較しています。
- バブルソートは隣接する要素を交換することで整列を行います。フラグを追加して早期リターンを可能にする、バブルソートの最良時間計算量を $O(n)$ に最適化できます。
- 挿入ソートは各ラウンドで未整列区間の要素を整列済み区間の正しい位置に挿入することで整列を完了します。挿入ソートの時間計算量は $O(n^2)$ ですが、基本操作が比較的少ないため、小規模データのソート処理で非常に人気があります。
- クイックソートは番兵分割操作に基づいて整列を行います。番兵分割では毎回最悪の基準値を選んでしまう可能性があり、その結果、時間計算量 $O(n^2)$ まで劣化することがあります。中央値の基準値やランダムな基準値を導入すると、この劣化の確率を下げられます。短い部分配列を優先して再帰すれば、再帰の深さを効果的に抑え、空間計算量を $O(\log n)$ に最適化できます。
- マージソートは分割とマージという 2 つの段階からなり、分割統治戦略を典型的に体現しています。マージソートでは配列を整列する際に補助配列の作成が必要で、空間計算量は $O(n)$ です。一方、連結リストを整列する場合の空間計算量は $O(1)$ まで最適化できます。
- バケットソートはデータバケット分配、バケット内ソート、結果の結合という 3 つの手順を含みます。これも分割統治戦略を体現しており、データ量が非常に大きい場合に適しています。バケットソートの鍵はデータを平均的に分配することにあります。
- カウントソートはバケットソートの特例であり、データの出現回数を数えることで整列を行います。カウントソートはデータ量が大きく、かつデータ範囲が限られている場合に適しており、データを正の整数に変換できることが前提です。
- 基数ソートは桁ごとの整列によってデータを整列し、データが固定桁数の数値として表せることを前提とします。
- 総じて言えば、私たちは高効率、安定、インプレースで、さらに適応的であるといった利点を備えたソートアルゴリズムを見つけたいと考えます。しかし、ほかのデータ構造やアルゴリズムと同様に、これらすべての条件を同時に満たせるソートアルゴリズムは存在しません。実際の応用では、データの特性に応じて適切なソートアルゴリズムを選必要があります。
- 下図では、主流のソートアルゴリズムについて、効率、安定性、インプレース性、適応性などを比較しています。
![ソートアルゴリズムの比較](summary.assets/sorting_algorithms_comparison.png){ class="animation-figure" }
@@ -22,32 +22,32 @@ comments: true
### 2. &nbsp; Q & A
**Q**: ソートアルゴリズムの安定性はいつ必要ですか?
**Q**ソートアルゴリズムの安定性は、どのような場合に必須ですか?
には、オブジェクトの一つの属性に基づいてソートする場合があります。えば、学生は名前と身長の属性を持ち、多段階ソートを実装することを目指します:最初に名前で `(A, 180) (B, 185) (C, 170) (D, 170)`取得し、次に身長で。ソートアルゴリズムが不安定なため、`(D, 170) (C, 170) (A, 180) (B, 185)` になってしまう可能性があります。
実には、オブジェクトのある属性に基づいて整列することがあります。たとえば、学生には氏名と身長という 2 つの属性があり、多段階ソートを行いたいとします。まず氏名で整列して `(A, 180) (B, 185) (C, 170) (D, 170)`得て、その後に身長で整列します。ソートアルゴリズムが不安定である場合、結果は `(D, 170) (C, 170) (A, 180) (B, 185)` にな可能性があります。
学生DとCの位置が交換され、名前の順序性が破られているのが分かります。これは望ましくありません。
このように、学生 D と C の位置が入れ替わり、氏名に関する順序性が壊れてしまいます。これは望ましくありません。
**Q**: 歩哨分割での「右から左への検索」と「左から右への検索」の順序を交換できますか?
**Q**:番兵分割において、「右から左へ探索する」順序と「左から右へ探索する」順序は入れ替えられますか?
いいえ、最左要素をピボットとして使用する場合、最初に「右から左への検索」を行い、に「左から右への検索」を行う必要があります。この結論はやや直に反するので、理由を分析してみましょう。
できません。最も左端の要素を基準値とする場合は、必ず先に「右から左へ探索する」を行い、その後に「左から右へ探索する」を行う必要があります。この結論はやや直に反するので、理由を分析してみましょう。
歩哨分割 `partition()` の最後のステップは `nums[left]``nums[i]` を交換することです。交換後、ピボットの左側の要素はすべてピボット以下になります。**これには最後の交換前に `nums[left] >= nums[i]` が成り立つ必要があります**。「左から右への検索」を最初に行い、ピボットより大きい要素が見つからない場合、**`i == j` でループを終了し、`nums[j] == nums[i] > nums[left]` となる可能性があります**。つまり、最終交換操作はピボットより大きい要素配列の左端に交換し、歩哨分割失敗させます。
番兵分割 `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]` なり、これは正しくありません
たとえば、配列 `[0, 0, 0, 0, 1]` が与えられたとき、先に「左から右へ探索する」を行うと、番兵分割後の配列は `[1, 0, 0, 0, 0]` なります。これは誤った結果です
さらに考えると、`nums[right]`ピボットとして選択する場合、まったく逆で、最初に「左から右への検索」を行う必要があります。
さらに考えると、`nums[right]`基準値に選ぶ場合はちょうど逆になり、必ず先に「左から右へ探索する」を行う必要があります。
**Q**: 末尾再帰最適化について、短い配列を選択することで再帰の深さが $\log n$ を超えないことを保証するのはなぜですか?
**Q**:クイックソートの再帰深度最適化について、短い配列を選ぶとなぜ再帰深度が $\log n$ を超えないと保証できるのですか?
再帰の深さは現在リターンしていない再帰メソッドの数です。歩哨分割の各ラウンドは元の配列を2つの副配列に分割します。末尾再帰最適化により、再帰的に続行する副配列の長さは最大でも元の配列長の半分です。最悪の場合常に長さを半分にすると仮定すると、最終的な再帰の深さは $\log n$ になります。
再帰深度とは、現在まだ戻っていない再帰呼び出しの数のことです。各ラウンドの番兵分割では、元の配列を 2 つの部分配列に分ます。再帰深度の最適化後は、下方向に再帰する部分配列の長さは最大でも元の配列長の半分です。最悪の場合でも毎回半分の長さになると仮定すれば、最終的な再帰深度は $\log n$ になります。
元のクイックソートを見直すと、より大きな配列を継続的に再帰処理する可能性があり、最悪の場合 $n$、$n - 1$、...、$2$、$1$ で、再帰の深さは $n$ になります。末尾再帰最適化はこのシナリオを回避できます。
元のクイックソートを振り返ると、長いほうの配列に対して連続して再帰してしまう可能性があり、最悪の場合 $n$、$n - 1$、$\dots$、$2$、$1$ と続き、再帰深度は $n$ になります。再帰深度の最適化により、このような状況を避けられます。
**Q**: 配列のすべての要素が等しい場合、クイックソートの時間計算量は $O(n^2)$ すか?この劣化ケースをどう処理すべきですか?
**Q**配列のすべての要素が等しい場合、クイックソートの時間計算量は $O(n^2)$ になりますか?このような退化はどう処理すべきですか?
はい。この状況については、歩哨分割を使用して配列をピボットより小さい、等しい、大きいの3つの部分に分割することを検討してください。小さい部分と大きい部分のみを再帰的に進めます。この方法では、すべての入力要素が等しい配列を1ラウンドの歩哨分割だけでソートできます。
はい。この場合は、番兵分割によって配列を「基準値より小さい」「基準値に等しい」「基準値より大きい」の 3 つの部分に分ける方法を検討できます。下方向に再帰するのは、小さい部分と大きい部分だけです。この方法では、入力要素がすべて等しい配列は、1 回の番兵分割だけで整列を完了できます。
**Q**: なぜバケットソートの最悪ケース時間計算量 $O(n^2)$ ですか?
**Q**バケットソートの最悪時間計算量 $O(n^2)$ なのはなぜですか?
最悪の場合、すべての要素が同じバケットに配置されます。これらの要素をソートするために $O(n^2)$ アルゴリズムを使用する場合、時間計算量は $O(n^2)$ になります。
最悪の場合、すべての要素が同じバケットに振り分けられます。その要素群を整列するのに $O(n^2)$ アルゴリズムを使えば、時間計算量は $O(n^2)$ になります。