數據結構第九篇——棧與遞歸
棧還有一個重要應用是在程序設計中實現遞歸。遞歸是計算機 科學和數學中一種解決問題的及其重要的方法。在數據結構中,可以用它來設計簡單。易於理解的算法,特別是在一些具有遞歸定義的結構上設計算法。
遞歸的概念
一個直接或間接地調用自己的函數,稱作遞歸函數。遞歸是程序設計中一個強有力的方法。
遞歸函數和運行時棧
棧還有一個重要應用就是在程序設計語言中實現函數調用。當一個函數在運行期間調用另一個函數時,在運行被調用函數之前,系統需要將實際參數和返回值地址等數據傳遞給被調函數,當函數調用時,這些數據與局部變量一起構成一條“工作記錄”,被壓入系統提供的棧。
下面用一個簡單的階乘函數的執行過程來說明遞歸與棧的關系。描述如下:
1 int fact(int w) 2 { 3 if(w==0) 4 return 1; 5 else 6 t=fact(w-1) 7 return (w*t); 8 }
遞歸算法的設計方法
遞歸時解決復雜問題的一種有效方法。如果掌握了遞歸算法的設計思路,就能比較容易地解決一類復雜的問題。但是什麽樣的問題可以用遞歸來解決呢?如何設計遞歸算法呢?
一般來說,如果一個復雜的問題能夠被分解成若幹個同類型的子問題,那麽這個問題可以用遞歸算法實現。
在設計遞歸算法時,應該遵循下面的規則:
①定義一個最小規模的問題,並給出其解。
②把復雜的問題劃分為同類型的若幹規模較小的子問題 ,並分別解決子問題。
③把各子問題旳解組合起來,即可得到原問題的解。
例如:漢諾塔問題:
1 void Hanoi(int n,char A,char B,char C) 2 { 3 if(n>0) //n=0是最小子問題,不做任何動作,直接退出 4 { 5 Hanoi(n-1,A,C,B); //子問題①:將A上面的n-1個盤子移到B上 6 Move(A,n.C); //將編號為n的圓盤從A移到C 7 Hanoi(n-1,B,A,C); //子問題②:將B上的n-1個盤子移到C上 8 }9 }
遞歸算法的分析
利用遞歸使我們設計算法和編程都非常簡單,可讀性高。但是往往導致算法效率比較低。
這體現了算法的簡單性與效率之間的矛盾。
用Fibonacci數列來說明這個問題。
1 long Fib(int n) 2 { 3 if(n==1||n==2) 4 return 1; 5 else 6 return Fib(n-1)+Fib(n-2); 7 }
從調用情況來看,相同實參的同一個函數被調用了多次。求Fib(6)時,Fib(3)被調用了3次,Fib(2)被調用了5次,總共調用的次數為15次。算法時間復雜度為O(2n)。
為了便於比較,下面給出非遞歸算法解決Fibonacci問題。
1 long FibIter(int n) 2 { 3 long oneback,twoback,current; 4 oneback=1; 5 twoback=1; 6 7 if(n==1||n==2) 8 return 1; 9 else 10 { 11 for(int i=0;i<n;i++) 12 { 13 current=oneback+twoback; 14 twoback=oneback; 15 oneback=current; 16 } 17 } 18 return current; 19 }
很顯然,以上算法當n>=3時,時間復雜度是n-2,遠小於遞歸算法的時間復雜度。
需要註意的是,在使用遞歸時必須權衡設計簡單與算法的時間和空間復雜度的關系,再有足夠並且效率要求不高的情況下可以使用遞歸。
數據結構第九篇——棧與遞歸