[小西ホームページ]   [目次・索引]   [前の授業]   [次の授業]

コンピュータIIIB(Javaアルゴリズム)第4回

目次
4.1 整列
4.1.1 整列とは
4.1.2 番兵
4.1.3 選択整列
4.1.4 挿入整列
4.2 整列の応用
4.2.1 整列済みの配列
4.2.2 整列済みの集合
4.3 演習4
4.4 レポート課題
4.5 参考文献
索引
外部整列   整列   選択整列   挿入整列   内部整列   番兵  

4.1 整列

4.1.1 整列とは

与えられた数列を小さい順に並べ替えることを、 整列sorting ) と言います。 数列を大きい順に並び替えることや、文字列の列を辞書の順序に並べ替えることも、整列に含めます。 また、例えば会員番号と氏名からなるレコードを、会員番号順に並べ替えることも整列です。 この場合は、レコードの会員番号の部分を数列と見なします。

整列はよく利用されますので、色々な高速アルゴリズムが提案されています。

整列アルゴリズムは、内部整列と外部整列に分類されます。 内部整列internal sorting ) とは、メモリにすべての数列が格納される場合の整列法です。 一方、メモリにすべての数列が収まらないときは、ハード・ディスクなどに数列をファイルとして保存しながら整列します。 この場合の整列法が、 外部整列external sorting ) です。 内部整列と外部整列とでは、適したアルゴリズムは異なります。 今日は、内部整列について考えます。

4.1.2 番兵

内部整列では、まず、数列を配列に格納します。 このとき、番兵を配列の端に追加することがあります。 番兵sentinel ) とは、配列の端を表す特別な要素のことです。 番兵を置きますと、整列アルゴリズムによっては、要素を比較することと、配列の範囲内か確認することが、一本化できるのです。 ここでは、 a [0] に負の大きな数を格納し、整列する数列は a [1] から格納します。

4.1.3 選択整列

最初に紹介するのは選択整列です。

選択整列selection sort ) のアルゴリズムは次の通りです。 まず、 a [1] 以降で最も小さい数を探し、それと a [1] を交換します。 次に、 a [2] 以降で最も小さい数を探し、それと a [2] を交換します。 その次に、 a [3] 以降で最も小さい数を探し、それと a [3] を交換します。 この手続きを、 a [ a .length - 2] まで繰り返します。

選択整列のイメージ
図 4.1  選択整列のイメージ(枠内は未解決部分)

選択整列は分かりやすいですが、あまり速くはありません。

プログラムは以下の通りです。

/*  1*/ class SelectionSort {
/*  2*/     public static void main (String[] args) {
/*  3*/         int i, j, min, t, SENTINEL = -9999;
/*  4*/         int[] a = {SENTINEL, 3, 6, 5, 4, 9, 1, 8, 2, 7};
/*  5*/         for (i = 1; i < a.length; i++) {
/*  6*/             System.out.print(" " + a[i]);
/*  7*/         }
/*  8*/         System.out.println();
/*  9*/         for (i = 1; i < a.length - 1; i++) {
/* 10*/             min = i;
/* 11*/             for (j = i + 1; j < a.length; j++) {
/* 12*/                 if (a[j] < a[min]) {
/* 13*/                     min = j;
/* 14*/                 }
/* 15*/             }
/* 16*/             t = a[min];
/* 17*/             a[min] = a[i];
/* 18*/             a[i] = t;
/* 19*/             for (j = 1; j < a.length; j++) {
/* 20*/                 System.out.print(" " + a[j]);
/* 21*/             }
/* 22*/             System.out.println();
/* 23*/         }
/* 24*/     }
/* 25*/ }
asiaa1:~/comp3b b08a001$ java SelectionSort
 3 6 5 4 9 1 8 2 7
 1 6 5 4 9 3 8 2 7
 1 2 5 4 9 3 8 6 7
 1 2 3 4 9 5 8 6 7
 1 2 3 4 9 5 8 6 7
 1 2 3 4 5 9 8 6 7
 1 2 3 4 5 6 8 9 7
 1 2 3 4 5 6 7 9 8
 1 2 3 4 5 6 7 8 9
asiaa1:~/comp3b b08a001$

4.1.4 挿入整列

次に紹介するのが挿入整列です。

挿入整列insertion sort ) のアルゴリズムは次の通りです。 まず、 a [2] を左に移動して、 a [1] から a [2] までを整列済みにします。 次に、 a [3] を左に移動して、 a [1] から a [3] までを整列済みにします。 その次に、 a [4] を左に移動して、 a [1] から a [4] までを整列済みにします。 この手続きを、 a [ a .length - 1] まで繰り返します。

挿入整列のイメージ
図 4.2  挿入整列のイメージ(枠内は未解決部分、丸印は注目要素)

挿入整列も分かりやすいですが、あまり速くはありません。 ただし、データ数が 10 以下ならば速いことが知られています。

プログラムは以下の通りです。 要素を左に移動する際、最小要素でも番兵の直前で停止しますので、配列からはみ出すことはありません。

/*  1*/ class InsertionSort {
/*  2*/     public static void main (String[] args) {
/*  3*/         int i, j, v, SENTINEL = -9999;
/*  4*/         int[] a = {SENTINEL, 6, 4, 1, 5, 9, 8, 2, 3, 7};
/*  5*/         for (i = 1; i < a.length; i++) {
/*  6*/             System.out.print(" " + a[i]);
/*  7*/         }
/*  8*/         System.out.println();
/*  9*/         for (i = 2; i < a.length; i++) {
/* 10*/             v = a[i];
/* 11*/             j = i;
/* 12*/             while (a[j - 1] > v) {
/* 13*/                 a[j] = a[j - 1];
/* 14*/                 j--;
/* 15*/             }
/* 16*/             a[j] = v;
/* 17*/             for (j = 1; j < a.length; j++) {
/* 18*/                 System.out.print(" " + a[j]);
/* 19*/             }
/* 20*/             System.out.println();
/* 21*/         }
/* 22*/     }
/* 23*/ }
asiaa1:~/comp3b b08a001$ java InsertionSort
 6 4 1 5 9 8 2 3 7
 4 6 1 5 9 8 2 3 7
 1 4 6 5 9 8 2 3 7
 1 4 5 6 9 8 2 3 7
 1 4 5 6 9 8 2 3 7
 1 4 5 6 8 9 2 3 7
 1 2 4 5 6 8 9 3 7
 1 2 3 4 5 6 8 9 7
 1 2 3 4 5 6 7 8 9
asiaa1:~/comp3b b08a001$

4.2 整列の応用

4.2.1 整列済みの配列

ここで、データを整列する理由について考えます。 一般的に、データが整列済みであれば、データの処理は高速化できます。 つまり、いろいろな処理を行うデータに関しては、あらかじめ整列しておけば、最終的に短時間で処理が終わるのです。

例えば、配列

{2, 6, 4, 2, 8, 4, 2, 6, 4, 2}

から要素5を探索することを考えます。 配列が整列されていなければ、探索するには、配列要素を一つ一つ調べるしかありません。 「見つかりません」という結論を得るには、すべての要素を確認することになります。

プログラムは以下の通りです。 なお、配列の最後に番兵(探索要素自身)を配置して、配列の外に出ないようにするチェックを省略しています。

/*  1*/ class ArraySearch {
/*  2*/     public static void main (String[] args) {
/*  3*/         int i = 0, search = 5;
/*  4*/         int SENTINEL = search;
/*  5*/         int[] a = {2, 6, 4, 2, 8, 4, 2, 6, 4, 2, SENTINEL};
/*  6*/         boolean found = false;
/*  7*/         while (true) {
/*  8*/             if (a[i] == search) {
/*  9*/                 if (i < a.length - 1) {
/* 10*/                     found = true;
/* 11*/                 }
/* 12*/                 break;
/* 13*/             }
/* 14*/             i++;
/* 15*/         }
/* 16*/         if (found) {
/* 17*/             System.out.println("見つかりました");
/* 18*/         } else {
/* 19*/             System.out.println("見つかりません");
/* 20*/         }
/* 21*/     }
/* 22*/ }
asiaa1:~/comp3b b08a001$ java ArraySearch
見つかりません
asiaa1:~/comp3b b08a001$

もし、配列が

{2, 2, 2, 2, 4, 4, 4, 6, 6, 8}

と整列済みであれば、5を探索するのに、すべての要素を調べる必要がなくなります。 一つずつ要素を確認し、6を見た段階で、「見つかりません」と結論が出せるのです。

プログラムは以下の通りです。 なお、配列の最後に番兵(巨大な数)を配置して、配列の外に出ないようにするチェックを省略しています。

/*  1*/ class SortSearch {
/*  2*/     public static void main (String[] args) {
/*  3*/         int i = 0, search = 5, SENTINEL = 9999;
/*  4*/         int[] a = {2, 2, 2, 2, 4, 4, 4, 6, 6, 8, SENTINEL};
/*  5*/         boolean found = false;
/*  6*/         while (true) {
/*  7*/             if (a[i] >= search) {
/*  8*/                 if (a[i] == search && a[i] != SENTINEL) {
/*  9*/                     found = true;
/* 10*/                 }
/* 11*/                 break;
/* 12*/             }
/* 13*/             i++;
/* 14*/         }
/* 15*/         if (found) {
/* 16*/             System.out.println("見つかりました");
/* 17*/         } else {
/* 18*/             System.out.println("見つかりません");
/* 19*/         }
/* 20*/     }
/* 21*/ }
asiaa1:~/comp3b b08a001$ java SortSearch
見つかりません
asiaa1:~/comp3b b08a001$

整列済みの配列の探索については、左から一つ一つ調べるのではなく、まず中央を調べ、そして左半分か右半分を調べ…とすると、より高速な探索が可能です。

もう一つ、整列しておくと高速処理ができる例を紹介します。

配列

{2, 6, 4, 2, 8, 4, 2, 6, 4, 2}

の要素を、重複を除いて出力することを考えます。 これは、「自分より左に自分と同じ要素がなければ出力する」という方針でやればできます。 しかし、自分より左にある要素を確認すると、a[1]については最大1回、a[2]については最大2回、a[3]については最大3回…と、どんどん確認回数が増えてしまいます。

プログラムは以下の通りです。

/*  1*/ class ArrayUnique {
/*  2*/     public static void main (String[] args) {
/*  3*/         int i, j;
/*  4*/         int[] a = {2, 6, 4, 2, 8, 4, 2, 6, 4, 2};
/*  5*/         boolean found;
/*  6*/         for (i = 0; i < a.length; i++) {
/*  7*/             found = false;
/*  8*/             j = 0;
/*  9*/             while (true) {
/* 10*/                 if (a[j] == a[i]) {
/* 11*/                     if (j < i) {
/* 12*/                         found = true;
/* 13*/                     }
/* 14*/                     break;
/* 15*/                 }
/* 16*/                 j++;
/* 17*/             }
/* 18*/             if (!found) {
/* 19*/                 System.out.println(a[i]);
/* 20*/             }
/* 21*/         }
/* 22*/     }
/* 23*/ }
asiaa1:~/comp3b b08a001$ java ArrayUnique
2
4
6
8
asiaa1:~/comp3b b08a001$

もし、配列が

{2, 2, 2, 2, 4, 4, 4, 6, 6, 8}

と整列済みであれば、重複を除いて出力するのに、たくさんの要素を調べる必要がなくなります。 a[1]についてもa[2]についても、要素が変化したかどうか、1回だけ確認すればよいのです。

プログラムは以下の通りです。

/*  1*/ class SortUnique {
/*  2*/     public static void main (String[] args) {
/*  3*/         int i, val = 0, SENTINEL = 9999;
/*  4*/         int[] a = {2, 2, 2, 2, 4, 4, 4, 6, 6, 8, SENTINEL};
/*  5*/         if (a.length > 1) {
/*  6*/             val = a[0];
/*  7*/             System.out.println(val);
/*  8*/         }
/*  9*/         i = 1;
/* 10*/         while (true) {
/* 11*/             if (a[i] != val) {
/* 12*/                 if (a[i] == SENTINEL) {
/* 13*/                     break;
/* 14*/                 }
/* 15*/                 val = a[i];
/* 16*/                 System.out.println(val);
/* 17*/             }
/* 18*/             i++;
/* 19*/         }
/* 20*/     }
/* 21*/ }
asiaa1:~/comp3b b08a001$ java SortUnique
2
4
6
8
asiaa1:~/comp3b b08a001$

4.2.2 整列済みの集合

集合としての配列については、前回説明しました。 集合は、要素の重複がないことを思い出してください。

集合としての配列が整列済みであれば、集合演算もまた、高速化できます。 例えば、集合 A = {10, 20, 30, 40, 50} と B = {10, 30, 50, 70, 90} の和集合を出力するには、a[i] と b[j] を比較して小さい方を出力し、もし等しいならまとめて出力する、という方法でできます。 整列済みでないときは、何度も探索して和集合を求めたのですが、整列済みであれば、そんなことをしなくてよいのです。

プログラムは以下の通りです。 なお、配列の最後に番兵(巨大な数)を配置して、配列の外に出ないようにするチェックを省略しています。

/*  1*/ class SortUnion {
/*  2*/     public static void main (String[] args) {
/*  3*/         int i = 0, j = 0, SENTINEL = 9999;
/*  4*/         int[] a = {10, 20, 30, 40, 50, SENTINEL};
/*  5*/         int[] b = {10, 30, 50, 70, 90, SENTINEL};
/*  6*/         while (true) {
/*  7*/             if (a[i] < b[j]) {
/*  8*/                 System.out.println(a[i]);
/*  9*/                 i++;
/* 10*/             } else if (a[i] > b[j]) {
/* 11*/                 System.out.println(b[j]);
/* 12*/                 j++;
/* 13*/             } else if (a[i] == SENTINEL) {
/* 14*/                 break;
/* 15*/             } else { // a[i] == b[j]
/* 16*/                 System.out.println(a[i]);
/* 17*/                 i++;
/* 18*/                 j++;
/* 19*/             }
/* 20*/         }
/* 21*/     }
/* 22*/ }
asiaa1:~/comp3b b08a001$ java SortUnion
10
20
30
40
50
70
90
asiaa1:~/comp3b b08a001$

4.3 演習4

集合としての配列が整列済みと仮定して、集合 A = {10, 20, 30, 40, 50} と B = {10, 30, 50, 70, 90} の共通部分を出力するプログラムを作成してください。 番兵については、和集合のときと同じでよいです。

asiaa1:~/comp3b b08a001$ java SortIntersection
10
30
50
asiaa1:~/comp3b b08a001$

4.4 レポート課題

今日の演習4の答案(Javaプログラム)をメールで提出してください。 差出人は学内のメール・アドレス(b08a001@cis.twcu.ac.jpなど)とし、宛先はkonishi@cis.twcu.ac.jpとします。 メールの本文には、学生番号、氏名、科目名、授業日(10月17日)を明記してください。


4.5 参考文献


[小西ホームページ]   [目次・索引]   [前の授業]   [次の授業]

2008年10月17日更新
小西 善二郎 <konishi@cis.twcu.ac.jp>
Copyright (C) 2008 Zenjiro Konishi. All rights reserved.