整数划分(递归、动态规划、回溯)

问题:

将正整数n表示成一系列的正整数之和,n=n1+n2+…+nk(n1>=n2>=…>=nk>=1,k>=1).正整数n的这种表示称为正整数n的划分。

1.递归

分析:

假设能划分的最大整数为m,用q(n,m)来表示其能划分的种类数,则分为两种情况:n<=m、n>m(n<m和n=m合并为一种)。

当n<=m时,q(n,m)=q(n,n),计算q(n,n)可将其存在最大划分数m和不存在m的情况,存在最大划分数m的情况只有一种,不存在最大划分数的有q(n,n-1)。两者相加即可。

当n>m时,存在最大划分数的时候,还有n-m个数可划分,最大划分数仍然存在m,则存在q(n-m,m)种;当不存在最大划分数的时候,存在q(n,m-1)。两者相加即可。

综上所述:

代码:

#include<stdio.h>
int q(int n,int m){
	if(n<1||m<1)
		return 0;
	if(n<=m)
		return 1+q(n,n-1);
	if(n>m)
		return q(n-m,m)+q(n,m-1);		
}
int main(){
	int n,m;
	printf("请输入要划分的正整数和最大划分数:\n");
	scanf("%d%d",&n,&m);  //输入要划分的数字和最大划分数 
	printf("%d",q(n,m));	
	return 0;
}

2. 动态规划

分析:

分析与递归一致,需要注意初始条件。

在这里,由一个a[N][N]的二维数组来存放每一个数的划分种类数,边界条件应该是a[i][1]和a[1][i]均为1。a[n][m]即所要求的种类数。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=500;
int a[N][N];
int main(void){
	int n,m;
	cout<<"请输入要划分的数和其能划分的最大数字:"<<endl; 
	cin>>n>>m;
	//边界条件
	for(int i=1;i<=n;i++)
		a[i][1]=1;
	for(int i=1;i<=m;i++){
		a[1][i]=1; 
	}
	for(int i=2;i<=n;i++)
		for(int j=2;j<=m;j++){
			if(i>j)
				a[i][j]=a[i-j][j]+a[i][j-1];
			else
				a[i][j]=a[i][i-1]+1;	 
		}
	cout<<"有"<<a[n][m]<<"种情况";	
	return 0;
}

3.回溯(dfs+剪枝)--》仅考虑了n=m的情况

dfs基本思想:每一次都能划分从1-n,如果其值加起来等于n,就将其拿出来。(时间和空间复杂度都太大了,需要适当剪枝处理)

1)将所划分值从大到小排序

分析:

需要三个参数m,i,h来表示要划分的数,位置,最大可划分的数。可划分的数j从h-1开始列举,获得一个划分数之后,需要划分的数m=m-j;为了使得列举不重复,最大可划分的数h必须要小于等于上一个数字,但是又不能超过紧接着还需要划分的数,所以h=min(m-j,j)。跳出循环的条件即是m==0时。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1000;
int x[N];   //解向量 
int m,cnt=0;
//m划分的数,i位置 ,h最大划分数
//从大到小排列 
void output(int i){
	printf("%-2d:",cnt);
	cout<<m<<"=";
	for(int k=1;k<i-1;k++)
		cout<<x[k]<<"+";
	cout<<x[i-1]<<endl;
}
void huafen(int m,int i,int h){
	if(m==0){
		cnt++;
		output(i);
	}else{
		for(int j=h;j>=1;j--){
			x[i]=j;
			huafen(m-j,i+1,min(m-j,j)); 
		}
	} 
}
int main(void){
	cin>>m;
	huafen(m,1,m);
	return 0;
} 

结果示例:

2)将所划分值从小到大排序

分析:

与划分值从大到小差不多。需要三个参数m,i,h来表示要划分的数,位置,最小可划分的数。可划分的数j从h-m开始列举,获得一个划分数之后,m=m-j,h为了使得列举不重复,必须要大于等于上一个数字,所以h=j。m跳出循环的条件即是m==0时。

值得注意的是,能够接着递归下去的条件是m-j>=j或者m-j==0。m-j>=j是因为m-j为还需要划分的数,j为最小的划分值,不可能(还需要划分的数)<(最小的划分值),这样子递归无法进行。m-j==0可以接着递归的原因是使得下一次能进入m==0,然后正确输出。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=500;
int x[N],m; //x为解向量
int cnt=0;
void output(int i){
	printf("%-2d:",cnt);
	cout<<m<<"=";
	for(int k=1;k<i-1;k++)
		cout<<x[k]<<"+";
	cout<<x[i-1]<<endl;
}
//m划分的数,i位置,h最小划分数 
void huafen(int m,int i,int h){
	if(m==0){
		cnt++;
		output(i);
	}else{
		for(int j=h;j<=m;j++){
			x[i]=j;
			if(m-j>=j||m-j==0){
				huafen(m-j,i+1,j);			
			}
				
		}
	}
} 
int main(void){
	cin>>m;
	huafen(m,1,1); 
	return 0;
} 

结果示例:

疑问? 

为什么在这里不用恢复m、h的状态?

答:在这里,我们在需要回溯的地方(huafen()函数)对其参数进行修改,相当于形参,在返回时对外面的数据不会产生影响(除非用了指针,这里没用指针哈哈哈)。

over,终于搞懂了,撒花!!!

自己写的,有啥问题,欢迎指正!嘿嘿。

  • 4
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值