PTA 7-12 整數分解為若干項之和(20 分)
將一個正整數N分解成幾個正整數相加,可以有多種分解方法,例如7=6+1,7=5+2,7=5+1+1,…。程式設計求出正整數N的所有整數分解式子。
輸入格式:
每個輸入包含一個測試用例,即正整數N (0 < N ≤ 30)。
輸出格式:
按遞增順序輸出N的所有整數分解式子。遞增順序是指:對於兩個分解序列 N1=n1,n2,⋯
和 N2=m1,m2,⋯,若存在 i 使得 n1=m1, ⋯ , ni=mi,但是 ni+1<mi+1,則 N1 序列必定在 N2序列之前輸出。每個式子由小到大相加,式子間用分號隔開,且每輸出 4 個式子後換行。
輸入樣例:
7
輸出樣例:
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
解題思路:
採用了深度優先處理的思想,涉及到了一點點資料結構的知識。如果還沒學到資料結構,也不必擔心。在之前的題目中也可能用到了其它容易實現的資料結構,只是不知道它是資料結構中的內容。資料結構就是把各種各樣的操作、邏輯關係進行分類、總結,從而讓我們更加方便地設計演算法來解決問題。
深度優先演算法用遞迴寫起來比較方便。遞迴有兩個重要元素:
- 遞迴出口
- 遞迴的表示式
遞迴對技巧性要求很高,大多數時候其關係式並不是很容易找到。而且對遞迴的設計與理解,很容易鑽到具體細節的實現上。遞迴的優點就是可以讓一些複雜問題簡單化,把具體的細節交給計算機執行。而過分鑽研細節,就非常容易陷進去理不清頭緒。對於遞迴的學習應該是多看看經典的遞迴寫法,遇到類似問題會模仿寫就行了,不一定要自己創造出一個遞迴關係式。
本題也是如此。注意演算法的主體部分,關鍵資訊無非是:
void division () {
division (下一個);
對結點進行處理;
}
遞迴出口是累加的總和等於了輸入的 N。
到這裡,就可以去看下面的程式碼了。然後試著自己寫,不會寫,就模仿,下面的框圖對寫這個演算法基本上沒有幫助——除了讓人覺得「好像挺複雜的」以外。遞迴的特點就是形式簡單,實際上細節繁多。不要扣於細節,先會寫了,再去思考和模擬它的執行細節以掌握它,這樣才不至於困難重重,無從下手。如果細節上有疑問,可以來看看下面的處理流程。
演算法的處理流程是:
- 假設輸入的 N 為 3:
第一層遞迴 | 第二層遞迴 | 第三層遞迴 | 主要執行細節 |
---|---|---|---|
division (1) sum = 1,不跳出 → |
division (1) sum = 2,不跳出 → |
division (1) sum = 3 等於 N,輸出當前序列 1 1 1, 跳出,執行 for 迴圈,sum 均大於 3,跳出,返回上一層 ↓ |
第三層 s[0] s[1] s[2] 動作 1 1 1 輸出 1 1 2 跳出 1 1 3 跳出 1 1 4 跳出 |
↓ |
開始處理 division (2) sum = 3,輸出當前序列 1 2,然後跳出,執行 for 迴圈,均跳出 ← 返回至上一層 |
← 返回至上一層 |
第二層 s[0] s[1] 動作 1 2 輸出 1 3 跳出 1 4 跳出 |
開始處理 division (2) sum = 2,不跳出 → |
division (2) sum = 4,跳出,返回上一層 ↓ |
第二層 s[0] s[1] 動作 2 2 跳出 |
|
開始處理 division (3) sum = 3, 輸出當前序列 3,結束程式 |
← 返回至上一層 |
第一層 s[0] 動作 3 跳出 |
- 箭頭指明瞭各層之間的流動方向。
如果 N 更大一點,這個表格會變得更加複雜。遞迴的手動模擬範圍應儘量小一點,否則容易混亂。
你可以發現,所謂的深度優先就是說,優先處理下一個節點,直到它們的 sum 等於 N,才返回上一個節點。先爬到最深處,再往回走。
解題程式碼:
#include<stdio.h>
int N;
int s[31]; // 存放劃分結果
int top = -1; // 陣列指標
int count = 0; // 統計輸出的次數
int sum = 0; // 拆分項累加和
void division (int i);
int main ()
{
scanf ("%d", &N);
division (1);
return 0;
}
void division (int i) {
if (sum == N) {
count ++;
printf("%d=", N);
int k;
for (k=0; k<top; k++) {
printf("%d+", s[k]);
}
if (count%4 == 0 || s[top] == N) {
printf("%d\n", s[top]);
} else {
printf("%d;", s[top]);
}
return;
} // 輸出部分
if (sum > N) {
return;
}
for (int j=i; j<=N; j++) {
s[++top] = j;
sum += j;
division (j);
sum -= j;
top --;
} // 演算法主體
}