迭代是一種不斷用變量的舊值推出新值的過程。例如,程序設計中常用到的計數cnt=cnt+1(或cnt++),就是用變量cnt的值加上1后賦值給cnt;對k的求和s=s+k,就是用變量s的值加上k后賦值給s。這種用變量cnt、s的新值取代舊值的過程,實際上就是迭代。
遞推實際上也是根據遞推關系式不斷推出新值的過程,與迭代有很多共同之處。很多迭代過程可以應用遞推來解決;反過來,很多遞推過程也可以應用迭代來解決。
例如,下面的水手分椰子問題,既可以采用遞推法求解,也可以用迭代法求解。
【例1】水手分椰子
五個水手來到一個島上,采了一堆椰子后,因為疲勞都睡着了。一段時間后,第一個水手醒來,悄悄地將椰子等分成五份,多出一個椰子,便給了旁邊的猴子,然后自己藏起一份,再將剩下的椰子重新合在一起,繼續睡覺。不久,第二名水手醒來,同樣將椰子等分成五份,恰好也多出一個,也給了猴子。然后自己也藏起一份,再將剩下的椰子重新合在一起。以后每個水手都如此分了一次並都藏起一份,也恰好都把多出的一個給了猴子。第二天,五個水手醒來,發現椰子少了許多,心照不宣,便把剩下的椰子分成五份,恰好又多出一個,給了猴子。問原來這堆椰子至少有多少個?
(1)編程思路1。
應用遞推來求解,按時間來實施遞推。
設第i個水手藏椰子數為y(i)(i=1、2、…、5)個,第二天5個水手醒來后各分得椰子為y(6)個,則原來這堆椰子數為
x=5*y(1)+1
1)如何求取y(1)呢?
由於第二個水手醒來所面臨的椰子數為4y(1),同時也為5y(2)+1,於是有
4*y(1)=5*y(2)+1
同樣,y(2)與y(3)之間的關系為:4*y(2)=5*y(3)+1
一般地,有遞推關系:4*y(i)=5*y(i+1)+1 (i=1、2、…、5)
2)遞推的初始(邊界)值如何確定?
問題本身沒有初始(邊界)條件限制,只要求上面5個遞推關系式所涉及的6個量y(i)都是正整數。也就是說,若有6個整數y(i)滿足5個方程4*y(i)=5*y(i+1)+1 (i=1,2,…,5),即為所求的一個解。
3)采用順推法求解。
將遞推式變形為從y(i)推出y(i+1)的形式
y(i+1)=(4*y(i)-1)/5 (i=1,2,…,5)
首先y(1)賦初值k后推出y(2),由y(2)推出y(3),…,依此經5次遞推得y(6)。如果某一次推出的不是整數,則中止繼續往后推,k增1后賦值給y(1),從頭開始。
這樣按時間順序從前往后遞推,若每次遞推所得都是整數,則找到了解,打印輸出。
為保證推出的y(i)為整數,則要求4*y(i-1)-1能被5整除(即前一個水手藏起一份后,剩下的4份能夠給猴子一個,再被分成五份)。因此,可確定最小的k值為4,即y(1)賦初值4;若在遞推過程中,某次y(i)不為整數,則重新賦y(1)從頭再來,為保證4*y(1)-1能被5整除,因此 k 的增量可設置為5。
(2)源程序1。
#include <iostream>
using namespace std;
int main()
{
int i,k,x,y[7];
k=4; y[1]=k;
i=2;
while (i<=6)
{
if ((4*y[i-1]-1)%5!=0)
{
k=k+5; y[1]=k; i=2; // 若y(i)不是整數,k增1重試
}
else
{
y[i]=(4*y[i-1]-1)/5; // 遞推求后一個水手藏起的椰子y(i)
i++;
}
}
x=5*y[1]+1;
cout<<"原有椰子至少"<<x<<"個。"<<endl;
for (i=1; i<=5; i++)
cout<<"第 "<<i<<" 個水手面臨椰子 "<<5*y[i]+1<<" 個,藏 "<<y[i]<<"個。"<<endl;
cout<<"最后一起分時有椰子 "<<5*y[6]+1<<" 個,每人分得"<<y[6]<<"個。"<<endl;
return 0;
}
(3)編程思路2。
采用倒推法求解,即改為y(6)賦初值k后遞推出y(5),由y(5)遞推出y(4),依此經5次遞推得y(1),“由后向前”遞推式為:
y(i)=(5*y(i+1)+1)/4 (i=1、2、…、5)
為確保從y(6)推出整數y(5),顯然y(6)(即初始參數k)只能取3、7、11、…,即取k%4==3。因而k賦初值為3,k的增量為4。
(4)源程序2。
#include <iostream>
using namespace std;
int main()
{
int i,k,x,y[7];
k=3; y[6]=k;
i=5;
while (i>=1)
{
if ((5*y[i+1]+1)%4!=0)
{
k=k+4; y[6]=k; i=5; // 若y(i)不是整數,k增1重試
}
else
{
y[i]=(5*y[i+1]+1)/4; // 遞推求前一個水手藏起的椰子y(i)
i--;
}
}
x=5*y[1]+1;
cout<<"原有椰子至少"<<x<<"個。"<<endl;
for (i=1; i<=5; i++)
cout<<"第 "<<i<<" 個水手面臨椰子 "<<5*y[i]+1<<" 個,藏 "<<y[i]<<"個。"<<endl;
cout<<"最后一起分時有椰子 "<<5*y[6]+1<<" 個,每人分得"<<y[6]<<"個。"<<endl;
return 0;
}
在思路(1)中,采用順推法,從前向后推,即從大到小推,試到k=3124才完成,從k=4到k=3124,試了625次;在思路(2)中,采用倒推法,從后往前推,即從小往大推,只要試到k=1023即可完成,從k=3到k=1023,試了256次。可見,在應用遞推時,選用合適的遞推方向關系到遞推的效率。
(5)編程思路3。
用迭代法求解。
從最后5位水手一起分椰子時的椰子數residual入手,設residual的初始值為6(每個水手至少能分1個,丟1個給猴子),但這不可能,因為residual的值一定是第5位水手分成5份后,藏1份,剩下的4份,即每次剩下的一定是4的倍數,因此residual值一定滿足兩個條件:(1)是4的倍數;(2)減1后能被5整除。即residual的值為16、36、56、76、…。
對residual值向前推導。看看能否前推5次,且每次剩下的椰子數均是4的倍數。例如,當residual=16時,第5位水手面臨的椰子數應為peachNum=present/4*5+1=16/4*5+1=21,而第5位水手面臨的椰子數是第4位水手藏起1份后剩下的4份,顯然21不是4的倍數,因此residual=16不可行,修改residual的值,使residual=residual+20=36,重新推導。
迭代時,迭代初值為 present=residual,迭代關系式為peachNum=present/4*5+1, present=peachNum,迭代控制條件為:在保證每次迭代后,present的值為4的倍數的情況下,迭代次數能達到5次。若迭代過程中,得到的present的值不是4的倍數,則修改residual的值,使residual=residual+20=36,重新迭代求解。
(6) 源程序3。
#include <iostream>
using namespace std;
int main()
{
int residual,present,peachNum,count;
residual=16;
count=0;
present=residual;
while (count<=4)
{
if(present%4!=0)
{
count=0;
residual+=20;
present=residual;
}
peachNum=present/4*5+1;
count++;
present=peachNum;
}
cout<<"原有椰子至少"<<peachNum<<"個。"<<endl;
return 0;
}
比較遞推與迭代,兩者的時間復雜度是相同的。所不同的是,遞推往往設置數組,而迭代只要設置迭代的簡單變量即可。
遞推過程中數組變量帶有下標,推出過程比迭代更為清晰。也正因為遞推中應用數組,因此保留了遞推過程中的中間數據。例如,每個水手i藏起的椰子都保存在數組y[i]中,隨時可以查看;而迭代過程中不保留中間數據。