Web教材一覧アルゴリズム
(BOK大区分:1 基礎理論、中区分:2 アルゴリズムとプログラミング、中区分:2 アルゴリズム)

素数判別、素因数分解

学習のポイント

素数判別、素因数分解の基本的なアルゴリズムを理解します。

キーワード

アルゴリズム、素数、剰余、素数判別、エラトステネスのふるい、素因数分解


これまでは、素数研究が社会に直接関係することはあまりありませんでした。ところが、電子メールなどの暗号化が必要になりました。その暗号鍵、復号鍵では、「非常に大きいを素因数分解する計算には非常に時間がかかる」ことをベースにしています(→参考:「公開鍵暗号方式の原理」)。そのため、素数に関する関心が高まっているのです。

2,3,5,7,11,13,17,…など、1と自分自身でしか割り切れない数を素数といいます。12=4×3や、18=2×9のように、素数ではない数のことを合成数といい、2、3、4、9など、合成数を割り切れる数を約数といいます。そして、2や3など約数自体が素数であるとき、その約数を素因数といいます。

素数を扱うアルゴリズムは、大きく2つに区分されます。

素数判定
与えられた数nが素数であるかどうかを判定するアルゴリズムです。
n=12のとき、「約数4が見つかったので12は素数ではない」というように、一つの約数を見つければよいことになります。
素因数分解
12=2×2×3、18=2×3×3のように、合成数を構成するすべての素因数とその個数を列挙するアルゴリズムです。素因数分解のほうが複雑になります。一般的には、素数判定のアルゴリズムを発展させて、素因数分解に用いています。
単純な素数判定アルゴリズム

与えた数nが素数であるときは1、合成数のときは0を戻す、単純なプログラムを示します。
iを2からn-1まで変化させ、そのなかでnを割り切れたら合成数、どれでも割り切れなかったら素数だとすればよいことは自明でしょう。

  function prime(n) {
    for(i=2; i<=n-1; i++) {
      if ((n % i) == 0) return 0;
    }
    return 1;
  }

しかし、これではあまりにも非効率です。例えば、

  • n≦3のときは素数です。上のアルゴリズムでは、1のときの例外が考慮されていません。
  • nが2つの数の積になるのであれば、小さいほうの数は、√の以下になります。iの上限値を√にすべきです。
  • 2で割れないのであれば、それ以上の偶数で割り切れるはずがありません。iが3以上の場合はiが奇数のときだけチェックすればよいことになります。
それらを考慮すると、次のようになります。

  function prime(n) {
    if (n <= 3) return 0;     if ((n % 2) == 0) return 0;
    for(i=3; i<=Math.sqrt(n); i++) {
      if ((n % i) == 0) return 0;
    }
    return 1;
  }

エラトステネスのふるい

上と同様に考えて、nがある数で割りきれないときは、その倍数のチェックをする必要がありません。それで、table[i] という表を用意しておき、iで割り切れたときは、iの倍数kについて、table[k] を0にします。このようにして、素数を求めていくのです。
  function eratosthenes(n) {
    table = new Array(101);
             /* 初期設定:とりあえず2と奇数は素数だと仮定しておく */
    for (i=0; i<= n; i++) table[i] = 0;
    table[2] = 1;
    for (i= 3; i<=n; i=i+2) table[i] = 1;
             /* 3以上、nの平方根以下の奇数について */
    for (i=3; i<=Math.sqrt(n); i=i+2){
      if (table[i] == 0) continue;
             /* 既に倍数になっているならチェックしない */
      for (j=i+i; j<=n; j=j+i) table[j] = 0;
             /* j=2×i、3×i、…は倍数であるとする */
             /* このようにして、ふるいにかけられて、素数が減少する */
    }
    return table[n];
  }

エラトステネスのふるいでは剰余の計算が不要です。その観点からも効率的になります。

単純な素因数分解アルゴリズム

上述の「単純な素数判定アルゴリズム」では、素因数が何であるかを知ることができません。その「改良版」をベースにして、素因数分解アルゴリズムにします。

function fact(m){
  var n;
  n = m;
                    msg = "<html><body>"
                      + "<h4>単純法による素因数分解</h4>"
                      + "<p>" + n + "=";
  imax = Math.ceil( Math.sqrt(n) )  /* √の切り上げ */
  k = 0;
  if (n>=3) {
    while ( (n != 1) && ((n % 2) == 0) ) {
                    if (k > 0) msg = msg + "×";
      k++;
                    msg = msg + "2";
      n = n/2;
    }
    i=3;
    while ( (n != 1) && (i <= imax) ) {
      while ( (n != 1) && ((n % i) == 0) ) {
                    if (k > 0) msg = msg + "×";
        k++;
                    msg = msg + i;
        n = n/i;
      }
      i = i+2;  /* 奇数だけ。3,5,7,… */
    }
    if ( (k > 1) && (n > imax) ) {
      k++;
                    msg = msg + "×" + n;
    }
  }
                    if (k == 0) msg = msg + "素数";
                    msg = msg + "</p></body></html>";
                    document.write(msg);
}

フェルマの方法

素因数分解というより、2つの約数の積として分解するアルゴリズムです。その約数をさらに分解すれば、素因数分解になりますが、ここでは、解法のアイデアを示すことを目的として、約数を求めるだけにします。
   n=(x-y)×(x+y)=x2-y2 (x≧y≧0の整数)
として、x-y=1ならば素数、そうでなければx±yが約数になります。
 上式から、
   y=√2-n
となりますが、yは整数でなければなりません。
 それで、yが整数になるまで(n=(x-y)×(x+y) になるまで)、
   √≦x≦n/2
の範囲で、xを1ずつ変化させて、試行錯誤させることになります。

function fermat(n) {
            msg = "<html><body>"
              + "<h4>フェルマの方法による約数探索</h4>"
              + "<p>n=" + n + "</p>"
              + "<table border='1'><tbody>"
              + "<tr><td>x</td><td>y</td>"
              + "<td>x-y</td><td>x+y</td><td>(x-y)(x+y)</td></tr>";
  xmin = Math.ceil(Math.sqrt(n));     /* √の切り上げ */
  xmax = n/2;
  for (x=xmin; x<=xmax; x++) {
    y = Math.floor(Math.sqrt(x*x-n));  /* √の切り捨て */
            xmy = x-y; xpy = x+y; xy = xmy*xpy;
            msg = msg + "<tr><td>" + x + "</td><td>" + y + "</td>"
            + "<td>" + xmy + "</td><td>" + xpy + "</td><td>" + xy + "</td></tr>";
    if ( (x-y)*(x+y) == n) break;
  }
            msg = msg + "</tbody></table>";
            xmy = x-y; xpy = x+y; xy = xmy*xpy;
            if (xy == n) msg = msg + "<p>" + n + "=" + xmy + "×" + xpy + "</p>";
            else msg = msg + "<p>" + n + "は素数</p>";
            msg = msg + "</body></html>";
            document.write(msg);
}

この方法は、約数が大きいとき(√nに近いとき)に効率的です。しかし、√を求める計算に時間がかかるので、nが大きいときには探索範囲が広く、非効率になります。

大きな数の素因数分解には、非常に計算量が大きくなることから、効率的な解法を求めることが、数学者の関心を集め、以前からも多様な研究が行われてきました。素因数を探すことに比べて、最大公約数を求める操作は計算量が小さい。古典的な方法では、それをうまく用いて高速化を図っています。

特に最近ではデータの暗号化が重視され、特に公開鍵暗号方式が広く用いられています。この方式は、百桁以上の非常に大きな2つの素因数をもつ合成数から素因数を見つけることは時間がかかることを基盤にしています(→参考:「公開鍵暗号方式の原理」)。
 効率的な解法が発見されると、この暗号方式の機密性が崩されます。これは、個人のクレジット番号の漏洩から、国家機密にいたるまで、大きな影響を及ぼします。暗号方式の脆弱性を把握するために、効率的な解法の研究が重要課題になり、多様な方法が発表されています。


計算プログラム

1:素数表による因数分解
2:単純計算による因数分解
3:フェルマの方法による約数の検出
4:ρ法による約数の検出(求められないこともある)
nに大きい数値を与えると、時間がかかるので注意してください。

方法= n=


アルゴリズムへ