遞歸

程序調用自身的編程技巧稱爲遞歸( recursion)。遞歸作爲一種算法在程序設計語言中普遍應用。 一個過程或函數在其定義或說明中有直接或間接調用自身的一種方法,它一般把一個大型複雜的問題層層轉化爲一個與原問題類似的規模較小的問題來求解,遞歸策略只需少許的程序就可描述出解題過程所須要的屢次重複計算,大大地減小了程序的代碼量。遞歸的能力在於用有限的語句來定義對象的無限集合。通常來講,遞歸須要有邊界條件、遞歸前進段和遞歸返回段。當邊界條件不知足時,遞歸前進;當邊界條件知足時,遞歸返回。算法

 

遞歸定義

 

遞歸,就是在運行的過程當中調用本身。
構成遞歸需具有的條件:
函數嵌套調用過程示例 函數嵌套調用過程示例
1. 子問題須與原始問題爲一樣的事,且更爲簡單;
2. 不能無限制地調用自己,須有個出口,化簡爲非遞歸情況處理。
在數學和計算機科學中,遞歸指由一種(或多種)簡單的基本狀況定義的一類對象或方法,並規定其餘全部狀況都能被還原爲其基本狀況。
例如,下列爲某人祖先的遞歸定義:
某人的雙親是他的祖先(基本狀況)。某人祖先的雙親一樣是某人的祖先(遞歸步驟)。斐波納契數列(Fibonacci Sequence),又稱黃金分割數列,指的是這樣一個數列:一、一、二、三、五、八、1三、21..... I[1] 
斐波納契數列是典型的遞歸案例:
遞歸關係就是實體本身和本身創建關係。
Fib(0) = 1 [基本狀況] Fib(1) = 1 [基本狀況] 對全部n > 1的整數:Fib(n) = (Fib(n-1) + Fib(n-2)) [遞歸定義] 儘管有許多數學函數都可以遞歸表示,但在實際應用中,遞歸定義的高開銷每每會讓人望而卻步。例如:
階乘(1) = 1 [基本狀況] 對全部n > 1的整數:階乘(n) = (n * 階乘(n-1)) [遞歸定義] 一種便於理解的心理模型,是認爲遞歸定義對對象的定義是按照「先前定義的」同類對象來定義的。例如:你怎樣才能移動100個箱子?答案:你首先移動一個箱子,並記下它移動到的位置,而後再去解決較小的問題:你怎樣才能移動99個箱子?最終,你的問題將變爲怎樣移動一個箱子,而這時你已經知道該怎麼作的。
如此的定義在數學中十分常見。例如,集合論對天然數的正式定義是:1是一個天然數,每一個天然數都有一個後繼,這一個後繼也是天然數。
德羅斯特效應 德羅斯特效應
德羅斯特效應是遞歸的一種視覺形式。圖中女性手持的物體中有一幅她本人手持同一物體的小圖片,進而小圖片中還有更小的一幅她手持同一物體的圖片,依此類推。
又例如,咱們在兩面相對的鏡子之間放一根正在燃燒的蠟燭,咱們會從其中一面鏡子裏看到一根蠟燭,蠟燭後面又有一面鏡子,鏡子裏面又有一根蠟燭……這也是遞歸的表現。

 

遞歸應用

遞歸算法通常用於解決三類問題:
(1)數據的定義是按遞歸定義的。(Fibonacci函數)
(2)問題解法按遞歸算法實現。
這類問題雖則自己沒有明顯的遞歸結構,但用遞歸求解比迭代求解更簡單,如Hanoi問題。
(3)數據的結構形式是按遞歸定義的。
如二叉樹、廣義表等,因爲結構自己固有的遞歸特性,則它們的操做可遞歸地描述。
遞歸的缺點:
遞歸算法解題相對經常使用的算法如普通循環等,運行效率較低。所以,應該儘可能避免使用遞歸,除非沒有更好的算法或者某種特定狀況,遞歸更爲適合的時候。在遞歸調用的過程中系統爲每一層的返回點、局部量等開闢了棧來存儲。遞歸次數過多容易形成棧溢出等。
遞歸典型問題: 梵塔問題(漢諾塔問題)
已知有三根針分別用A, B, C表示,在A中從上到下依次放n個從小到大的盤子,現要求把全部的盤子
從A針所有移到B針,移動規則是:可使用C臨時存放盤子,每次只能移動一塊盤子,並且每根針上
不能出現大盤壓小盤,找出移動次數最小的方案.
[2] 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/*Name:HANOITOWER
*Description:solvethehanoitowerproblembyrecursion
*/
#include<stdio.h>
#include<stdlib.h>
/*movenplates:from-->to,
*thebuffercanbeusedifneeded*/
inthanoi(intn,charfrom,charbuffer,charto)
{
     if (n==1)
     {
         /*movetheNO.1platedirectly:from-->to*/
         printf ( "Moveplate#%dfrom%cto%c\n" ,n,from,to);
         /*theNO.1plateismovedsoreturn*/
         return0;
     }
     else
     {
         /*nplatestobemoved:from-->to,
         *movethen-1platesabove:from-->buffer,
         *givethistasktothenextrecursion*/
         hanoi(n-1,from,to,buffer);
         /*then-1platesaboveweremovedtobuffer,
         *sotheNO.nplatecanbemoveddirectly*/
         printf ( "Moveplate#%dfrom%cto%c\n" ,n,from,to);
         /*howeverthen-1platesarestillinbuffer,
         *movethemtotheterminalposition,
         *(the"from"positionhasnoplate,&canbeoneso-calledbuffer)*/
         hanoi(n-1,buffer,from,to);
         /*thetaskgivenisdonesoreturn*/
         return0;
     }
}
intmain()
{
#defineN4
     /*NplatesinA,let'smovethemtoC*/
     hanoi(N, 'A' , 'B' , 'C' );
     return0;
}
如:
1
2
3
4
5
//pascal
procedurea;
begin
a;
end;
這種方式是直接調用.
又如:
1
2
3
4
5
6
7
8
9
10
//pascal
 
procedureb;
begin
c;
end;
procedurec;
begin
b;
end;
這種方式是間接調用.
例1計算n!可用遞歸公式以下:
1 當 n=0 時
fac(n)={n*fac(n-1) 當n>0時
可編寫程序以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//pascal
 
 
programfac2;
var
n:integer;
functionfac(n:integer):real;
begin
ifn=0thenfac:=1elsefac:=n*fac(n-1);
end;
begin
write( 'n=' );readln(n);
writeln( 'fac(' ,n, ')=' ,fac(n):6:0);
end.
例2 樓梯有n階臺階,上樓能夠一步上1階,也能夠一步上2階,編一程序計算共有多少種不一樣的走法.
設n階臺階的走法數爲f(n)
顯然有
1 n=1
f(n)={2 n=2
f(n-1)+f(n-2) n>2
可編程序以下:
1
2
3
4
5
6
7
8
9
10
11
12
//pascal
programlouti;
varn:integer;
functionf(x:integer):integer;
begin
ifx=1thenf:=1else
ifx=2thenf:=2elsef:=f(x-1)+f(x-2);
end;
begin
write( 'n=' );read(n);
writeln( 'f(' ,n, ')=' ,f(n))
end.
2.2 如何設計遞歸算法
1.肯定遞歸公式
2.肯定邊界(終了)條件
練習:
用遞歸的方法完成下列問題
1.求數組中的最大數
2.1+2+3+...+n
3.求n個整數的積
4.求n個整數的平均值
5.求n個天然數的最大公約數與最小公倍數
6.有一對雌雄兔,每兩個月就繁殖雌雄各一對兔子.問n個月後共有多少對兔子
7.已知:數列1,1,2,4,7,13,24,44,...求數列的第 n項.
2.3典型例題
例3 快速排序
快速排序的思想是:先從數據序列中選一個元素,並將序列中全部比該元素小的元素都放到它的右邊或左邊,再對左右兩邊分別用一樣的方法處之直到每個待處理的序列的長度爲1,處理結束.
程序以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
programkspv;
var
a:array[0..10000]oflongint;
i,n:integer;
procedurequicksort(l,r:longint);
vari,j,mid:longint;
begin
i:=l;j:=r;mid:=a[(l+r)div2];
repeat
whilea[i]<middoinc(i);
whilea[j]>middodec(j);
ifi<=jthen
begin
a[0]:=a[i];a[i]:=a[j];a[j]:=a[0];
inc(i);dec(j);
end;
untili>j;
ifi<rthenquicksort(i,r);
ifl<jthenquicksort(l,j);
end;
begin
write( 'inputdata:' );
readln(n);
fori:=1tondoread(a[i]);
writeln;
quicksort(1,n);
write( 'outputdata:' );
fori:=1tondowrite(a[i], '' );
writeln;
end.
練習:
1.計算ackerman函數值:
n+1 m=0
ack(m,n)={ ack(m-1,1) m<>0,n=0
ack(m-1,ack(m,n-1)) m<>0,n<>0
求ack(5,4)
相關文章
相關標籤/搜索