2016 年度「計算数学」 2017-01-27

§11.1 マージソート 【課題5】

(11.1.1) マージ (merge) ソートでは, ソートすべきデータがはいっている配列 a[ ] のほかに, それと同じ大きさの配列 b[ ] が必要となる。 そこが欠点であるが,アルゴリズムは比較的わかりやすい。

(11.1.2) 配列 a[ ] が次を満たしているとする。

これを(ここでは)「4 ずつ並んでいる」と呼ぼう。 (最後に半端が出たら,それらも小さい順に並んでいるとする。)

(11.1.3) このとき,この配列全体をもう一つの配列 b[ ] にコピーし, 次を満たしているようにする。

(11.1.4) 子どもたちが四人ずつ班に別れ, それぞれの班の中では背の順に並んでいるとき, 二班を一つにまとめ,八人が背の順に並ぶようにせよ,というのと同じである。

(11.1.5) それには,次のようにすればよい。 a[1] から a[8] までの部分で説明しよう。 まず a[1]a[5] を比べ,小さいほうを b[1] にコピーする。 もしも a[1] のほうがコピーされたなら, 次には a[2]a[5] を比較する。 いわば“勝ち抜き戦”である。 片方のグループの元をすべてコピーしてしまったら, あとはもう一つのグループの残りを(比較することなしに)順にコピーする。

(11.1.6) 配列 a[ ] 全体に対し上の操作を行なって配列 b[ ] にコピーし終えたら, それから,b[ ] 全体を a[ ] に, そっくりそのまま,順序を変えずにコピーする。

(11.1.7) これで,配列 a[ ] を, 「4 ずつ並んでいる」から 「8 ずつ並んでいる」に変えることができた。

(11.1.8) 最初に配列 a[ ] を乱数で初期化した直後には, この配列は「1 ずつ並んでいる」と言える。 それを上のような操作で「2 ずつ並んでいる」ように変え, 「4 ずつ並んでいる」ように変え, ……とくりかえしてゆけば,やがて配列全体がソートされる。 このアルゴリズムを「マージソート」という。

(11.1.9) m ずつ並んでいる m 個のもの二組を小さい順に並べて別の場所にコピーする際, 次にコピーすべき元(げん)を決めるには,たかだか一回の比較で済む。 最後の一つは,残ったものだから,比較しなくても決まる。 よって,比較回数はたかだか 2m-1 回である。 コピーされる元は 2m 個である。 これらは Ο(m) である。 よって, 「m ずつ並んでいる」から「2m ずつ並んでいる」に変えることは Ο(N) の計算量で可能である。 これを全部で約 log2(N) 回くりかえすから, 全体では Ο(N log(N)) である。

(11.1.10) クイックソートとは異なり, 初めに配列がどのように初期化されていても,このオーダーである。 また,乱数で初期化する場合,平均すると, クイックソートよりも比較回数は少ないようである。

§11.2 課題5

(11.2.1) 課題1と同様のことを,マージソートについて行なえ。

(11.2.2) 挿入ソートのソースファイルをコピーしたものに, 下のプログラムをはりつけ,ヒントに従って埋めてゆく。 ファイル名は merge.c がよいだろう。

(11.2.3) プログラムの main() は次のようになる。

main() {
    int m, start, i, j, k;

    /* ここで,いつものように,配列の大きさの表示,配列 */
    /* の初期化,初期化直後の配列の画面表示を行なう     */

    for (m = 1; m ???; m = ???) {
        printf("「%d ずつ並んでいる」のを", m);
        printf("「%d ずつ並んでいる」ように変えます.\n", 2*m);
        for (start = 1; start ???; start = ???) {
            i = 0; j = 0; k = 0;
            for (    ; ????????????????????????????????;   ) {
                if (comp(start+i, start+m+j) < 0) {
                    b[???] = a[???];
                } else {
                    b[???] = a[???];
                }
            }
            for (    ; ??????????????????????;   ) {
                b[???] = a[???];
            }
            for (    ; ??????????????????????;   ) {
                b[???] = a[???];
            }
        }
        /* ここで,for ループを用いて,配列 b[ ] を a[ ] に */
        /* そっくりそのままコピーする */
        print();
    }
    /* ここで,いつものように比較回数の表示などを行なう */
}

変数は,上のように,五つ用意する。

(11.2.4) 「int a[N+1];」の次の行に「int b[N+1];」を入れるのを忘れずに。

(11.2.5) ※ 実は,b[ ] をそのまま a[ ] にコピーして戻すのは時間の無駄である。 その際,ついでに「2m ずつ並んでいる」のを 「4m ずつ並んでいる」ように変えれば, この無駄が省ける。 しかし,プログラムが長くなるか,まだ習っていない書き方が必要になる(と思う)。

(11.2.6) いままでのアルゴリズムとは異なり,要素の交換のくり返しではないから, 関数 swap() は使わない。よって,交換回数は調べない。 (関数 swap() などは使わないが,残しておいて構わない。)

(11.2.7) 件名は「?? kadai5」(「??」は自分のアカウント名の下二桁。 全て半角文字,アルファベットは小文字。 kadai5 の間にはスペースをいれない)とせよ。 これ以外の注意点は課題1と同じ。

(11.2.8) N の値を大きくしての実験は, N を 10000, 100000, 1000000 にして,五回ずつ行なえ。

§11.3 ソートの安定性

(11.3.1) この節は,課題が早く終わって時間が余った人のためのものである。 前に (5.3.3) で述べた, ソート前のデータに同じ値のものがあった場合の問題を考えよう。

(11.3.2) あるソートのアルゴリズムが「安定 (stable)」であるとは, 同じ値のものが,ソート後も元の順序を保っていることをいう。 あるソートアルゴリズムが安定であることをいうには, 数学の定理と同様に証明が必要であり, 不安定(あるいは非安定)であることをいうには反例をもって示すべきであるが, 実験で試し,その結果を参考にすることができる。

(11.3.3) この授業で書いているプログラムを少し改変することで, その実験が行なえる。以下では,そのやり方の一つを説明する。 プログラムのコピーを,次のように改変する。

(11.3.4) 以上のように改変してから実行する。 ソート後,a[i] の値が同じものが並んだら, それらの中で b[i] の値が小さい順かどうかを調べる。


岩瀬順一