フリーソフトの小道   

コンピュータ・プログラマー養成講座(入門編)

第11回 「素数発見」アルゴリズムの進化を解説する A
〜高速アルゴリズム「倍数除去」の長所と欠点〜

 プログラミングコンテスト第1回「素数発見」競技の記録の伸びは飛躍的で、 コンテスト創設時に示した管理人の参考記録から10000倍の速度にまで達している。 同じコンピュータでありながらプログラムを変えただけで、これだけの性能アップが実現された秘密はどこにあるのだろうか。 この秘密について詳しく調べてみよう。  アルゴリズムの解説だから、プログラミング言語にはとらわれないつもりだが、 一部にプログラミングの話が出てくることは避けられないのでお許しを得たい。

「倍数除去」 アルゴリズムとは

 素数という数を学習したとき、先生が 「1から100までの素数を探しなさい」 と生徒に課題をだしたとしよう。 このとき、小さい数から1つずつ素数を見つけて行く方法を取る真面目な生徒がほとんどだろう。 それと対照的な生徒もいるはずだ。 その生徒の方法とは、 1から100までの数を全て紙に書きだす。 次に、1を消し、2より大きい2の倍数(偶数)を消し、3より大きい3の倍数を消し、5より大きい5の倍数を消し.... と1から100までの数から倍数を次々に消してゆき、最後に残った数(素数)を一気に集める方法である。
 この方法は最初のうちは作業だけで素数が求まるわけではない。 しかし、最終的には一番速く全ての素数を求めることができるのだ。
 下に示した例は、1から100までの数から、素数でない「1」と、2より大きい「偶数」を除去した段階の表である。 これに続いて、3以外の3の倍数、5以外の5の倍数...と 順に倍数を除去してゆく作業を繰り返すだけだ。 この場合、除去するときに、掛け算、割り算を一切使わずに済むことである(その数を加えてゆくだけだから、足し算のみで済む)。


      2   3      5      7      9  10
  11  12  13  14  15  16  17  18  19  20
  21  22  23  24  25  26  27  28  29  30
  31  32  33  34  35  36  37  38  39  40
  41  42  43  44  45  46  47  48  49  50
  51  52  53  54  55  56  57  58  59  60
  61  62  63  64  65  66  67  68  69  70
  71  72  73  74  75  76  77  78  79  80
  81  82  83  84  85  86  87  88  89  90
  91  92  93  94  95  96  97  98  99 100

 この作業の手順をアルゴリズムとして記述してみよう。 前回取り上げた「逐次余り判定」 のときと同様に、 まず最初に、作業を箇条書きして作業手順を表し、 次に繰り返し作業の共通部分を見つけ出す方法をとり、 「倍数除去」 アルゴリズムを記述してみよう。

「倍数除去」アルゴリズムを組み立てる

 素数を求める最大値を1000とする(いくらでもよいのだが)。

  1. [第1回目] 最初は、1から1000までの数の表を作る。
  2. [第2回目] 1は素数でないので表から消去する。
  3. [第3回目] 2より大きな2の倍数(偶数)を表から除去する。
  4. [第4回目] 3より大きな3の倍数(偶数)を表から除去する。
  5. [第5回目] 5より大きな5の倍数(偶数)を表から除去する。
  6. [第6回目] 7より大きな7の倍数(偶数)を表から除去する。
  7.      ・・・・・
  8.      ・・・・・

 次々と倍数を除去してゆく作業を繰り返すだけだから、アルゴリズムは簡単なものになりそうだ。 上の手順から 「繰り返しの部分」 を抜き出してみる。
 考える上で、繰り返しの起点を、 「5」 の倍数除去処理が終わったときとしよう。
 表の数「5」の次の数「6」は2の倍数、3の倍数のときの2回にわたって既に消去されているから表には無い。 表に残っている次の数は「7」である。 これが次の素数である。これに気付けば、繰り返しの部分のアルゴリズムは簡単に作り上げることができる。

  1. 既に倍数を除去された表の残りの先頭にある数が次の素数だから、表から消え残っている次の数を探す
  2. その素数自身以外の、「その数の倍数」 を表から全て除去する。

 繰り返しの部分は非常に簡単になってしまった。 上記の処理を 「素数チェック処理」 と呼ぶことにしよう。 次に、「素数チェック処理」を繰り返し実行して、次々と素数を求める部分のアルゴリズムを作ればよい。 素数チェックの最大数の大きさを1000として、素数発見アルゴリズムを完成してみよう。

  1. 最初に 1 を表から取り除く。
  2. 最初の素数を 2 とする。
  3. [倍数除去処理]その素数の倍数(2倍以上)を表から全て取り除く。
  4. 次の素数を探す。次の素数とは、直前に倍数除去した数以上で表から消え残っている最初の数が次の素数である。
  5. その素数が最大数1000に達していないときは、倍数除去処理に戻り、倍数除去をおこなう。
  6. 表全体から消え残って数が素数であるから、それらを順に画面に表示する(ファイルに書き出す)。

 これで「倍数除去」アルゴリズムのプロトタイプが出来上がったことになる。 このアルゴリズムをプログラミング言語で記述すれば、 素数発見プログラム(倍数除去アルゴリズムによるバージョン1)が完成する。

プログラム上での 「表」 は、配列変数を使って表現する

 プログラミング上で表を表現するためには、配列(array)を利用すればよい。 配列とは、変数をひとまとめにする。それぞれの変数に番号をつけて操作する変数の集合体のことである。 表の数字を先頭から T(1)、T(2)、T(3)、T(4)、.... 、T(1000) とし、それぞれの変数に1が入っておれば 表にその数字が書かれているとし、0が入っておれば、その数が表に無いとするのだ。
 BASIC言語で配列を使うときには、 DIM T(1000) とプログラムの最初に記述する。 これだけでこの配列が使えるようになる。 なお、C言語では int T[1001]; 、Pascal言語では T: array[1..1000] of integer; など、 プログラミング言語により微妙に異なる表現になるが、ほとんど同一といっても良い。
 センター試験で出題されるプログラミング言語であるBASIC言語を使って、 この表を記述し、アルゴリズム通りに作ったものが次に示すプログラム例である。


100 '素数発見プログラム プロトタイプ     BASIC言語での記述
110 DIM T(1000)            ---数の表を配列でとる
120 FOR I=1 TO 1000: T(i)=1: NEXT   ---表に数を書いたことにするため配列に1を全て入れる
130 T(1)=0     ---表から1を取り除いたことにするため T(1) にゼロを入れる
140 FOR I=2 TO 1000  ---2から初めてその数より1つ小さい数までチェックを繰り返す
150   IF T(I)=0 THEN GOTO 200   ---表から既に消えているので1つ大きい数に変える
160   J=I       ---その素数をセットし
170   J=J+I     ---その素数を順に足すことで倍数を求め、表から消したことにするため T(J)=0 とする
180   IF J > 1000 THEN GOTO 200  ---最大値を越えると倍数除去作業を終了
190     T(J)=0: GOTO 170
200 NEXT I
210 FOR I=2 TO 1000
220   IF T(I)=1 THEN PRINT I    ---T(I)=1である表に残った数を画面に表示(ファイルに書き出す)
230 NEXT I    ---調べる最大数 1000 で終了
240 END

 同じく、C言語で記述したものが次のプログラムである。


// 素数発見プログラム プロトタイプ     C言語での記述
#include < stdio.h >
int main(int argc,char **argv)
{
  int i,j;
  int t[1001];    ---表を表す配列領域を作る

  for( i = 1;  i <= 1001; i++ ) t[i]=1;   ---表に数を書いたことを表す1を配列に入れる
  t[1]=0;    ---1は素数でないことを示す0を入れる

  for( i = 2; i < 1001; i++ ) {   ---2から順に倍数を除く作業を繰り返す
     for(j=2; j <= 1001; j+=i ) t[j]=0;    ---倍数の除去
  }
  for( i = 2; i < 1001; i++ ) {   ---倍数を除かれた表から素数を取りだす
     if( t[i] ) printf("%d",i);  ---素数を画面に表示
  }
}

 Pascal言語で記述したものが次のプログラムである。


{ 素数発見プログラム プロトタイプ     Pascal言語(Delphi)での記述 }
program ProtoType-1;
var
 i,j: integer;
 t: array[1..1000] of integer;

begin
  for i:=1 to 1000 do t[i]:=1;    ---表の数全てを書き込んだとして1を代入する
  t[1]:=0;                        ---1は素数でないので0を入れ、表から消す
  for i:=2 to 1000 do
  begin
    j:=i+i;
    while j<=1000 do
    begin
      t[j]:=0; j:=j+i;            ---素数の倍数を表から消す(0を代入)
    end;
  end;
  for i:=2 to 1000 do             ---最初の素数を3とする
  begin
    if t[i]=1 then writeln(i);    ---表に残っている素数を画面に出力する
  end;
end.

 「倍数除去」アルゴリズムは、表を使うことで割り算をまったく使わない素数発見処理を可能とする。 このため、時間がかかる割り算をプログラムから排除できるため、素数発見処理を非常に速くできる。 処理速度を競うプログラムコンテストでは、ほとんどがこのアルゴリズムをベースにしたプログラムである。

アルゴリズムからプログラムへ、プログラムのチューニングで速度アップへ(手順はこれまで通り!)

 アルゴリズムからプログラムに作り上げることが出来た後は、プロラムのチューニング作業に入る。 「倍数除去」 アルゴリズムで作成されたプログラムにも、「逐次余りチェック」アルゴリズムのときと同様に、 無駄な処理がいたるところに数多く含まれているはずだ。 それの無駄な処理を見つけて、細部にわたるチューニングを行うことで、 より速いプログラムに成長させることが出来る。  1000万未満までは、プログラムが思い通りに高速で動き、 プログラムのチューニング作業をしてゆくたびに記録を更新できる。 プログラミング作業に充実感がある楽しい時期だ。 しかし、上の記録を狙うため素数発見の限界を大きくしてゆくことになる。

「倍数除去」アルゴリズムの弱点はどこにある? 〜その弱点は「1億未満」部門で現れる〜

 チューニング作業でプログラムの速度を上げることは難しくない。どこまでも速くできるのか? 限界値をどこまでも大きく出来るのか?と記録を目指せば、壁に突き当たる。
 このアルゴリズムにも 「大きな弱点」 が存在する。 その弱点が顕著に現れてくるのは「1億未満」部門のように素数限界値が大きくなり、 使う表が大きくなったときである。
 コンテストの記録を見ても、3月中旬までは1000万未満部門にはそれぞれ参加できてはいたが、 1億未満の部門には参加できなかった(1億未満は実行不可能?時間がかかりすぎた?からか)。 3月18日になって、「KITAKEN」君が初めて「1億未満」部門に登場する。 「影武者」さんは3月12日には「5億未満」部門の記録を出しているのに!
 なぜそのような違いが現れたのか。 それは、このアルゴリズムの弱点を克服する方法を「影武者」さんが早く発見できたということだ。 「1億未満」部門以上に参加するにはこの弱点を克服する技が必要になる。 それが出来ないうちは、1億未満部門に参加できないのだ。
 理論的には簡単に次の段階に行けるように思えるが、 突然に信じられないくらいにプログラムの処理速度が低速になってしまう。 プログラムがいつまで経っても終わらない状態に陥るのだ。
 「影武者」さん、「ことり」君、「rouden」君、「KITAKEN」君および、 プログラム作りに挑戦していた「影のコンテスト参加者」のプログラマさん達すべてが 経験した大きな壁だった。
 この速度低下現象が、「倍数除去」 アルゴリズムが持つ最大の弱点なのだ。 「表」 そのものに起因する弱点だから重大な問題点である。 アルゴリズム上で必須のものである 「表」 が原因となっている。 表をやめるわけには行かないから、このアルゴリズムでは致命的な弱点になる。

「表」が大きくなるにつれ、プログラムが必要とするメモリーが巨大化する!

 なぜ、プログラムが低速化するのか、その原因は何かを知り、 その状態に陥らないようにする対策にはどのような方法があるのかなど、 この弱点克服については、コンピュータの仕組みを含めて多方面の知識が必要になる。 次回は、この弱点克服について、そのための色々な技の詳細を説明する。 また、これらの技を使ってどのように弱点を克服してゆくかを解説する予定である。

 「第12回 「素数発見」アルゴリズムの進化を解説する B」 を 見る

2004/03/28  管理人(志)


感想・意見は、掲示板、または メールでどうぞ! --- 管理人(志)