2014年 秋期 応用情報技術者試験 問3

マージソート

マージソートは、整列(ソート)したいデータ(要素)列を、細かく分割した後に、併合(マージ)を繰り返して全体を整列する方法である。

ここでは、それぞれの要素数が1になるまでデータ列の分割を繰り返し、分割されたデータ列を昇順に並ぶように併合していくアルゴリズムを考える。例として、要素数が8の場合のアルゴリズムの流れを図1に示す。

[6][4][3][8][7][2][1][5]
[6][4][3][8]
[7][2][1][5]
[6][4]
[3][8]
[7][2]
[1][5]
[6]
[4]
[3]
[8]
[7]
[2]
[1]
[5]
分割
<div class="merge-phase">
  <div class="level">
    <div class="array">[4][6]</div>
    <div class="array">[3][8]</div>
    <div class="array">[2][7]</div>
    <div class="array">[1][5]</div>
  </div>
  <div class="level">
    <div class="array">[3][4][6][8]</div>
    <div class="array">[1][2][5][7]</div>
  </div>
  <div class="level">
    <div class="array">[1][2][3][4][5][6][7][8]</div>
  </div>
  <div class="phase-label">併合</div>
</div>
図1 アルゴリズムの流れ

再帰呼出しを使って記述したマージソートのアルゴリズムを図2に示す。

① 与えられたデータ列の要素数が1以下であれば、整列済みのデータ列とし、呼出し元に処理を戻す。要素数が2以上であれば、②に続く。

② データ列を、要素数がほぼ同じになるよう前半と後半のデータ列に分割する。

③ 前半と後半のデータ列に対し、それぞれマージソートのアルゴリズムを再帰的に呼び出す。

④ 前半と後半の二つのマージソート済みデータ列を、要素が昇順に並ぶよう一つのデータ列に併合する。

図2 マージソートのアルゴリズム

図2のアルゴリズムを連結リストに対して実行するプログラムを考える。ここでは、整列対象のデータとして正の整数を考える。連結リストは、複数のセルによって構成される。セルは、正の整数値を示すメンバvalueと、次のセルへのポインタを示すメンバnextによって構成される。連結リストの最後のセルのnextの値は、NULLである。連結リストのデータ構造を図3に示す。

6
4
3
8
7
2
1
5
NULL
図3 連結リストのデータ構造

【連結リストの分割】

図2中の②の処理を行う関数divideを考える。関数divideは、連結リストの先頭へのポインタ変数listを引数とし、分割後の後半の連結リストの先頭へのポインタを戻り値とする。連結リストの分割前後のイメージを図4に示す。

分割前
list →
[44] → [10] → [16] → [23] → [8] → [35] /
<div class="after">
  <div class="label">分割後</div>
  <div class="list-pointer">list →</div>
  <div class="linked-list">
    [44] → [10] → [16] /
  </div>
  <div class="return-value">関数divideの戻り値</div>
  <div class="linked-list">
    [23] → [8] → [35] /
  </div>
</div>
図4 連結リストの分割前後のイメージ

連結リストをセルの個数がほぼ同じになるように分割するために、ポインタ変数を二つ用意し、一方が一つ進むごとに、他方を二つずつ進める。後者のポインタが連結リストの終わりに達するまでこの処理を繰り返すと、前者のポインタは連結リストのほぼ中央のセルを指す。この方法を利用した関数divideのプログラムを図5に示す。

以下、連結リストのセルを指すポインタ変数をaとするとき、aが指すセルのメンバvalueをa->valueと表記する。

function divide( list )
    a ← list                    // aはセルへのポインタ
    b ← a->next                 // bはセルへのポインタ
    if ( b が NULL と等しくない )
        b ← b->next
    endif
    
    while (  )          // 連結リストの終わりまで繰り返す
        a ← a->next
        b ← b->next
        if ( b が NULL と等しくない )
            
        endif
    endwhile
                                 α
    p ← a->next                 // pはセルへのポインタ
     ← NULL
    return p
endfunction
図5 関数divideのプログラム

【連結リストの併合】

図2中の④の処理を行う関数mergeを考える。関数mergeは、二つの連結リストの先頭へのポインタ変数aとbを引数とし、併合後の連結リストの先頭へのポインタを戻り値とする。併合処理を行う際には、ダミーのセルを用意し(そのセルへのポインタをheadとする)、この後ろに併合後の連結リストを構成する。aとbが指すセルの値を比較しながら、値が小さい順に並ぶよう処理を進める。連結リストの併合の流れを図6(処理は、①、②、③、…と続く)に、関数mergeのプログラムを図7に示す。

a → [10] → [16] → [44] /
b → [8] → [23] → [35] /
head → [-] → [10] → [16] → [44] /
<div class="step">
  <div class="step-label">②</div>
  <div class="pointers">
    <div>a → [10] → [16] → [44] /</div>
    <div>b → [23] → [35] /</div>
  </div>
  <div>head → [-] → [8] → [23] → [35] /</div>
</div>

<div class="step">
  <div class="step-label">③</div>
  <div class="pointers">
    <div>a → [16] → [44] /</div>
    <div>b → [23] → [35] /</div>
  </div>
  <div>head → [-] → [8] → [10] → [16] → [44] /</div>
  <div class="continue">⋮</div>
</div>
図6 連結リストの併合の流れ
function merge( a, b )          // a, bは併合対象の連結リストの先頭へのポインタ
    ダミーのセルを用意する
    head ← ダミーのセルへのポインタ
    p ← head
    
    while (  , かつ, b が NULL と等しくない )
        if ( a->value が b->value 以下である )
            p->next ← a
            p ← a
            a ← a->next
        else
            p->next ← b
            p ← b
            b ← b->next
        endif
    endwhile
    
    if (  )              // 要素が残っている連結リストを連結する
        p->next ← b
    else
        p->next ← a
    endif
    
    return 
endfunction
図7 関数mergeのプログラム
出典:平成26年度 秋期 応用情報技術者試験 午後問題 問3