簡析遞歸
遞歸是編程過程中比較重要,也是比較簡單的算法之一,簡單的遞歸思路,往往比較容易理解,而略微複雜的遞歸則容易讓人頭痛。
遞歸主要分兩種
- 直接遞歸
直接遞歸函數裏面調用本身這個函數,實現遞歸 - 間接遞歸。
間接遞歸是兩個,或兩個以上的函數互相調用,正好形成一個循環,例如:A調用B,而B又調用A,在某種條件下,會實現間接遞歸。自然也可以是多個函數組成間接遞歸,不過考慮到邏輯問題,一般都不會這麼繞。
直接遞歸
public int Factorial( n)
{
if(n==0||n==1)
return 1;
else
return n * Factorial(n-1);
}
這是一個直接遞歸求階乘的例子,自然求階乘用遞歸不是一個好的方法,完全可以用循環來代替。由於這是一個單層次的問題,而用遞歸把每一次階乘都當作一個層次,而每一層都只有個元素,有些得不償失
看一下這個遍歷文件夾的例子
public static void getFileDir(File file){
//如果是文件夾,那就是直接遞歸,如果不是那就直接輸出文件名
if(file.isDirectory()) {
File[] files=file.listFiles();
for(int i=0;i<files.length;i++){
main.getFileDir(files[i]);
}
}
else{
System.out.println(file.getAbsolutePath());
}
}
思路相當的簡單,但是你只要給一個文件夾目錄,就可以遍歷出這個文件夾下面的所有文件名,而每一層都是一個目錄,同一層下有多個元素,就像一顆樹一樣。
遍歷的思想就體現出來了,樹形結構的遍歷不就是遞歸嗎!而線性結構的遍歷就是循環.
說一下遞歸的深度問題,有時候需要獲得遞歸的深度,好分組,像在爬蟲中就是使用遞歸的思想,但是如何分類呢?沒錯,就是根據深度來分,但如何知道這是第幾層呢(深度)。來讓我們小改一下程序
public static void getFileDir(File file,int deep){
if(file.isDirectory()) {
File[] files=file.listFiles();
for(int i=0;i<files.length;i++){
main.getFileDir(files[i],deep+1);
}
}
else{
System.out.println(file.getAbsolutePath()+" 深度爲:"+deep);
`
}
}
沒錯僅僅加了一個參數,deep,傳入1就可以了,多麼簡單,第二層輸出的是deep+1,第三層就是((deep+1)+1),那麼第一層的deep會變嗎?沒有變化!第二層永遠都會是二,我們沒有對deep做過賦值,只是改變傳入的參數而已(在前面遇到有不理解此問題的朋友,所有特意說明一下)
接下來看一下間接遞歸的例子
var time = 24*60*60*2; //倒計時兩天的時間,自己設定
//輸出信息
function begin(){
var today=new Date()
var day =today.getDate()
var dat=today.getMonth()
var future=day+2
document.getElementById('now').innerHTML="現在時間"+dat+"月"+day+"倒計時開始"
leasttime()
document.getElementById("future").innerHTML="預計結束時間"+dat+"月"+future
}
//時間倒計時函數
function leasttime(){
var ho=time/(60*60);
var mi=(time/60)%(60)
var se=time%60
mi=parseInt(mi)
ho=parseInt(ho)
ho=checkTime(ho)
se=checkTime(se)
mi=checkTime(mi)
time-=1;
document.getElementById("last").innerHTML=ho+":"+mi+":"+se
//倒計時結束
if(time==0){
// //重置計時器 ,再次開始計時
time=30;
begin()
}
setTimeout("leasttime()",1000);
}
//將時間的格式轉化一下
function checkTime(i)
{
if (i<10)
{i="0" + i}
return i
}
這是一個使用javascript完成的一個循環倒計時的計時器,由於在javascript中是解釋執行的,而且由於在主線程中,無法使用死循環執行循環倒計時的效果,瀏覽器會卡死並報錯。
總結一下遞歸的構成條件
- 子問題須與原始問題爲同樣的事,且更爲簡單;
- 不能無限制地調用本身,須有個出口,化簡爲非遞歸狀況處理。
遞歸的使用
1 遍歷樹形結構
2. 像javascript中間接遞歸的特例。
遞歸算法一般用於解決三類問題:
1.數據的定義是按遞歸定義的。(Fibonacci函數)
2.問題解法按遞歸算法實現。
這類問題雖則本身沒有明顯的遞歸結構,但用遞歸求解比迭代求解更簡單,如Hanoi問題。
3.數據的結構形式是按遞歸定義的。
如二叉樹、廣義表等,由於結構本身固有的遞歸特性,則它們的操作可遞歸地描述。
遞歸的缺點:
遞歸算法解題相對常用的算法如普通循環等,運行效率較低。因此,應該儘量避免使用遞歸,除非沒有更好的算法或者某種特定情況,遞歸更爲適合的時候。在遞歸調用的過程當中系統爲每一層的返回點、局部量等開闢了棧來存儲。遞歸次數過多容易造成棧溢出等。
如何設計遞歸算法
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項.
練習:
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)