任何一个大于 1的自然数 n,总可以拆分成若干个小于 n的自然数之和,试求 n的所有拆分。
用不完全归纳法
n =2 可拆分成 2 =1 +1
n =3 可拆分成 3 =1 +2 =1 +1 +1
n =4 可拆分成 4 =1 +3 =1 +1 +2 =1 +1 +1 +1 =2 +2
……
n =7 可拆分成 7 =1 +6
=1 +1 +5
=1 +1 +1 +4
=1 +1 +1 +1 +3
=1 +1 +1 +1 +1 +2
=1 +1 +1 +1 +1 +1 +1
=1 +1 +1 +2 +2
=1 +1 +2 +3
=1 +2 +4
=1 +2 +2 +2
=1 +3 +3
=2 +5
=2 +2 +3
=3 +4
方法1:递归
用数组 a 存储完成 n 的一种拆分。从上面不完全归纳法的分析 n =7 时,按 a[1]分类,有a[1]=1,a[1]= 2,…,a[1]= n/2,共 n/2 大类拆分。在每一类拆分时,a[1]= i ,a[2]= n - i ,从 k=2,继续拆分从 a[k]开始,a[k]能否再拆分取决于 a[k]/2 是否大于等于 a[k-1]。递归过程的参数 t 指向要拆分的数 a[k],于是有算法:
#include<iostream>using namespace std;
int a[100];
static int sum=0;
void Split(int t)
{
int i;
for(i = 1; i <= t; i++)
{
cout<< a[i]<<"\t";
}
++sum;
cout<< endl;
int j = t;//j是t的副本,改变j,不影响t,每一轮的a[t]值是不变的
int L = a[j];
for(i = a[j-1]; i <= L/2; i++)//a[t]处取值的范围为a[t-1]~a[t]/2
{
a[j] = i;
a[j+1] = L - i;
Split(j+1);
}
/* for(i = a[t-1]; i <=a[t]/2; i++)//此种写法不正确
{ a[t] = i;
a[t+1] = a[t] - i;
Split(t+1);
}*/
}
void SplitNum(int n)
{
int i;
for(i = 1; i <= n/2; i++)
{
a[1] = i;
a[2] = n - i;
Split(2);
}
}
void main()
{
const int n=7;
SplitNum(n);
cout<<"总数"<<sum<<endl;
}
运行结果:
原链接:http://jimobit.blog.163.com/blog/static/2832577820071021112229350/
方法二:回溯法
针对所给问题,定义问题的解空间;
如本题对5的拆分来说,1<=拆分的数<=5。 确定易于搜索的解空间结构;
如本题对5的拆分来说,用x[]数组来存储解,每个数组元素的取值范围都是1<=拆分的数<=5,
从1开始搜索直到5。 搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。
如本题对5的拆分来说,为了避免重复,x>=x[j](i>j),如x[]={2,3}满足条件而x[]={3,2}就不满足条件不是可行解即无效。
#include<stdio.h>
#include<stdlib.h>
//功能:回溯法进行自然数的拆分
void splitN(int n,int m);// n是需要拆分的数,m是拆分的进度。
int x[1024]={0},total=0 ;// total用于计数拆分的方法数,x[]用于存储解
void main()
{
int n ;
printf("please input the natural number n:");
scanf("%d",&n);
splitN(n,1);
printf("There are %d ways to split natural number %d. ",total,n);
}
void splitN(int n,int m)
{//n是需要拆分的数,m是拆分的进度
int rest,i,j;
for(i=1;i<=n;i++)
{//从1开始尝试拆分
if(i>=x[m-1])
{//拆分的数大于或等于前一个从而保证不重复
x[m]=i; // 将这个数计入结果中
rest=n-i ;// 剩下的数是n-i,如果已经没有剩下的了,并且进度(总的拆分个数)大于1,说明已经得到一个结果了
if(rest==0&&m>1)
{
total++;
printf("%d\t",total);
for(j=1;j<m;j++)
{
printf("%d+",x[j]);
}
printf("%d ",x[m]);
printf("\n");
}
else
{
splitN(rest,m+1);// 否则将剩下的数进行进度为m+1拆分
}
x[m]=0;// 取消本次结果,进行下一次拆分。环境恢复,即回溯
}
}
}
运行结果:
方法三:非递归
#include "stdio.h"
//功能:非递归实现自然数的拆分
void Divinteger(int n)
{
if (n==1) //处理输入值为1的情况,因为如果输入1,就不用再划分
{
printf("只有一种划分:1\n");
return;
}
if (n == 2) //处理输入值为2 的情况,如果输入为2,只要写成1+1就可以了。
{
printf("第1种划分:2\n");
printf("第2种划分:1 1\n");
printf("一共有2种划分!\n");
return;
}
int *a =new int(n); //定义动态数组,因为当n大于2时,如3=2+1,这时就需要对2进行相似划分,其大小是不固定的,因而用动态数组。
int div=0; //每一行划分的数组下标
int k=1;//记录种类的值。
a[0] = n - 1;
a[1] = 1;
div = 2; //第二次一定划分为2个值。n-1和1
printf("第%2d种划分:%d \n",1,n);
int i;
do{
k++;
printf("第%2d种划分:",k);
printf("%d", a[0]);
for (i = 1; i < div; i++)
printf(" %d", a[i]);
printf("\n");
int s = 0;
do{
s += a[--div];
}while (div >= 0 && a[div] == 1); //这里用来找到非1的可以继续划分的值,如5 1,通过这一部找到5;
if (div == -1)
break;
int d = a[div] - 1; //判断是否还有数字可以继续划分。
if (d == 1) //这句户用来为下一个循环做准备,
{
while (s > 0) //s存放着下一行一共有几个值
{
a[div++] = 1; //将下一行的值全部赋值为1.等待下一个循环输出。
s--;
}
}
else //表示还有值可以继续划分
{
do{
a[div++] = d;// 相当于向右移动一位,进入下一次划分(对4)
s -= d;
}while (s >= d); //得出a[0]=4
if (s != 0)
a[div++] = s; //得出a[1]=2 div=2
}
}while (1);
printf("\n一共有 %d 种方法!\n\n",k);
}
void main()
{
int n;
printf("输入你要求的划分整数值:\n");//输入n值
scanf("%d", &n);
Divinteger(n); //调用划分函数
}
原链接:http://blog.sina.com.cn/s/blog_5052ef9d0100dcf8.html