整數劃分問題(Java版)
整數劃分問題:將正整數n表示成一系列正整數之和:,其中,k≥1。正整數n的這種表示稱為正整數n的劃分。請設計一個演算法,求正整數n的不同劃分個數或方案。例如正整數6有以下11種不同的劃分個數或方案:
{6};
{5+1};
{4+2},{4+1+1};
{3+3},{3+2+1},{3+1+1+1};
{2+2+2},{2+2+1+1},{2+1+1+1+1};
{1+1+1+1+1+1}。
對於任意的正整數n,可以表示為一些整數相加的形式,我們可以把n的劃分函式p(n)定義為另一種形式q(n,m)。其中,n表示那些相加的整數中最大的那一個加數,
情況一:當n = = 1 || m = = 1時,返回的是1。這裡可以分成兩個小部分來看,當n = = 1時,也就是說最大加數為1,自然只有一種情況,當m = = 1時,那麼相加每一個整數都不能大於1,那麼也只有全部都是1這一種情況。
情況二:當n = = m時,返回的是1+q(n,n-1)。也就是說n的加數就是n(因為n= =m),那麼這個時候就增加了一種劃分情況,就是n={n},但是隻是返回1是不夠的,因為函式q(n,m)本身的功能是計算最大加數n不大於m的劃分個數,所以還要進一步遞迴計算整數
情況三:當n < m 時,返回的是q(n,n)。我們再一次強調一下q(n,m)這個函式的功能時計算最大加數n不大於m的劃分個數,如果n < m的時候,這和函式的功能時矛盾的,不過m其實可以縮小範圍,縮小到n,這時只需要計算最大加數n不大於n的劃分個數即可。
情況四:當n > m > 1時,返回的是
在分析完這4中情況之後,給出下面的遞迴表示式:
為了測試演算法的正確性,我們帶入題目給出的求正整數6劃分個數來驗證。一開始劃分數為0。正整數6可以由一些加數通過加法相加來得到,為了方便分析,先把這些加數的集合定義為A。
第一步:帶入q(6,6),進入函式之後,符合n = = m這中情況,那麼此時首先是增加了第一種情況,就是集合A中最大的加數為6的時候,只有一種情況,即6 = {6},在劃分數加1之後,繼續遞迴計算集合A中最大加數為5的所有劃分個數,也就是q(6,5)。所以當n = = m時,公式:q(n,m) = 1 + q(n, n-1)是正確的。
第二步:這一步計算的是q(6,5),符合第四種情況(n>m>1)。當集合A中最大加數已經確定是5之後,那麼只需要計算6-5=1不大於5的所有劃分數就是整數6的所有以5開頭的加法因子的劃分數,也就是q(1,5)。在計算最大加數為5的劃分數之後,還需要繼續遞迴計算最大加數為4的劃分數,也就是q(6,4)。所以當n>m>1時,公式q(n,m) = q(n,m-1) + q(n-m,m)是正確的。
第三步:因為上一步有兩個遞迴公式,首先先考慮q(n-m,m)也就是q(1,5),當最大加數為1的時候,由情況一可以知道只有加數是1這一種情況,在這裡就可以確定集合A中一最大加數為5開頭的所有劃分情況只有一種,就是6={5+1}。這裡有根據公式q(n,m) = q(n,m-1) + q(n-m,m)接著把q(6,4)劃分為q(6,3)+q(6-4,2)。
後面的情況幾乎和前面3步分析的情況差不多。所以我們可以根據上面的遞迴公式來計算出任意一個正整數的所有劃分數。但是題目還需要列印所有劃分的情況,那麼我們可以定義一個全域性陣列變數int a[],來記錄每一個加數,所以我們可以把上述所說的遞迴方程q(n,m)改成q(n,m,i),這裡的i表示陣列a的下標,那麼在每一次遞迴方法中,第一個操作就是賦值給陣列,也就是執行操作a[i]=m。
import java.util.Scanner;
public class Lab1_2 {
static int[] a = new int[1000];
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner in = new Scanner(System.in);
while(in.hasNextInt()) {
int n = in.nextInt();
int count = q(n, n, 0);
System.out.println(count);
}
}
public static int q(int n, int m, int i) {
if(n < m) {
return q(n, n, i);
}
a[i] = m;
if(n == 0 || m == 0) {
//列印下標從0到i
printPartition(i);
return 0;
}
if(n == 1 || m == 1) {
if(n == 1) {
//列印下標從0到i
printPartition(i);
}
else q(n-1, 1, i+1);
return 1;
}
if(n == m) {
//列印下標從0到i
printPartition(i);
return 1 + q(n, n-1, i);
}
return q(n-m, m, i+1) + q(n, m-1, i);
}
public static void printPartition(int i) {
System.out.print("{");
for(int j = 0; j <= i; j++) {
if(j == i) System.out.print(a[j]);
else System.out.print(a[j] + "+");
}
System.out.println("}");
}
}