遞歸算法
程序調用自身的編程技巧稱為遞歸( recursion)。
一個過程或函數在其定義或說明中又直接或間接調用自身的一種方法,它通常把一個大型復雜的問題層層轉化為一個與原問題相似的規模較小的問題來求解,遞歸策略只需少量的程序就可描述出解題過程所需要的多次重復計算,大大地減少了程序的代碼量。
注意:
(1) 遞歸就是在過程或函數裡調用自身;
(2) 在使用遞增歸策略時,必須有一個明確的遞歸結束條件,稱為遞歸出口。
一個比較經典的描述是老和尚講故事,他說從前有座山,山上有座廟,廟裡有個老和尚在講故事,他說從前有座山,山上有座廟,廟裡有個老和尚在講故事,他說從前有座山, ……。這樣沒完沒了地反復講故事,直到最後老和尚煩了停下來為止。
反復講故事可以看成是反復調用自身,但如果不能停下來那就沒有意義了,所以最終還要能停下來。遞歸的關鍵在於找出遞歸方程式和遞歸終止條件。即老和尚反復講故事這樣的遞歸方程式要有,最後老和尚煩了停下來這樣的遞歸的終止條件也要有。
階乘的算法可以定義成函數
當 n>0時,用 f(n-1)來定義 f(n),用 f(n-1-1)來定義 f(n-1)……,這是對遞歸形式的描述。
當 n=0時, f(n)=1,這是遞歸結束的條件。
例:用遞歸策略求N!的解。
N!=1*2*3*...*N
分析:
(1) 不運用遞歸的解法
(2) 運用遞歸策略
N!=1*2*3*...*N
=[1*2*3*...*(N-1)]*N
(N-1)!=1*2*3*...*(N-1)
設 f(N)=N!
那麼 f(N-1)=(N-1)!
則 f(N)=f(N-1)*N
這就是遞歸式子,由於式子中有N-1,所以N>=1,遞歸出口的條件是N=1時。
函數模式:
function f(n:integer):longint;
var
...
begin
if 遞歸出口的時候 then
f:=1
else
f:=f(n-1)*n;
end;
遞歸算法一般用於解決三類問題:
⑴. 數據的定義形式是按遞歸定義的。
比如階乘的定義。
例 1 又如裴波那契數列的定義: f(n)=f(n-1)+f(n-2); f(0)=1; f(1)=2
對應的遞歸程序為:
var n:integer;
function f(n:integer):longint;
begin
case n of
0:f:=1; { 遞歸結束條件 }
1:f:=2;
else
f:=f(n-1)+f(n-2) {遞歸調用}
end
end;
begin
readln(n);
writeln(f(n))
end.
這類遞歸問題往往又可轉化成遞推算法,遞歸邊界作為遞推的邊界條件。
⑵. 問題解法按遞歸算法實現。例如回溯等。
⑶. 數據的結構形式是按遞歸定義的。如樹的遍歷 , 圖的搜索等。
遞歸解決實際問題的例子很多,如經典的梵塔問題。
例 2 梵塔問題:有 n個半徑各不相同的圓盤,按半徑從大到小,自下而上依次套在 A柱上,另外還有 B、 C兩根空柱。要求將 A柱上的 n個圓盤全部搬到 C柱上去,每次只能搬動一個盤子,且必須始終保持每根柱子上是小盤在上,大盤在下。
在移動盤子的過程當中發現要搬動 n個盤子,必須先將 n-1個盤子從 A柱搬到 B柱去,再將 A柱上的最後一個盤子搬到 C柱,最後從 B柱上將 n-1個盤子搬到 C柱去。搬動 n個盤子和搬動 n-1個盤子時的方法是一樣的,當盤子搬到只剩一個時,遞歸結束。
程序如下:
var a,b,c,number:integer;
procedure move(n,a,b,c:integer);
begin
if n=1 then writeln(a,'->',c)
else
begin
move(n-1,a,c,b);
writeln(a,'->',c);
move(n-1,b,a,c)
end;
end;
begin
write('the number of dish:');
readln(number);
move(number,1,2,3);
readln
end.
自然數的拆分,數字的拆分等都可以用到遞歸算法。