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

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

前回は順列を生成するクラスを作成しました。nPkの公式を知っていれば、順列の数がどれだけなのか計算が出来ます。しかし、順列を全て列挙するというのは、人の手によると大変な作業です。nがちょっと大きくなるとすぐに手に負えなくなります。コンピュータのありがたさがわかるシーンです。今回は組合せを生成するクラスを作ってみましょう。前回作成したPermutationクラスを活用して、出来るだけシンプルなクラスにしましょう。2つのクラスに共通するクラスを抽出すれば更にシンプルなコードになるのですが、今回はそこまで要求しません。Permutationクラスをそのまま利用してください。

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

1からnまでの整数からk個取り出す場合を考えましょう。そして、取り得る場合を全て作成し、画面に出力するプログラムを作りましょう。組合せを作成するのは、順列のように簡単ではありません。重複する場合を取り除かなければならないからです。高速に処理する方法、メモリやディスクの使用量を抑える方法など様々考えられますが、今回は地味でも遅くてもかまいませんから、最もシンプルな方法で作成しましょう。

出力の形式は

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

としましょう。

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

ソースコード:Combination.javaのスケルトン
/*
 * class Combination
 * 目的: 組合せに必要なメソッドを備える
 */

import java.util.ArrayList;

class Combination {

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

/*
 * 目的: コンストラクタ。組合せに必要な変数を用意し、
 * 組合せを作成する。
 * 引数: nCk のn,k
 */
  Combination(int n,int k){
    _n = n; _k = k;

    /*
      -------------------------------------------
      
     (1)Permutationクラスを利用して順列を作成する。
     (2)Permutationクラスで生成した順列を1つ1つ読み込む。
     (3)読み込んだ順列がユニークな組合せならば_Eに保持。
      -------------------------------------------
     */


  }// end of Constructor Combination()


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


/*
 * 目的: 指定番目(0..(nCk-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 Combination

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

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

import java.util.ArrayList;

class TestCombination {

  public static void main(String[] args) {

    int n=3;
    int k=2;

    Combination c = new Combination(n,k);

    System.out.println("C("+c.getN()+","+c.getK()+")="
                       +c.getSize());

    System.out.println("全ての組合せを表示します。");

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

    System.out.println("直接値を取得");
    int temp[] = new int[k];
    for(int i=0; i<c.getSize(); ++i){
      String result = "";
      c.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 TestCombination

TestCombination.java の実行結果は次のようになるはずです。

C>java TestCombination
C(3,2)=3
全ての組合せを表示します。
toString を利用
0:0,1
1:0,2
2:1,2
直接値を取得
0:0,1
1:0,2
2:1,2

解説

それでは、目的の動作をするコードの完成版を示します。Permutationクラスで順列を生成し、その中からユニークな組合せをピックアップしていく仕組みです。何の工夫もない、いたって単純な方法です。順列は大変大きな数になりやすいので、ユニークな要素のピックアップには工夫のしどころが多くあります。発展課題として取り組んでみてはいかがでしょうか。

ソースコード:Combination.java完成版
/*
 * class Combination
 * 目的: 組合せに必要なメソッドを備える
 */

import java.util.ArrayList;

class Combination {

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

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

    //(1)
    Permutation _p = new Permutation(_n,_k);
    //(2)
    for (int i=0; i<_p.getSize(); ++i){
      _p.getElements(i,temp);
      //(3)
      if(isUniqueElement(temp) == true){
        ArrayList<Integer> Nums = new ArrayList<Integer>();
        for(int j=0; j<_k; ++j) {
          Nums.add(temp[j]);
        }
        _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..(nCk-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) / factorial(_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);// 再帰呼び出し
    }
  }


  //_E に同じ組合せがないかチェック
  //重複があればfalse を返す。ユニークならtrue。
  private boolean isUniqueElement(int val[]){
    java.util.Arrays.sort(val);
    int temp[] = new int[_k];
    for(int i=0; i<_E.size(); ++i){
      getElements(i,temp);
      java.util.Arrays.sort(temp);
      boolean match = true;
      for(int j=0; j<temp.length; ++j){
        if(val[j] != temp[j]){
          match = false;
          break;//ほんのちょっと時間節約
        }
      }
      //_E のi 番目の要素がval と一致したら終了
      if(match == true) return false;
    }
    //val は_E の全ての要素と一致しなかった
    return true;
  }// end of isUniqueElement


} // end of class Combination

今回はここまで

Permutationクラスを利用することで、Combinationクラスのコンストラクタはシンプルになりました。しかし、全体にわたってCombinationクラスのメソッドはPermutationクラスと共通しています。共通部分をスーパークラスに抽出し、より洗練された美しいコードを目指すのは、良い発展課題となるでしょう。

これで、確率の数学をコンピュータで学習するための道具がかなりそろいました。さいころ、そして順列と組合せのクラス。いろんなシミュレーションが出来そうですね。次回から、いよいよ本格的に確率の数学に取り組みます。お楽しみに。

おすすめ記事

記事・ニュース一覧