前回、「挿入ソート問題をC言語のコードで確認してみよう!」でトレース問題の解き方を確認した。今回は挿入ソートをGo・Kotlinにも移植し、3言語で比べてみる。
これまでのバブルソート・選択ソートと同様、ロジックそのものは3言語で共通しつつ、ループの表現方法にどんな違いが出るかを見ていく。掲載しているコードはすべてそのままコピーして実行できる。
C言語版(おさらい)
#include <stdio.h>
#define SIZE 6
void print_array(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
void insertion_sort(int arr[], int size) {
for (int i = 1; i < size; i++) {
int key = arr[i];
int j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
printf("%d回目のパス後: ", i);
print_array(arr, size);
}
}
int main(void) {
int data[SIZE] = {5, 2, 8, 1, 9, 3};
printf("ソート前: ");
print_array(data, SIZE);
insertion_sort(data, SIZE);
printf("ソート後: ");
print_array(data, SIZE);
return 0;
}
実行方法
gcc -Wall -Wextra -o insertion_sort insertion_sort.c
./insertion_sortGo版
package main
import "fmt"
func insertionSort(arr []int) {
for i := 1; i < len(arr); i++ {
key := arr[i]
j := i - 1
for j >= 0 && arr[j] > key {
arr[j+1] = arr[j]
j--
}
arr[j+1] = key
fmt.Printf("%d回目のパス後: %v\n", i, arr)
}
}
func main() {
data := []int{5, 2, 8, 1, 9, 3}
fmt.Println("ソート前:", data)
insertionSort(data)
fmt.Println("ソート後:", data)
}実行方法
go run insertion_sort.go
Kotlin版
fun insertionSort(arr: IntArray) {
for (i in 1 until arr.size) {
val key = arr[i]
var j = i - 1
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j]
j--
}
arr[j + 1] = key
println("${i}回目のパス後: ${arr.joinToString(" ")}")
}
}
fun main() {
val data = intArrayOf(5, 2, 8, 1, 9, 3)
println("ソート前: ${data.joinToString(" ")}")
insertionSort(data)
println("ソート後: ${data.joinToString(" ")}")
}実行方法
kotlinc InsertionSort.kt -include-runtime -d insertion_sort.jar
java -jar insertion_sort.jar実行結果
出力の書式は言語によって少し異なる(Goは[ ]で囲んで表示するなど)が、パスごとの経過・最終的なソート結果はもちろん同じになる。
C言語版
ソート前: 5 2 8 1 9 3
1回目のパス後: 2 5 8 1 9 3
2回目のパス後: 2 5 8 1 9 3
3回目のパス後: 1 2 5 8 9 3
4回目のパス後: 1 2 5 8 9 3
5回目のパス後: 1 2 3 5 8 9
ソート後: 1 2 3 5 8 9Go版
ソート前: [5 2 8 1 9 3]
1回目のパス後: [2 5 8 1 9 3]
2回目のパス後: [2 5 8 1 9 3]
3回目のパス後: [1 2 5 8 9 3]
4回目のパス後: [1 2 5 8 9 3]
5回目のパス後: [1 2 3 5 8 9]
ソート後: [1 2 3 5 8 9]Kotlin版
ソート前: 5 2 8 1 9 3
1回目のパス後: 2 5 8 1 9 3
2回目のパス後: 2 5 8 1 9 3
3回目のパス後: 1 2 5 8 9 3
4回目のパス後: 1 2 5 8 9 3
5回目のパス後: 1 2 3 5 8 9
ソート後: 1 2 3 5 8 93言語を比べてみる
key退避 → シフト → 挿入という3ステップの流れは、3言語ともまったく同じ順序で書かれている。挿入ソートは選択ソートと違って交換処理がないため、言語機能による書き方の差がほとんど出ない、というのがまず見えてくる特徴である。
| while相当の書き方 | 特徴 | |
|---|---|---|
| C | while (j >= 0 && arr[j] > key) | そのままwhile文 |
| Go | for j >= 0 && arr[j] > key | Goにwhile文はなく、条件だけのfor文で代用する |
| Kotlin | while (j >= 0 && arr[j] > key) | Cと同じくwhile文がある |
ここが選択ソート編との違いである。選択ソートでは「交換の書き方」に言語差が出たが、挿入ソートではループ文そのものの語彙に差が出る。Goにはwhile文という予約語自体が存在せず、forの条件式だけを書くことで同じ動きを表現する。C・Kotlinの経験があると、最初は「while はどこ?」と探してしまうポイントである。
もう一つの共通点として、keyという一時変数を使ってシフト前の値を退避する、という発想は3言語とも変わらない。この「退避してから走査する」という考え方自体が挿入ソートの核であり、言語を変えても崩れない部分である。
FE試験のポイント
- 試験の擬似言語ではwhile文に相当する繰り返し構文がそのまま出題される。Goのように「forでwhileを表現する」書き方は擬似言語には登場しないため、C・Kotlinの書き方の方が擬似言語との対応がとりやすい
- key(退避用変数)の役割を問う設問では、「ループ中に配列のどこにも書き戻されていない」という性質が問われやすい。この点は言語が変わっても共通の急所である
まとめ
- 挿入ソートはkey退避・シフト・挿入という3ステップの流れが3言語で完全に一致している
- 選択ソートと違い、交換処理がないぶん言語機能による書き方の差は小さい
- 唯一の大きな違いはwhile文の有無で、Goだけがforの条件式で代用する
これでバブル・選択・挿入という3つのソートについて、C・Go・Kotlinそれぞれの実装とトレース問題対応、そして3言語比較まで一区切りとなる。次回以降は、この3つのソートをまとめて振り返る記事や、より発展的なソートアルゴリズム(マージソート・クイックソートなど)を取り上げる予定である。
この記事で扱ったコードは、GitHubの c-language-studies リポジトリ (FE-algorithm/04_insertion_sort/)で公開している。


コメント