Juliaプログラミング:挿入ソート(insorted)の基本と応用、エラー解決まで

2025-05-27

挿入ソート(Insertion Sort)とは?

まず、挿入ソートについて簡単に説明します。挿入ソートは、配列(またはベクトル)をソートするためのアルゴリズムの一つです。

  • これを繰り返すことで、最終的に配列全体がソートされます。
  • 各要素を、すでにソート済みの部分配列の適切な位置に挿入します。
  • 配列の要素を一つずつ順番に見ていきます。

Juliaでのsort(v, alg=InsertionSort)insorted(v)

Juliaでは、挿入ソートを使って配列をソートするために、主に二つの方法があります。

    • sort()関数は、配列vをソートするための汎用的な関数です。
    • alg=InsertionSortという引数を指定することで、挿入ソートアルゴリズムを使用するように指示します。
    • この関数は、元の配列vを変更せずに、ソートされた新しい配列を返します。
    • 例:
      v = [5, 2, 8, 1, 9]
      sorted_v = sort(v, alg=InsertionSort)
      println(sorted_v) # 結果:[1, 2, 5, 8, 9]
      println(v) #結果:[5, 2, 8, 1, 9] 元の配列は変更されない
      
  1. insorted(v)

    • insorted()関数は、配列vがすでにソートされているかどうかを判定せずに、挿入ソートを実行します。
    • この関数は、元の配列vを変更せずに、ソートされた新しい配列を返します。
    • sort(v, alg=InsertionSort)と全く同じ結果を返します。
    • 例:
      v = [5, 2, 8, 1, 9]
      sorted_v = insorted(v)
      println(sorted_v) # 結果:[1, 2, 5, 8, 9]
      println(v) #結果:[5, 2, 8, 1, 9] 元の配列は変更されない
      
  • 挿入ソートは、小さな配列やほぼソート済みの配列に対して効率が良いですが、大きな配列に対しては効率が悪いです。
  • どちらを使っても結果は同じです。
  • どちらの関数も、元の配列を変更せずに、ソートされた新しい配列を返します。
  • sort(v, alg=InsertionSort)insorted(v)は、どちらも挿入ソートを使って配列をソートします。


  1. 要素の型が比較できない場合 (TypeError: < not defined for ...)

    • エラー
      sort()またはinsorted()は、配列の要素を比較するために<演算子を使用します。要素の型が<演算子で比較できない場合、TypeErrorが発生します。
    • 原因
      例えば、配列に文字列と数値が混在している場合や、ユーザー定義の型で<演算子が定義されていない場合に発生します。
    • 対処法
      • 配列の要素の型を統一します。
      • ユーザー定義の型を使用する場合は、<演算子を適切に定義します。
      • sort()関数のlt引数を使用して、カスタムの比較関数を提供します。
      • 例:
        v = [5, "2", 8] # 文字列と数値が混在
        #エラーが発生する
        #sorted_v = sort(v, alg=InsertionSort)
        
        v2 = [5, 2, 8] # 数値のみ
        sorted_v2 = sort(v2, alg=InsertionSort) # 正しい
        println(sorted_v2)
        
        v3 = ["5","2","8"] #文字列のみ
        sorted_v3 = sort(v3, alg=InsertionSort) #正しい
        println(sorted_v3)
        
  2. 配列が大きすぎる場合 (パフォーマンスの問題)

    • 問題
      挿入ソートは、小さな配列やほぼソート済みの配列に対しては効率的ですが、大きな配列に対しては効率が悪いです。大きな配列を挿入ソートでソートすると、時間がかかりすぎることがあります。
    • 原因
      挿入ソートの平均時間計算量はO(n^2)です。
    • 対処法
      • より効率的なソートアルゴリズム(例えば、sort()のデフォルトであるクイックソートやマージソート)を使用します。
      • 配列のサイズを小さくできる場合は、小さくします。
      • 例:
        v = rand(10000) # 大きな配列
        #挿入ソートは時間がかかる
        #sorted_v = sort(v, alg=InsertionSort)
        
        #より効率的なソートアルゴリズムを使う
        sorted_v2 = sort(v) # デフォルトのクイックソート
        
  3. 配列が変更されない (新しい配列が返される)

    • 問題
      sort()またはinsorted()は、元の配列を変更せずに、ソートされた新しい配列を返します。元の配列が変更されないことに注意する必要があります。
    • 原因
      sort()insorted()は、非破壊的な関数です。
    • 対処法
      • ソートされた新しい配列を変数に代入して使用します。
      • 元の配列を直接変更したい場合は、sort!()関数を使用します。
      • 例:
        v = [5, 2, 8, 1, 9]
        sorted_v = sort(v, alg=InsertionSort)
        println(v) # 元の配列は変更されない
        println(sorted_v) # ソートされた新しい配列
        
        v2 = [5, 2, 8, 1, 9]
        sort!(v2, alg=InsertionSort) # 元の配列を直接変更
        println(v2)
        
  4. 予期しないソート結果 (カスタム比較関数)

    • 問題
      sort()関数のlt引数を使用してカスタム比較関数を提供した場合、予期しないソート結果になることがあります。
    • 原因
      カスタム比較関数が正しく定義されていない場合や、意図しない比較を行っている場合に発生します。
    • 対処法
      • カスタム比較関数が正しい比較を行っているか確認します。
      • 比較関数が推移律を満たしているか確認します。(a < b かつ b < c ならば a < c)
      • 例:
        v = [(1, "b"), (2, "a"), (1, "a")]
        sorted_v = sort(v, lt = (x, y) -> x[1] < y[1]) # 最初の要素でソート
        println(sorted_v)
        


基本的な例

# 整数配列のソート
v1 = [5, 2, 8, 1, 9]
sorted_v1 = sort(v1, alg=InsertionSort)
println("ソートされた配列 (sort): ", sorted_v1) # 結果:[1, 2, 5, 8, 9]

# insorted()の使用例
v2 = [5, 2, 8, 1, 9]
sorted_v2 = insorted(v2)
println("ソートされた配列 (insorted): ", sorted_v2) # 結果:[1, 2, 5, 8, 9]

# 元の配列は変更されないことを確認
println("元の配列: ", v1) # 結果:[5, 2, 8, 1, 9]

文字列配列のソート

# 文字列配列のソート
v3 = ["apple", "banana", "cherry", "date"]
sorted_v3 = sort(v3, alg=InsertionSort)
println("ソートされた文字列配列: ", sorted_v3) # 結果:["apple", "banana", "cherry", "date"]

カスタム比較関数を使用したソート

# タプルの配列を、タプルの2番目の要素でソート
v4 = [(1, "b"), (2, "a"), (1, "a")]
sorted_v4 = sort(v4, alg=InsertionSort, by = x -> x[2])
println("カスタム比較関数でソートされた配列: ", sorted_v4) # 結果:[(1, "a"), (2, "a"), (1, "b")]

# lt引数を用いた比較関数
v5 = [(1, "b"), (2, "a"), (1, "a")]
sorted_v5 = sort(v5, alg=InsertionSort, lt = (x,y) -> x[2] < y[2])
println("lt引数を用いた比較関数でソートされた配列: ", sorted_v5) # 結果:[(1, "a"), (2, "a"), (1, "b")]

構造体の配列のソート

# 構造体の定義
struct Person
    name::String
    age::Int
end

# 構造体の配列
people = [Person("Bob", 30), Person("Alice", 25), Person("Charlie", 35)]

# 年齢でソート
sorted_people = sort(people, alg=InsertionSort, by = p -> p.age)
println("年齢でソートされた構造体の配列: ", sorted_people)

# 名前でソート
sorted_people_name = sort(people, alg=InsertionSort, by = p -> p.name)
println("名前でソートされた構造体の配列: ", sorted_people_name)

sort!()を使用したインプレースソート

# 元の配列を直接変更するインプレースソート
v6 = [5, 2, 8, 1, 9]
sort!(v6, alg=InsertionSort)
println("インプレースソートされた配列: ", v6) # 結果:[1, 2, 5, 8, 9]

ソート済みかどうかの確認

# isperm()関数でソート済み配列かどうかを確認する。
v7 = [1,2,3,4,5]
v8 = [1,5,2,4,3]
println(isperm(v7)) # 結果:true
println(isperm(v8)) # 結果:false
# 大きな配列でのパフォーマンス比較
using BenchmarkTools

v9 = rand(1000)

println("挿入ソートのパフォーマンス:")
@btime sort(v9, alg=InsertionSort)

println("デフォルトソートのパフォーマンス:")
@btime sort(v9)


デフォルトのsort()関数 (クイックソートまたはマージソート)

  • sort!()は元の配列を直接変更します。
    v = [5, 2, 8, 1, 9]
    sort!(v)
    println(v) # 結果:[1, 2, 5, 8, 9]
    
  • 例:
    v = [5, 2, 8, 1, 9]
    sorted_v = sort(v) # デフォルトのソートアルゴリズムを使用
    println(sorted_v) # 結果:[1, 2, 5, 8, 9]
    
  • sort()関数は、デフォルトでクイックソートまたはマージソートを使用します。これらのアルゴリズムは、挿入ソートよりも一般的に高速で、特に大きな配列に対して効率的です。

別のソートアルゴリズムの指定

  • 例:
    v = [5, 2, 8, 1, 9]
    sorted_v_merge = sort(v, alg=MergeSort)
    println(sorted_v_merge) # 結果:[1, 2, 5, 8, 9]
    
  • sort()関数では、alg引数を使用して他のソートアルゴリズムを指定できます。
    • alg=QuickSort:クイックソート
    • alg=MergeSort:マージソート
    • alg=TimSort:TimSort

sortperm()関数 (ソートされたインデックスの取得)

  • これは元の配列を並び替えるのではなく、並び替えた場合の順番をインデックスとして取得したいときに便利です。
  • 例:
    v = [5, 2, 8, 1, 9]
    p = sortperm(v)
    println(p) # 結果:[4, 2, 1, 3, 5]
    sorted_v = v[p]
    println(sorted_v) # 結果:[1, 2, 5, 8, 9]
    
  • sortperm()関数は、配列をソートしたときのインデックスの順列を返します。元の配列は変更されません。

partialsort()関数 (部分的なソート)

  • partialsort!()は元の配列を直接変更します。
  • 例:
    v = [5, 2, 8, 1, 9]
    partial_sorted_v = partialsort(v, 1:3) # 最初の3つの要素をソート
    println(partial_sorted_v) # 結果:[1, 2, 5]
    println(v) #元の配列は変更されない
    
  • partialsort()関数は、配列の指定された範囲のみをソートします。

partialsortperm()関数 (部分的なソートのインデックス取得)

  • 例:
    v = [5, 2, 8, 1, 9]
    partial_p = partialsortperm(v, 1:3)
    println(partial_p) # 結果:[4, 2, 1]
    partial_sorted_v = v[partial_p]
    println(partial_sorted_v) # 結果:[1, 2, 5]
    
  • partialsortperm()関数は、配列の指定された範囲のみをソートしたときのインデックスの順列を返します。

sortslices()関数 (多次元配列の行または列のソート)

  • 例:
    A = [3 2 1; 6 5 4; 9 8 7]
    sorted_A = sortslices(A, dims=1) # 行でソート
    println(sorted_A)
    # 結果:
    # [3 2 1; 6 5 4; 9 8 7] (各行は最初の行の順番で並び替えられる)
    sorted_A_col = sortslices(A, dims=2) # 列でソート
    println(sorted_A_col)
    #結果
    # [1 2 3;4 5 6;7 8 9]
    
  • sortslices()関数は、多次元配列の行または列をソートします。
  • 例:
    v = [(1, "b"), (2, "a"), (1, "a")]
    sorted_v_stable = sort(v, alg=MergeSort, by = x -> x[1])
    println(sorted_v_stable) # 結果:[(1, "b"), (1, "a"), (2, "a")]
    
  • 安定ソートとは、同じ値の要素の順序を保持するソートアルゴリズムです。マージソートやTimSortは安定ソートですが、クイックソートは安定ソートではありません。