基本情報技術者試験的な備忘録(マージソートをVBAで考える・再帰処理)

20230102基本情報技術者試験的な備忘録(マージソートVBAで考える・再帰処理)


パソコン素人です。


基本情報技術者試験の問題・テキスト等を見て気になったメモです。
間違っているので詳しく見ないで下さい。すみません。
(引用した内容等、その他問題があれば削除いたします。)

基本情報技術者試験の出題範囲「アルゴリズムとデータ構造」で、「マージソート」がある。
マージソートの説明は省略します。)


ネットには、pythonで「マージソートを実装」というのが結構出ている。
ネット上の「paiza.io」で入れてみると確かに結果が出るが、「paiza.io」でのpythonのステップ実行ができない。
(自分が知らないだけかもしれないが。)


で、ステップ実行やブレークポイントの設定がしやすいVBA(エクセル)でやってみた。

 

参考にしたpythonのコードは、
@Yu-ya-Shimizu様の「Pythonで学ぶアルゴリズム 第20弾:並べ替え(マージソート)」
です。感謝。

 

これ以外にもPythonでは方法はいくつかある。
実際にはマージソートは配列を分解(分割)しないでするプログラムが多いが、
これは実際に配列を分割して、新たに配列に並べ替えて統合していたので、これを見本にしました。

 

で、作り始めると、再帰処理が必須らしい。
再帰処理の説明も省略します。)

 

VBAでは配列がpythonのようにはシンプルにできない。
素数を定義したり・・・(前回のVBAの配列備忘録参照)。

途中、ブレークポイントやローカルウィンドウで変数の推移や再帰の流れを確認していったがよく分からない。

 

で、どうしようかとネットを再度見ると、VBAで作成した方がいた。
「もしも普通のサラリーマンがお金とITを学んだら」さんのマージソート
(感謝)
詳しく解説してくれ、完成品を表示していた。
(最初からこれで良かったのかも・・・)

 

で、それを参考に作成したのが下記。
(「もしも普通のサラリーマンがお金とITを学んだら」さんとは少し違ってます。)

 

************************************コード(間違ってたらすみません)
Option Explicit

Sub macro1_mergesort_saiki_hairetu()  'hairetuが並び変わってhairetu_goが入るように
    
    Dim hairetu As Variant
    Dim hairetu_go As Variant
    
    
    ReDim hairetu(3)  '0番使う(これなら4個使える)
    hairetu(0) = 5
    hairetu(1) = 3
    hairetu(2) = 4
    hairetu(3) = 1
    
    ReDim Preserve hairetu(7)  '0使う(これなら8個使える)
    hairetu(4) = 2
    hairetu(5) = 10
    hairetu(6) = 9
    hairetu(7) = 8
    
    ReDim Preserve hairetu(8) '0使う(これなら9個使える)
    hairetu(8) = 6
    

    hairetu_go = bunkatu(hairetu)  '戻ってくる配列
    
 

   Stop  'ローカルウィンドウで確認して下さい。(いろんな箇所でstopやステップインF8を使うと流れが分かります。)
 End Sub
 Function bunkatu(ByRef hairetu As Variant) As Variant    '1マージソート前の分割処理

    Dim buf As Variant
    buf = hairetu   'そのまま配列を受ける
    If UBound(buf) + 1 <= 1 Then  '再帰処理の終了条件

        bunkatu = buf
    Else  '分割処理
        Dim mid As Long
        
        mid = (UBound(buf) + 1) / 2   '半分に分割するため
        
        '追記疑問
        'VBAの配列は、pythonのようなリストの切り出し[:mid]とかできないのかもしれない。
                

        Dim left_hairetu As Variant  'integerだと要素数設定必要(ないとエラー)
        
        
        'データを分割・格納
        Dim a As Long
        ReDim left_hairetu(mid - 1)  '0まで使えるので-1
        
        For a = 0 To mid - 1    '前半(left)を回す
            left_hairetu(a) = buf(a)
        Next a
      
        
        Dim right_hairetu As Variant
    
        Dim b As Long
        ReDim right_hairetu(UBound(buf) - mid)  '配列(使える数)なので+1はしない
        Dim c As Long
        For b = mid To UBound(buf) '後半(right)を回す+1はしない
 
            right_hairetu(c) = buf(b)  '配列0から入れる
            c = c + 1
        Next b
        
        '再帰
        Dim left_saiki As Variant
        left_saiki = bunkatu(left_hairetu)
        '再帰
        Dim right_saiki As Variant
        right_saiki = bunkatu(right_hairetu)
        
        
        '統合(再帰後配列出し)
        Dim mergego_hairetu As Variant
        mergego_hairetu = Merge(left_saiki, right_saiki)
 
         '結果(マージ後配列戻し)
        bunkatu = mergego_hairetu
    End If
 
 
'    Stop
End Function

Function Merge(left_saiki, right_saiki)  '2比較して入れ替えて配列を統合


    ReDim result(UBound(left_saiki) + 1 + UBound(right_saiki) + 1 - 1) 'Uboundが0なら+1する。(足しても0だから)。

    
    Dim i As Long, j As Long
    Dim cnt As Long

    Do While (i <= UBound(left_saiki)) And (j <= UBound(right_saiki))

        If left_saiki(i) <= right_saiki(j) Then  '左<=右のとき(左小さい)
                                                '左から取り出してresultに追加(入れる)
            result(cnt) = left_saiki(i)
            i = i + 1
        Else                                      '右小さい
            result(cnt) = right_saiki(j)           '右から取り出してresultに追加(入れる)
            j = j + 1
        End If
        cnt = cnt + 1   '(出る時にカウント)
    Loop
    
    '左右差(端数のこり処理(左右上記処理でどうしても発生)) 残りは複数の場合あり・・・
    Dim d As Long
    
    If i < UBound(left_saiki) + 1 Then  '右辺大きいなら残りがある
        For d = i To UBound(left_saiki)
            ReDim Preserve result(cnt)
            result(cnt) = left_saiki(d)
            cnt = cnt + 1
        Next d
    End If
    
     If j < UBound(right_saiki) + 1 Then
        For d = j To UBound(right_saiki)
            ReDim Preserve result(cnt)
            result(cnt) = right_saiki(d)
            cnt = cnt + 1
        Next d
    End If
    
    Merge = result    'function bunkatuに返す

End Function
********************************************

 

これで、ローカルウィンドウなどで再帰処理を見ていく。
単純に、再帰は、ループから出る処理(if文など終了条件)を付けておく。
(無限ループしないように。)
また、再帰は上→下へ進むだけでなく、再帰するとそれより下へ行かず、
再帰を繰り返し、終了地点(上記のif文など)まで来ると、巻き戻るように逆順で動く。

 

今回の場合、functionが2つあり、bunkatuで再帰をし、mergeの方で並び替えて要素を結合するが、
bunkatuを全部やり終えた時点では、途中の分解した要素がたくさんできて、
mergeで途中分解したものを逆順で結合していくような感じだった。

 

再帰で順を追うのはなかなか大変だと感じました。