阮小二买彩票

题目描述

在同学们的帮助下,阮小二是变的越来越懒了,连算账都不愿意自己亲自动手了,每天的工作就是坐在电脑前看自己的银行账户的钱是否有变多。可是一段时间观察下来,阮小二发现自己账户的钱增长好慢啊,碰到节假日的时候连个铜板都没进,更郁闷的是这些天分文不进就算了,可恨的是银行这几天还有可能“落井下石”(代扣个人所得税),看着自己账户的钱被负增长了,阮小二就有被割肉的感觉(太痛苦了!),这时阮小二最大的愿望无疑是以最快的速度日进斗金,可什么方法能够日进斗金呢?抢银行(老本行)?不行,太危险,怕有命抢没命花;维持现状?受不了,搂钱太慢了!想来想去,抓破脑袋之后,终于想到了能快速发家致富的法宝----买彩票,不但挣了钱有命花,运气好的话,可以每天中他个几百万的,岂不爽哉!抱着这种想法,阮小二开始了他的买彩票之旅。想法是“好的”(太天真了OR 太蠢了),可是又发现自己的数学功底太差,因为不知道数字都有哪些组合排列?那现在就请同学们写个递归程序,帮助阮小二解决一下这个问题吧!

输入

不超过6位数的正整数N,注意:构成正整数N的数字可重复

输出

组成正整数N的所有位数的全排列,这些排列按升序输出,每个排列占一行。

注意:输出数据中不能有重复的排列

样例输入

123

样例输出

123
132
213
231
312
321
#include<bits/stdc++.h>
using namespace std;
int A[10],P[10];
void print_permutation(int n, int *P,int *A, int cur) {
	if(cur == n) { //递归边界
		for(int i = 0; i < n; i++) printf("%d", A[i]);
		printf("\n");
	}
	else 
		for(int i = 0; i < n; i++) {
			if(!i||P[i]!=P[i-1]){//!i是针对i==0的情况 
				int c1 = 0, c2 = 0;
				for(int j = 0; j < cur; j++) if(A[j] == P[i]) c1++;
				for(int j = 0; j < n; j++) if(P[i] == P[j]) c2++;
				if(c1 < c2) {
					A[cur] = P[i];
					print_permutation(n, P, A, cur+1);
				}
			}	
		}
}
int main(){
	char s[10];
	int j;
	gets(s);
	for(j=0;j<strlen(s);j++){
		P[j]=s[j]-'0';
	}
	sort(P,P+strlen(s));
	print_permutation(strlen(s),P,A,0);
	return 0; 
}

代码取自刘汝佳的紫书,原文如下:

有没有想过如何打印所有排列呢?输入整数n,按字典序从小到大的顺序输出前n个数的
所有排列。前面讲过,两个序列的字典序大小关系等价于从头开始第一个不相同位置处的大
小关系。例如,(1,3,2) < (2,1,3),字典序最小的排列是(1, 2, 3, 4,…, n),最大的排列是(n, n-1,
n-2,…, 1)。n=3时,所有排列的排序结果是(1, 2, 3)、(1, 3, 2)、(2, 1, 3)、(2, 3, 1)、(3, 1, 2)、
(3, 2, 1)。
7.2.1 生成1~n的排列
我们尝试用递归的思想解决:先输出所有以1开头的排列(这一步是递归调用),然后
输出以2开头的排列(又是递归调用),接着是以3开头的排列……最后才是以n开头的排
列。
以1开头的排列的特点是:第一位是1,后面是2~9的排列。根据字典序的定义,这些2
~9的排列也必须按照字典序排列。换句话说,需要“按照字典序输出2~9的排列”,不过需
注意的是,在输出时,每个排列的最前面要加上“1”。这样一来,所设计的递归函数需要以
下参数:
已经确定的“前缀”序列,以便输出。
需要进行全排列的元素集合,以便依次选做第一个元素。
这样可得到一个伪代码:
void print_permutation(序列A, 集合S){
    if(S为空) 输出序列A;
    else 按照从小到大的顺序依次考虑S的每个元素v{
          print_permutation(在A的末尾填加v后得到的新序列, S-{v});
   }
}
暂时不用考虑序列A和集合S如何表示,首先理解一下上面的伪代码。递归边界是S为空
的情形,这很好理解:现在序列A就是一个完整的排列,直接输出即可。接下来按照从小到
大的顺序考虑S中的每个元素,每次递归调用以A开头。
下面考虑程序实现。不难想到用数组表示序列A,而集合S根本不用保存,因为它可以
由序列A完全确定——A中没有出现的元素都可以选。C语言中的函数在接受数组参数时无法
得知数组的元素个数,所以需要传一个已经填好的位置个数,或者当前需要确定的元素位置
cur,代码如下:
void print_permutation(int n, int* A, int cur) {
    if(cur == n) { //递归边界
        for(int i = 0; i < n; i++) printf("%d ", A[i]);
        printf("\n");
    }
   else for(int i = 1; i <= n; i++) { //尝试在A[cur]中填各种整数i
       int ok = 1;
       for(int j = 0; j < cur; j++)
          if(A[j] == i) ok = 0; //如果i已经在A[0]~A[cur-1]出现过,则不能再选
          if(ok) {
              A[cur] = i;
              print_permutation(n, A, cur+1); //递归调用
         }
    }

}
循环变量i是当前考察的A[cur]。为了检查元素i是否已经用过,上面的程序用到了一个
标志变量ok,初始值为1(真),如果发现有某个A[j]==i时,则改为0(假)。如果最终ok仍
为1,则说明i没有在序列中出现过,把它添加到序列末尾(A[cur]=i)后递归调用。
声明一个足够大的数组A,然后调用print_permutation(n, A, 0),即可按字典序输出1~n的
所有排列。
7.2.2 生成可重集的排列
如果把问题改成:输入数组P,并按字典序输出数组A各元素的所有全排列,则需要对
上述程序进行修改——把P加到print_permutation的参数列表中,然后把代码中的if(A[j] == i)
和A[cur] = i分别改成if(A[j] == P[i])和A[cur] = P[i]。这样,只要把P的所有元素按从小到大的
顺序排序,然后调用print_permutation(n, P, A, 0)即可。
这个方法看上去不错,可惜有一个小问题:输入1 1 1后,程序什么也不输出(正确答案
应该是唯一的全排列1 1 1),原因在于,这样禁止A数组中出现重复,而在P中本来就有重
复元素时,这个“禁令”是错误的。
一个解决方法是统计A[0]~A[cur-1]中P[i]的出现次数c1,以及P数组中P[i]的出现次数
c2。只要c1<c2,就能递归调用。
else for(int i = 0; i < n; i++) {
int c1 = 0, c2 = 0;
for(int j = 0; j < cur; j++) if(A[j] == P[i]) c1++;
for(int j = 0; j < n; j++) if(P[i] == P[j]) c2++;
if(c1 < c2) {
A[cur] = P[i];
print_permutation(n, P, A, cur+1);
}
}
结果又如何呢?输入1 1 1,输出了27个1 1 1。遗漏没有了,但是出现了重复:先试着把
第1个1作为开头,递归调用结束后再尝试用第2个1作为开头,递归调用结束后再尝试用第3
个1作为开头,再一次递归调用。可实际上这3个1是相同的,应只递归1次,而不是3次。
换句话说,我们枚举的下标i应不重复、不遗漏地取遍所有P[i]值。由于P数组已经排过
序,所以只需检查P的第一个元素和所有“与前一个元素不相同”的元素,即只需在“for(i = 0; i
< n; i++)”和其后的花括号之前加上“if(!i || P[i] != P[i-1])”即可。
至此,结果终于正确了。

对于if(!i || P[i] != P[i-1])的个人理解是这样的,比如输入112,即P[0]=1 P[1]=1 P[2]=2,其中!i是针对i=0时的情况,而P[i] != P[i-1],一开始觉得i=1时会因为P[0]==P[1]而被跳过,但后来经过观察调试,发现关键如下

for(int i = 0; i < n; i++) {
	if(!i||P[i]!=P[i-1]){//!i是针对i==0的情况 
		int c1 = 0, c2 = 0;
		for(int j = 0; j < cur; j++) if(A[j] == P[i]) c1++;
		for(int j = 0; j < n; j++) if(P[i] == P[j]) c2++;
		if(c1 < c2) {
			A[cur] = P[i];
			print_permutation(n, P, A, cur+1);
		}
	}	
}

比如第一次循环时A[0]=1,并进入下一次print_permutation(n, P, A, cur+1),还是从i=0开始循环,在这里因为c1恰好==c2,所以A[1]=1,再进入下一个print_permutation(n, P, A, cur+1),i=0时,因为c1>c2(因为一共只有两个1)不符合条件,而i=2时,不满足P[i]!=P[i-1]的条件,直到i=3,所以这样就很好的避免了重复。

C++的STL中提供了一个库函数next_permutation。看看下面的代码片段,就会明白如何使用它了。

#include<bits/stdc++.h>
using namespace std;
int main(){
	char s[10];
	int j,P[10],len;
	gets(s);
	len=strlen(s);
	for(j=0;j<len;j++){
		P[j]=s[j]-'0';
	}
	sort(P,P+len);
	do {
		for(int i = 0; i <len ; i++) printf("%d", P[i]); //输出排列p
		printf("\n");
	} while(next_permutation(P, P+len)); 
	return 0; 
}

关于next_permutation函数 https://blog.csdn.net/han_hhh/article/details/81055320

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值