重複無しの組み合わせと重複ありの組み合わせの一覧を取得するプログラム

組み合わせの一覧を取得するプログラムを書きました。

Combination::Combination

Combinationは{}_n \mathrm{C} _r通りすべてのパターンを取得します。

m_flag[index]に、index番目(計{}_n \mathrm{C} _r個=m_size)のビットフラグ(int型)を収納します。

index=0では、下位ビットからr個埋めて、次からのパターンでは上位ビットに繰り上げていきます。
(たとえば、r=5の場合、0011111からスタートします。次は0101111です。)

“結果”を見て具体的に考えながら、アルゴリズムを追うと分かりやすいでしょう。

//r個をn人で分ける。1人1個まで
Combination::Combination(int n, int r){
	m_n = n;
	m_size = PossibleOutcomes::Combination(n, r);
	m_flag = new int[m_size];
	for (int i=0; i<m_size; i++){
		m_flag[i] = 0;
	}

	m_flag[0] = (1<<r) - 1;//下位の人から順に、1個ずつ渡す(計r個渡す)

	for (int i=1; i<m_size; i++){
		m_flag[i] = m_flag[i-1];
		int j = 0;
		while (!(m_flag[i] & (1<<j))){//持っている人を見つける=j
			j++;
		}
		int k = j;//下位から見たときに、最初に持っている人
		do { 
			j++;
		} while (m_flag[i] & (1<<j));//持っていない人を見つける=j
		int m = (1<<j);
		m_flag[i] |= m;//持っていない人に渡す
		m_flag[i] &= ~(m-1);//下位の持っていた人達から没収
		m_flag[i] |= (1<<(j-k-1)) -1;//残った分を下位の人達に渡す
	}
}

main:

int main() {
	Combination obj(5,2);
	for (int i=0; i<obj.GetSize(); i++){
		std::cout << "[" << i << "]" << std::flush;
		for(int j=obj.GetN()-1; j>=0; j--){
			std::cout << " " << obj.Get(i,j) << std::flush;
		}
		std::cout << std::endl;
	}
	int a;
	std::cin >> a ;
	return 0;
}

結果は次のようになります。(n=5, r=2)

Combination結果

RepeatedCombination::RepeatedCombination

重複有りの組み合わせでは、1人何個でも持てるので0, 1以上の情報を持つ必要があります。

そこで、int型の配列を使います。

全パターンの配列を取得したいので、結局2元配列を使うことになります。

//r個をn人で分ける。1人何個でもよい
RepeatedCombination::RepeatedCombination(int n, int r){
	m_n = n;
	m_size = PossibleOutcomes::RepeatedCombination(n, r);
	m_share = new int*[m_size];
	for (int i=0; i<m_size; i++)
		m_share[i] = new int[n];

	m_share[0][0] = r;
	for (int i=1; i<m_n; i++){
		m_share[0][i] = 0;
	}

	for (int i=1; i<m_size; i++) {
		for (int j=0; true/*j<m_n*/; j++) {
			if(m_share[i-1][j] > 0) {//0個持ちでない人を見つける[j]
				m_share[i][0] = m_share[i-1][j] - 1;//その人の持つ個数から1個引いた残りを最下位の人([0]の人)に渡す
				for(int k=1; k<=j; k++)
					m_share[i][k] = 0;//取り上げられた人以下の人は0個持ちに([0]の人は除く)
				m_share[i][j+1] = m_share[i-1][j+1] + 1;//取り上げた人より1つ上の人は1個受け取る[j+1]
				for(int k=j+2; k<m_n; k++)
					m_share[i][k] = m_share[i-1][k];//それ以上の人は変わらず
				break;
			}
		}
	}
}

結果:(n=5, r=2)

Repeated結果

以下はクラス宣言のファイルと階乗計算などの関数です。

class PossibleOutcomes {
protected:
	__int64 Factorial(int x) const;//階乗計算
	__int64 Junretsu(int x, int k) const;//順列
	int Combination(int n, int r) const;//組み合わせ
	int RepeatedCombination(int n, int r) const;//重複組み合わせ
};

//n個からr個選ぶ。同じものは選べない=重複無しの組み合わせ
//(r個をn人で分ける。1人1個まで)←関数中のコメントはこの考え方
class Combination : public PossibleOutcomes {
public:
	Combination(int n, int r);
	~Combination();
	int Get(int index) const;//int型のビットフラグを取得
	int Get(int index, int n) const;//n番目の人がもつかどうか。持っていれば1
	int GetSize() const;//パターン数を取得
	int GetN() const;
protected:
	int m_n;//保持人数
	int m_size;//パターン数
	int *m_flag;
};

//n種類のものからr個選ぶ=重複組み合わせ
//(r個をn人で分ける。1人何個でも)←関数中のコメントはこの考え方
class RepeatedCombination : public PossibleOutcomes {
public:
	RepeatedCombination(int n, int r);//r個をn人で分ける
	~RepeatedCombination();
	int Get(int index, int n) const;//n番目の人がもつ個数を取得
	int GetSize() const;//パターン数を取得
	int GetN() const;
protected:
	int m_n;//保持人数
	int m_size;//パターン数
	int **m_share;
private:
};
__int64 PossibleOutcomes::Factorial(int x) const{
	__int64 temp = 1;
	for(; x>0; x--)
		temp *= x;
	return temp;
}

__int64 PossibleOutcomes::Junretsu(int x, int k) const{
	__int64 temp = x;
	for(int i=1; i<k; i++)
		temp *= x-i;
	return temp;
}

int PossibleOutcomes::Combination(int n, int r) const{
	int temp = 1;
	if (r > n - r)
		r = n - r;
	for (int i=1; i<=r; i++)
		temp = (temp*(n-r+i)) / i;
	return temp;
}

int PossibleOutcomes::RepeatedCombination(int n, int r) const{
	return Combination(n+r-1, r);
}

公開日:2014年6月29日 最終更新日:2014年7月26日