本文屬xxKarina原創,轉載請注明
個人博客地址:https://xxkarina.github.io/
對于開發來說 ,常用的輔助技能,輔助知識不管是為了自己開發時候的便利性,還是工作中的需要,都是不可或缺的一部分,于是這里就簡單的說下遞歸
文章要點
- 遞歸是什么
- 遞歸怎么用
- 使用遞歸需要注意什么
遞歸是什么?
說到遞歸,咱們先來了解下迭代,遞歸可以說是迭代的特例,二者有相似之處也有區別
遞歸,簡單的說就是自己調用自己,自己包含自己
迭代,簡單的說就是將輸出作為輸入,更新內部的值
上面的代碼是直接在瀏覽器的控制臺編輯js代碼的,我們可以看到整個循環就是改變num 的值,num = num + i
這個語句我們可以看到,右邊的num就是以輸入值的形式進行運算的,但是他的值又是來源于上一個循環的輸出值,所以就有了
num = 0; //初始化
num = 0+0; //第一個循環,此時num的輸出值為0
num = 0+1; //第二個循環,此時num為0作為輸入值,計算得到新的num值為1
num = 1+2; //第三個循環,此時num為1作為輸入值,計算得到新的num值為2
3 = 3; //不滿足循環條件,退出,輸出結果值 3
這是一個很典型的斐波那契數列,1、1、2、3、5、8、13.....就是前兩個數的和等于這個數的數值,第一第二個數為1,于是我們就可以這樣分析:
f5 = f4 + f3;
f4 = f3 + f2;
f3 = f2 + f1;
f2 = 1;
f1 = 1;
// 于是就可以用解方程的思想算出 f5 = 1+1+1+1+1 = 5;
// 我們可以發現對于`fn`我們也可以直接考慮`fn`前兩個數的和就好了,
// 所以只要我們寫出一個可以計算前兩個值的和的函數,
// 然后再通過不斷的調用這個函數,傳入不同的參數,我們就可以得到我們的結果
整個運行的過程大致是這樣:
fun(5)
fun(4) + fun(3)
((fun(3) + fun(2))+(fun(2) + fun(1))
(((fun(2) + fun(1)) + fun(2))+(fun(2) + fun(1))
(((1+1)+1)+1+1)
5
遞歸雖然簡化了我們的計算量,但是細節問題,不注意就會適得其反,所以使用遞歸的時候我們需要注意三大問題:尤其有注意遞歸的結束條件
1.(遞歸前進段) 提取重復的邏輯部分(上例中的是,每個數等于其前兩個數的和)
2. (遞歸邊界段)控制邏輯邊界,什么時候停止調用自身(上例中的是,當num = 1或者num = 2的時候)
3. (遞歸返回段)結束的時候停止執行(退出)
遞歸怎么用?
遞歸在生活中并不罕見,遞歸的出現,給我們帶來了極大的便利,但是也有其弊端,遞歸相對于常用的循環結構,在某種情況下效率并沒有優勢,反而會顯得比較復雜,所以選擇遞歸的時候要適當,那我們要在什么時候選擇遞歸會比較好呢?
其主要用于解決三類問題
- 回溯算法——用遞歸解決問題
- Fibonacci,階乘——用遞歸定義問題數據
- 二叉樹的遍歷,圖的遍歷——用遞歸定義問題的結構
回溯算法類似枚舉搜索的過程,是一種深度優先搜索的策略,簡單的說就是,先在一條路試著走走,走不通了之后就回來再重新走別的路
二叉樹的遍歷有三種:前序遍歷、中序遍歷、后序遍歷
訪問結點本身(N); 遍歷該結點的左子樹(L); 遍歷該結點的右子樹(R)
- NLR 訪問根節點的操作在遍歷其左右子樹之前
- LNR 訪問根節點的操作在遍歷其左右子樹之中
- LRN 訪問根節點的操作在遍歷其左右子樹之后
圖的遍歷:指的是從圖中的任意一點出發,對每個點進行有且僅有一次的訪問,涉及的算法主要有兩個:
使用遞歸需要注意什么?
- 使用遞歸需要注意最重要的一點就是要有結束條件。
- 遞歸可以解決復雜問題、縮短程序代碼、提高編程效率等優點
函數在調用另一個函數時,需要把原來的函數的局部變量、返回地址等壓入堆棧(即所謂的保留現場),以達到正常返回和繼續執行。在一個函數進行遞歸調用時,每一次調用它本身,就象調用一個新的函數一樣,他的所有的局部變量都要在內存中保留一份(即壓棧),如果遞歸調用的層次過多,將出現“堆棧溢出錯誤”。因此選擇遞歸需要看情況。
使用的時候需要注意:
為防止遞歸持續不斷被調用,要有結束條件
遞歸但一般不能提高程序的執行效率。直接遞歸函數要不斷的調用自身,而間接遞歸會調用兩個以上的函數,占用內存空間,為避免這種情況,應盡量少用局部變量