1、问题
1.1 袋中取球
袋子里有4个球,分别编号为{1, 2, 3, 4},依次取出,按照取出的先后从左至右排列,会得到一个不同的数字(如 1 2 3 4,有点像双色球开奖),求输出所有的数字组合。
1.2 不重复的数
有4个数字{0, 1, 2, 3},问用这4个数字能组成多少种不能的4位数(0123也算,因为我们也可以用{1, 2, 3, 4})?
2、排列组合
上述2个问题实际都是一个排列组合的问题,首先我们来计算一共有多少种组合
取第1个球 |
取第2个球 |
取第3个球 |
取第4个球 |
|
能取球的次数 |
4 |
3 |
2 |
1 |
组合数 = 4 * 3 * 2 * 1 = 4!
即n个球的组合总数 = n!
我们可以验证下:
1个球的组合:{0} = 1
2个球的组合:{0, 1}, {1, 0} = 2
3个球的组合:{0, 1, 2}, {0, 2, 1}, {1, 0, 2}, {1, 2, 0}, {2, 0, 1}, {2, 1, 0} = 6
3、如何输出线型排列组合
根据我们刚才验证组合的过程,我们发现我们在取第n个球时,我们总是从最小的数字0开始,如果该球已被取过了,则跳过继续遍历下一个数字,不管是取第0个球还是第n个,都是如此,因此我们只要知道第n个球的数字,那么下一个球
的数字都可以用相同的方法求出来,直到只剩一个球为止。
算法公式f(x):
1> 当取第x个球时,假设前x - 1个球已取{a, b, c, ...}个数字,放在容器S中(当然,开始时,容器S是空的)
2> 从{0, ..., n-1}循环
判断第i个数是否已被取过,即是否在S中
如果在,则跳过这个数
如果不在,则取出这个球,并添加到容器S中,表示该球已被占用
这时,该输出下一个球(x+1)个球了,我们用同样的算法f(x+1)
当x之后所有的球都被取出后,这时我们要继续循环下一个不在容器中的数字,但要注意,因为第x个球下次循环的数字将要改变了(i→i+1),球i将不再被占用,以供取后续的球之用,所以,我们要讲i这个球从占用容器S中取出
3> 很明显,这是一个递归过程,算法是有有限性的,那么什么时候这个递归停止呢
我们知道,当取出后一个球(n-1)时,袋子里就没有球了,再取下一个时,本次递归就可以结束了
4. 算法
注意:根据编程习惯,第一个球的编号和首次取球都是以0开始
//每个递归,都遍历一遍 vectorBall 中的空值,然后下次递归,递归完后,将原来设定的值设为0 //nIndex = 取第x个球 void CPermutation::SetBallNum(int nIndex, std::vector<int>& vectorBall, std::vector<int> vectorBallSet, std::vector<std::vector<int>>& vectorPermutation) { int nEmptyBallNum = 0; int nBallNum = vectorBall.size(); if (nBallNum == nIndex) { vectorPermutation.push_back(vectorBallSet); return; } for (int i = 0; i < nBallNum; i++) { //第x个球,最多有 nBallNum - x 个空位 if (nEmptyBallNum >= (nBallNum - nIndex)) { break; } //如果找到空值 if (0 == vectorBall[i]) { vectorBall[i] = 1; nEmptyBallNum++; vectorBallSet[nIndex] = i; SetBallNum(nIndex + 1, vectorBall, vectorBallSet, vectorPermutation); vectorBall[i] = 0; //将尝试完的值设为0,再尝试下一个 } } } //有n个编号为{0, n-1}的小球,依次取出,按照取出的先后从左至右排列,会得到一个不同的数字,打出所有的数字组合 //类似问题,用{0, n-1}个数,能组成多个数字不重复的n位数 bool CPermutation::PermutationBall(int nBallNum /* = 4 */) { printf("Ball num = %d\n", nBallNum); std::vector<int> vectorBallSet(nBallNum, 0); std::vector<int> vectorBall(nBallNum, 0); std::vector<std::vector<int>> vectorPermutation; SetBallNum(0, vectorBall, vectorBallSet, vectorPermutation); PrintPermutation(vectorPermutation); return true; }