PTA_數據結構學習與實驗指導_題解_2-2.5 整數分解爲若干項之和

基礎實驗2-2.5整數分解爲若干項之和

將一個正整數N分解成幾個正整數相加,能夠有多種分解方法,例如7=6+1,7=5+2,7=5+1+1,…。編程求出正整數N的全部整數分解式子。
輸入格式:
每一個輸入包含一個測試用例,即正整數N (0<N≤30)。
輸出格式:
按遞增順序輸出N的全部整數分解式子。遞增順序是指:對於兩個分解序列N​1​​={n​1​​,n​2​​,⋯}和N​2​​={m​1​​,m​2​​,⋯},若存在i使得n​1​​=m​1​​,⋯,n​i​​=m​i​​,可是n​i+1​​<m​i+1​​,則N​1​​序列一定在N​2​​序列以前輸出。每一個式子由小到大相加,式子間用分號隔開,且每輸出4個式子後換行。
輸入樣例:算法

7

輸出樣例1:編程

7=1+1+1+1+1+1+1;7=1+1+1+1+1+2;7=1+1+1+1+3;7=1+1+1+2+2
7=1+1+1+4;7=1+1+2+3;7=1+1+5;7=1+2+2+2
7=1+2+4;7=1+3+3;7=1+6;7=2+2+3
7=2+5;7=3+4;7=7

題目:基礎實驗2-2.5 整數分解爲若干項之和 (20分)性能

算法分析

 看到這題第一反應是分治。很像全排列問題,先寫出第一項,再寫出剩下的數的分解項。一塊兒來分析步驟。
 首先假設咱們要求 $F(4)$, 即寫出 $n=4$ 的全部分解項。那麼咱們能夠先寫出第 1 位的分解項的全部可能。即 $k_1=1,2,3,4$。對於每一個 $k_1$,咱們只需求解它的子問題 $F(n-k_1)$ 便可。重複以上步驟,可得一棵結果樹:
image.png測試

 因爲題目中要求結果序列升序排列,因此咱們要去除重複狀況與降序狀況,分析後發現:spa

  1. 對於重複狀況,咱們每次遞歸只須要取到 $n$ 的一半,即 $\lfloor\frac n2\rfloor$ 即可。
  2. 對於降序狀況,咱們要保證 $k_{i-1}<=k_i$ 。

整理一下,可得:處於區間 $[0,k_{i-1}-1]\bigcup(\lfloor\frac n2\rfloor,n)$ 的狀況必須捨去,咱們只須要區間 $[k_{i-1},\lfloor\frac n2\rfloor]\bigcup\{n\}$ 即可。對於 $F=4$ 的狀況,咱們能夠看作是 $F(5)=1+F(4)$ ,可得 $k_{i-1}=1$,帶入剛纔推導出的公式得第 1 位的分解項的全部值爲 $i=1,2,4$。
對於上述全部i,問題都被分解成了:對應的 $k_i$ 與 $F(n-k_i)$ 。遞歸這個過程,獲得一棵以下的樹:
image.pngcode

遞歸公式也不難寫出來
$$Recursion:\begin{cases}n==0, (遞歸基)\\F(n)=k_i+F(n-k_i),\ (i\in[k_{i-1},\lfloor\frac n2\rfloor]\bigcup\{n\})\end{cases}$$ blog

 相信看到這裏不少同窗已經能用DFS解出來了,估計核心代碼在六、7行左右。可是看到幾十層的遞歸棧······打擾了。筆者決定再推一下遞推式,畢竟性能快好多不是。
 PS.筆者數學巨爛,下面的推導過程不是很容易懂,正在研究2.0版本...遞歸

下面根據遞歸式去推導遞推。 get

 顯然,F(n)的分解式不會超過 $n$ 項,也就是第一次DFS到底時, $\underbrace{1+1+1+···+1}_{n個1}$這種狀況,設爲序列U。咱們從這種狀況開始反推。因爲公式爲 $F(n)=k_i+F(n-k_i)$, 將序列U末尾2位代入公式,得 : 數學

$$\begin{cases}k_{n-1}=1, \\n-k_{n-1}=n-1=1\Rightarrow n=2\end{cases}$$

此時再斷定有無處於區間 $[k_{n-1},\lfloor\frac n2\rfloor]\bigcup\{n\}$ 的其餘 $k_{n-1}$,有的話每次只須要處理比當前 $k_{n-1}$ 大 1 的狀況,即若是 $j=\lfloor\frac n2\rfloor-k_{n-1}>0$,進行j次循環分解,每次執行 $n-=k_i+1$ ,遇到逆序狀況則退出循環,最後存留的 $n$ 即爲最後一位的值。與遞歸相反,整個遞推過程當n=30時天然結束遞歸了。

代碼

 遞歸的代碼網上都是,筆者就不傳了,這邊給出非遞歸的代碼,應該是性能一致的狀況下代碼行數最少的版本了吧。看到其餘博主好像基本上都用goto來實現非遞歸的,未免也太...

#include <stdio.h>
int main()
{
    int n = 0, a[31] = {0};
    scanf("%d", &n);
    for(int i=0; i<n; i++){
        a[i] = 1;
    } 
    int pos = n-1, temp = 0, cnt = 0;
    while(pos>=0){
        printf("%d=", n);
        for(int i=0; i<=pos; i++){
            if(i==pos){
                printf("%d", a[i]);
            }else{
                printf("%d+",a[i]);
            }
        }
        if(++cnt%4 == 0){
            printf("\n");
            cnt=0;
        }else{
            if(pos!=0) printf(";");
        }
        if(pos==0) break;
        int sum = a[pos] + a[pos-1];
        a[pos--] = 0;
        temp = a[pos] + 1;
        int j = (double)sum/2;
        int k = j - a[pos];
        while(k-->0){
            if(sum-temp < temp) break;
            sum -= temp;
            a[pos++] = temp;
        }
        a[pos] = sum;
    }
    return 0;
}

小結

 涉及到複雜一些的遞推遞歸DP回溯搜索的算法,作的時候很快,但給別人講我仍是說不明白...啊我好菜...

相關文章
相關標籤/搜索