はじめMath! Javaでコンピュータ数学

第49回確率の数学 順列と組合せ [中編]

今回は、Java言語で順列を作るクラスを練習問題に取り上げます。数学では言葉と式で表現していたものをプログラミング言語に置き換えます。高校数学や大学受験の問題を解くのに十分な程度の品質を目標として取り組んで下さい。

問題 「n個の数値からk個を取る順列」を全て列挙するプログラムを作りましょう

1からnまでの整数から順番にk個取り出す場合を考えましょう。そして、取り得る場合を全て作成し、画面に出力するプログラムを作りましょう。

出力の形式は

  • カウント:1番目の数値,2番目の数値,・,k番目の数値

としましょう。

順列を生成するクラスはPermutationという名前にします。以下にスケルトンを示します。正しく処理するためのコードを追加して完成させましょう。

ソースコード:Permutation.javaのスケルトン
/*
 * class Permutation
 * 目的: 順列に必要なメソッドを備える
 */
class Permutation {

  //プライベートな変数・オブジェクトの宣言
  ArrayList<ArrayList> _E = new ArrayList<ArrayList>();
  int _n;
  int _k;

/*
 * 目的: コンストラクタ。順列に必要な変数を用意し、
 *       順列を作成する。
 * 引数: nPk のn,k
 */
  Permutation(int n,int k){
    _n = n; _k = k;
    /*
      -------------------------------------------
      <手順>
     (1)val[0]..val[_k-1]に初期値0をセットする。
          Javaでは数値型配列の初期値はゼロクリアされている仕様だが、良き習慣としてゼロクリアを実行しておこう。
     (2)val[_k-1]を最下位桁として、1つずつ値を増していく。val[]をn進k桁の数として扱う。
     (3)こうして出来た値の列について、数値の重複する桁がないかチェックする。
     (4)重複がなければ、ユニークな順列の要素として保管する。
      -------------------------------------------
     */

  }// end of Constructor Permutation()


/*
 * 目的: n,k へのアクセッサ
 * 戻り値: n,k の値
 */
  public int getN(){
    return _n;
  }
  public int getK(){
    return _k;
  }// end of getN,getK


/*
 * 目的: 指定番目(0..(nPk-1)) の順列を返す。
 * 引数: 添字, 順列を格納する配列
 */
  public void getElements(int count, int val[]){

  }// end of getElements


/*
 * 目的: 引数で指定された順番の順列を返す
 * 引数: 生成した順列の添字
 * 戻り値: CSV 形式の文字列
 */
  public String toString(int count){

  }// end of toString


/*
 * 目的: 順列の数を返す
 * 戻り値: 順列の数
 */
  public long getSize(){

  }// end of getSize


} // end of class Permutation

完成したPermutationクラスを利用して、次のコードを動作させましょう。このソースコードを変更する必要はありません。このままで、Permutationクラスを利用してください。

ソースコード:TestPermutation.java
//サンプルコード
//問題1「n 個の数値からk 個を取る順列」を全て列挙するプログラムを作りましょう。
//filename : TestPermutation.java

import java.util.ArrayList;

class TestPermutation {

  public static void main(String[] args) {

    int n=3;
    int k=3;

    Permutation p = new Permutation(n,k);

    System.out.println("P("+p.getN()+","+p.getK()+")="
                        +p.getSize());

    System.out.println("全ての順列を表示します。");

    System.out.println("toString を利用");
    for(int i=0; i<p.getSize(); ++i){
      System.out.println(i + ":" + p.toString(i));
    }

    System.out.println("直接値を取得");
    int temp[] = new int[k];
    for(int i=0; i<p.getSize(); ++i){
      String result = "";
      p.getElements(i,temp);
      result += i+":";
      for(int j=0; j<temp.length; ++j){
        result += temp[j];
        if(j!=temp.length-1){
          result += ",";
        }
      }
      System.out.println(result);
    }

  }// end of main


}// end of class TestPermutation
TestPermutation.javaの実行結果は次のようになるはずです。
C>java TestPermutation
P(3,3)=6
全ての順列を表示します。
toString を利用
0:2,1,0
1:1,2,0
2:2,0,1
3:0,2,1
4:1,0,2
5:0,1,2
直接値を取得
0:2,1,0
1:1,2,0
2:2,0,1
3:0,2,1
4:1,0,2
5:0,1,2

解説

問題 ⁠n個の数値からk個を取る順列」を全て列挙するプログラムを作りましょう。

実装の方法は様々考えられます。ここに示す実装がベストであるとは思いません。むしろ色々突っ込みどころが多いと思います。特に、順列の総数は階乗ですから、すぐに膨大な数になります。そのための対応策は一切含んでいません。実際の問題に利用する場合には、問題に応じてじっくり検討する必要があります。

ともあれ、ひとつの取っかかりとして、このソースコードを参考にしていただけると幸いです。

ソースコード:Permutation.java完成版
import java.util.ArrayList;

/*
 * class Permutation
 * 目的: 順列に必要なメソッドを備える
 */
class Permutation {

  //プライベートな変数・オブジェクトの宣言
  ArrayList<ArrayList> _E = new ArrayList<ArrayList>();
  int _n;
  int _k;

/*
 * 目的: コンストラクタ。順列に必要な変数を用意し、
 *      順列を作成する。
 * 引数: nPk のn,k
 */
  Permutation(int n,int k){
    _n = n; _k = k;
    //引数チェック
    if (_k>_n || _k<0 || _n<0) {
      _n=0;_k=0; //不当な引数であるため、0 とする。
    }
    int val[] = new int[_k];

    //(1)
    for(int j=0; j<_k; ++j) val[j] = 0;
    //(2)
    for (int j=0; j<Math.pow(_n,_k); ++j){
      incrementVal(val,0);
      //(3)
      if (isNoDup(val) == true) {
        //(4)
        ArrayList<Integer> Nums = new ArrayList<Integer>();
        for(int jj=0; jj<_k; ++jj) {
          Nums.add(val[jj]);
        }
        _E.add(Nums);
      }
    }

  }// end of Constructor Permutation()


/*
 * 目的: n,k へのアクセッサ
 * 戻り値: n,k の値
 */
  public int getN(){
    return _n;
  }
  public int getK(){
    return _k;
  }// end of getN,getK


/*
 * 目的: 指定番目(0..(nPk-1)) の順列を返す。
 * 引数: 添字, 順列を格納する配列
 */
  public void getElements(int count, int val[]){
    ArrayList<Integer> temp = new ArrayList<Integer>();
    if(count < getSize()){
      temp = _E.get(count);
      for(int i=0; i<temp.size(); ++i){
        val[i]=temp.get(i);
      }
    }
  }// end of getElements


/*
 * 目的: 引数で指定された順番の順列を返す
 * 引数: 生成した順列の添字
 * 戻り値: CSV 形式の文字列
 */
  public String toString(int count){
    ArrayList<Integer> temp = new ArrayList<Integer>();
    temp = _E.get(count);
    String result = "";
    for (int i=0; i<_k; ++i){
      result += temp.get(i);
      if (i != _k-1) result += ",";
    }
    return result;
  }// end of toString


/*
 * 目的: 順列の数を返す
 * 戻り値: 順列の数
 */
  public long getSize(){
    long size = 0;
    size = factorial(_n) / factorial(_n - _k);
    return size;
  }// end of getSize


  //- - - - - - - - - - - - - - - - - - - - - - -
  //PRIVATE METHODS
  //- - - - - - - - - - - - - - - - - - - - - - -

  //階乗計算
  private long factorial(int n) {
    if ( n == 0 ) {// n が0 になったら1 を返して脱出 
      return 1;
    } else {
      return n*factorial(n-1);// 再帰呼び出し
    }
  }


  //n 進数k 桁の数値をインクリメント
  private void incrementVal(int val[],int cnt){
    if(cnt >= _k){
      //k 桁を超える桁上がり。全桁をゼロに。
      for(int i=0; i <_k; ++i) val[i]=0;
    } else {
      //インクリメント操作
      ++val[cnt];
      if(val[cnt]>=_n){
        //桁上がり
        for(int i=0; i <= cnt; ++i) val[i]=0;
        incrementVal(val,cnt+1);
      }
    }
  }// end of incrementVal


  //引数の配列にセットされた数値の重複チェック
  //重複があればfalse を返す。
  private boolean isNoDup(int val[]){
    int num[] = new int[_n];
    for(int i=0; i<_n; ++i) num[i]=0;
    for(int j=0; j<_k; ++j){
      ++num[val[j]];
      if (num[val[j]] > 1) return false;
    }
    return true;
  }// end of isNoDup


} // end of class Permutation

今回はここまで

演習問題はいかがでしたか?今回の問題の解説用ソースコードを作るに当たって、いくつものアドバイスをいただく機会がありました。その中にはLispやPythonで順列を生成するものがあり、それらは大変シンプルなコードでした。どちらも10行から20行程度です。Java言語でクラスを作成するということを考えると、とてもそのような行数では同様のものを作ることが難しいと思いますが、より短くシンプルで、かつ理解しやすいコードを目指すことの大切さを感じました。

次回は今回作成したPermutationクラスを利用して、Combinationクラスを作ってみましょう。

おすすめ記事

記事・ニュース一覧