1. 程式人生 > >整數劃分問題(python)--遞迴 and 動態規劃(m個盤裡放n個蘋果思想類似)

整數劃分問題(python)--遞迴 and 動態規劃(m個盤裡放n個蘋果思想類似)

這篇部落格旨在對正整數劃分的多種題目就遞迴和動態規劃進行討論與總結

以下將正整數劃分分為三種題型:1.一般性,即對個數以及大小以及重複性不加約束 2.對重複性有約束 3.對元素的個數有約束。至於每個元素的大小則可以歸併到第一類題:通過更改q(n,m)的引數m即可。

1.

問題描述:

描述
將正整數n 表示成一系列正整數之和,n=n1+n2+…+nk, 其中n1>=n2>=…>=nk>=1 ,k>=1 。 
正整數n 的這種表示稱為正整數n 的劃分。正整數n 的不同的劃分個數稱為正整數n 的劃分數。
輸入
標準的輸入包含若干組測試資料。每組測試資料是一個整數N(0 < N <= 50)。
輸出
對於每組測試資料,輸出N的劃分數。
樣例輸入
5
樣例輸出
7
提示:
5, 4+1, 3+2, 3+1+1, 2+2+1, 2+1+1+1, 1+1+1+1+1


解題思路

如題,令p(n)表示正整數n的劃分數,則難以找到遞迴關係,因此增加一個自變數m,設定最大加數n1不大於m的劃分個數。即p(n)=q(n,m),則有以下關係:

1.首先當n=0:return 1:表示已經被分完

2. m=0:則無法實現,即return 0

3.當m=1 or n=1:則return 1(n=1:則由1組成;m=1:則由n個1組成這兩種情況都是一種方式)

4.當n<m則滿足:q(n,m)=q(n,n) :n的劃分由n1<=n的劃分組成

5.當n=m:q(n,m)=1+q(n,m-1):正整數n的劃分由n1=n的劃分和n1<=n-1的劃分組成

6.n>m>1:q(n,m)=q(n,m-1)+q(n-m,m):正整數n的最大加數不大於m的劃分由n1=m的劃分和n1<=m-1的劃分組成

其中第一條用於控制劃分的情況,第二條用於可以劃分的終止條件

程式碼實現:

1.遞迴:則根據以上的解題思路進行寫程式碼即可:

#-*-coding:utf-8 -*-
def dfs(n,m):
    if n==0:
        return 1
    elif m==0:
        return 0
    elif n==1 or m==1:
        return 1
    elif n<m:
        return dfs(n,n)
    elif n==m:
        return dfs(n,m-1)+1
    else:
        return dfs(n,m-1)+dfs(n-m,m)
if __name__ == '__main__':
    n,m=map(int,raw_input().split()) #如果沒有限制,即m<=n即可,則將下一行中的m改成n即可
    print dfs(n,m)

2.動態規劃:利用動態規劃解題最重要的就是狀態轉移方程,還有邊界控制,本題為體現一般性,則先進行利用動態規劃打表即(m=n),然後根據實際的m輸出dp[n][m]即可

狀態轉移方程:即解題思路的3、4、5,邊界條件:dp[0][0]=1,注意建立(n+1)*(n+1)dp二維列表

#-*-coding:utf-8 -*-
#求整數n的劃分數
while True:
    try:
        n=int(raw_input())
        dp=[[0 for i in range(n+1)] for j in range(n+1)]
        dp[0][0]=1
        for i in range(1,n+1):
            for j in range(1,n+1):
                if i < j:
                    dp[i][j]=dp[i][i]
                elif i==j:
                    dp[i][j]=1+dp[i][j-1]
                else:
                    dp[i][j]=dp[i][j-1]+dp[i-j][j]
        print dp[n][n]
    except:
        break


2.

問題描述大致1,增加限制條件,每一種劃分方案的組成元素不重複

解題思路:

同題1不同的是:本題增加了限制條件不可重複性,有如下遞迴關係:

1.n=0:return 1,當n=0時,表示已經分完

1.m<1 :return 0:m=0的時候無法繼續劃分

3.n<m:p(n,m)=p(n,n) 

4.p(n,m)=p(n-m,m-1)+p(n,m-1) :p(n-m,m-1)表示先拿出一個m,剩下的n-m在n1<=m-1的劃分數;p(n,m-1)指n在n1<=m-1的劃分數

程式碼實現:

1.遞迴:注意遞迴的終止調價

2.動態規劃:狀態轉移方程:dp[i][j]=dp[i-j][j-1]+dp[i,j-1]

#-*-coding:utf-8 -*-
#劃分為不同整數
while True:
    try:
        n=int(raw_input())
        dp=[[0 for i in range(n+1)] for j in range(n+1)]
        dp[0][0]=1
        for i in range(n+1):
            for j in range(1,n+1):
                if j>i:
                    dp[i][j]=dp[i][i]
                else:
                    dp[i][j]=dp[i][j-1]+dp[i-j][j-1]
        print dp[n][n]
    except:
        break


3.

問題描述同1,增加限制條件,每一種劃分方案規定元素個數

解題思路:

同題1不同的是:該題限定了元素個數m,則m<=n,因此p(n,m)的理解是n劃分為m個元素,有如下遞迴關係:

1.m=1 or m=n:return 1:n劃分為一個元素或者劃分為n個元素,很明顯只有一種情況

2.m<1 or n<1:return 0:0無法劃分以及無法劃分為0個元素

3.p(n,m)=p(n-m,m)+p(n-1,m-1) :p(n-m,m)是指m個都分1,然後n-m在m個元素上進行分;p(n-1,m-1)指n-1劃分為m-1個數

程式碼實現:

1.遞迴:根據解題思路建立遞迴方程,注意終止條件

#-*-coding:utf-8 -*-
def dfs(n,m):
    if n==m or m==1:
        return 1
    elif n<1 or m<1:
        return 0
    else:
        return dfs(n-1,m-1)+dfs(n-m,m)
if __name__ == '__main__':
    n,m=map(int,raw_input().split())
    print dfs(n,m)


2.動態規劃:狀態轉移方程:dp[i][j]=dp[i-1][j-1]+dp[i-j][j]

#-*-coding:utf-8 -*-
#求整數n劃分成k個整數的劃分數
while True:
    try:
        n,k=map(int,raw_input().split())
        dp=[[0 for i in range(n+1)] for j in range(n+1)]
        dp[0][0]=1
        for i in range(1,n+1):
            for j in range(i+1):
                if j==1:
                    dp[i][j]=1
                else:
                    dp[i][j]=dp[i-1][j-1]+dp[i-j][j]
        print dp[n][k]
    except:
        break


以上是本人對整數劃分的幾種情況就遞迴以及動態規劃進行的分析,經實踐證明,如果資料較大,遞迴運算時間比動態規劃慢。雖然遞迴結構清晰,可讀性強,而且易用數學歸納法證明演算法的正確性,但是執行效率很低。因此,遞迴是設計演算法的有效工具,優化演算法時可以將遞迴轉化為非遞迴演算法。

ps:分蘋果、分蛋糕問題也可以根據本文的思路解決。根據實際情況增加相應的限制條件即可