整数划分问题
正整数s(简称为和数)的划分(又称分划或拆分)是把s分成为若干个正整数(简称为零数或部分)之和,划分式中允许零数重复,且不记零数的次序。
试求s=12共有多少个不同的划分式?展示出所有这些划分式。
1 整数划分递推设计
1.探索划分的递推关系
为了建立递推关系,先对和数k较小时的划分式作观察归纳:
k=2:1+1;2
k=3:1+1+1;1+2;3
k=4:1+1+1+1;1+1+2;1+3;2+2;4
k=5:1+1+1+1+1;1+1+1+2;1+1+3;1+2+2;1+4;2+3;5
由以上各划分看到,除和数本身k=k这一特殊划分式外,其它每一个划分式至少为两项之和。约定在所有划分式中零数作不减排列,探索和数k的划分式与和数k−1的划分式存在以下递推关系:
(1)在所有和数k−1的划分式前加零数1都是和数k的划分式。
(2)和数k−1的划分式的前两个零数作比较,如果第1个零数x1小于第2个零数x2,则把第1个零数加1后成为和数k的划分式。
2.递推算法设计
设置三维数组a,a(k, j, i)为和数k的第j个划分式的第i个数。
从k=2开始,显然递推的初始条件为:
a(2,1,1)=1;a(2,1,2)=1; a(2,2,1)=2。
根据递推关系,实施递推:
(1)实施在k−1所有划分式前加1操作
a(k,j,1)=1;
for(t=2;t<=k;t++)
a(k,j,t)=a(k−1,j,t−1); // k−1的第t−1项变为k的第t项
(2)若k−1划分式第1项小于第2项,第1项加1,变为k的第i个划分式
if(a(k−1,j,1)<a(k−1,j,2)
{ a(k,i,1)=a(k−1,j,1)+1;
for(t=2;t<=k−1;t++)
a(k,i,t)=a(k−1,j,t);
}
以上递推算法的时间复杂度与空间复杂度为O(n2u),其中u为n划分式个数。注意到u随n增加非常快,难以估算其数量级,其时间复杂度与空间复杂度是很高的。
3.整数划分的程序实现
// 整数s划分展示
#include <stdio.h>
void main()
{ int s,i,j,k,t,u;
static int a[21][800][21];
printf("input s(s<=20):"); scanf("%d",&s);
a[2][1][1]=1;a[2][1][2]=1;a[2][2][1]=2;
u=2;
for(k=3;k<=s;k++)
{ for(j=1;j<=u;j++)
{ a[k][j][1]=1;
for(t=2;t<=k;t++) // 实施在k−1所有划分式前加1操作
a[k][j][t]=a[k−1][j][t−1];
}
for(i=u,j=1;j<=u;j++)
if(a[k−1][j][1]<a[k−1][j][2]) // 若k−1划分式第1项小于第2项
{ i++; // 第1项加1为k的第i个划分式的第1项
a[k][i][1]=a[k−1][j][1]+1;
for(t=2;t<=k−1;t++)
a[k][i][t]=a[k−1][j][t];
}
i++;a[k][i][1]=k; // k的最后一个划分式为:k=k
u=i;
}
for(j=1;j<=u;j++) // 输出s的所有划分式
{ printf("%3d: %d=%d",j,s,a[s][j][1]);
i=2;
while(a[s][j][i]>0)
{printf("+%d",a[s][j][i]);i++;}
printf("\n");
}
}
运行程序,输入s=12,得
input s(s<=20):12
1: 12=1+1+1+1+1+1+1+1+1+1+1+1
2: 12=1+1+1+1+1+1+1+1+1+1+2
3: 12=1+1+1+1+1+1+1+1+1+3
……
75: 12=5+7
76: 12=6+6
77: 12=12
运行程序,输入s=20,可得20的共627个划分式。
2 整数划分递推优化
考察以上应用三维数组a(k, j, i)完成递推过程,当由k−1的划分式推出k的划分式时,k−1以前的数组单元已完全闲置。为此可考虑把三维数组a(k,j,i)改进为二维数组a(j,i)。二维数组a(j,i)表示和数是k−1的已有划分式,根据递推关系推出k的划分式:
1. 把a(j,i)依次存储到a(j,i+1),加上第一项a(j,1)=1;这样完成在k−1的所有划分式前加1的操作,转化为k的划分式。
for(t=i;t>=1;t−−)
a(j,t+1)=a(j,t);
a(j,1)=1;
2. 对已转化的u个划分式逐个检验,若其第2个数小于第3个数(相当于k−1时的第1个数小于第2个数),则把第2个数加1,去除第一个数后,作为k时增加的一个划分式,为第t(t从u开始,每增加一个划分式,t增1)划分式。
for(t=u,j=1;j<=u;j++)
if(a(j,2)<a(j,3)) // 若k−1划分式第1项小于第2项
{t++;
a(t,1)=a(j,2)+1; // 第1项加1 作为k的第t个划分式的第1项
i=3;
while(a(j,i)>0)
{a(t,i−1)=a(j,i);i++;}
}
改进的递推设计把原有的三维数组优化为二维数组,降低了算法的空间复杂度,拓展了算法的求解范围。
3.优化递推设计的程序实现
// 整数s划分优化递推设计
#include <stdio.h>
void main()
{ int s,i,j,k,t,u;
static int a[1600][25];
printf("input s(s<=24):");
scanf("%d",&s);
a[1][1]=1;a[1][2]=1;a[2][1]=2;u=2;
for(k=3;k<=s;k++)
{ for(j=1;j<=u;j++)
{ i=k−1;
for(t=i;t>=1;t−−) // 实施在k−1所有划分式前加1操作
a[j][t+1]=a[j][t];//把k-1的所有划分式都可看做有k-1项,其中在有些划分式的k-1项中有很多0项
a[j][1]=1;
}
for(t=u,j=1;j<=u;j++)
if(a[j][2]<a[j][3]) // 若k−1划分式第1项小于第2项
{ t++;
a[t][1]=a[j][2]+1; // 第1项加1
i=3;
while(a[j][i]>0)
{a[t][i−1]=a[j][i];i++;}
}
t++;a[t][1]=k; // 最后一个划分式为:k=k
u=t;
}
for(j=1;j<=u;j++) // 输出所有u个划分式
{ printf("%3d: %d=%d",j,s,a[j][1]);
i=2;
while(a[j][i]>0)
{printf("+%d",a[j][i]);i++;}
printf("\n");
}
}
注意:因划分式的数量u随和数s增加相当迅速,尽管改进为二维数组,求解的和数s不可能太大。