以前的文章我們已經聊過了「 數組和鏈表 」、「 堆棧 」和「 隊列 」,今天我們來看看「 遞歸 」,固然「 遞歸 」並非一種數據結構,它是不少算法都使用的一種編程方法。它太廣泛了,而且用它來解決問題很是的優雅,但它又不是那麼容易弄懂,因此我特地用一篇文章來介紹它。
1、「 遞歸 」是什麼?
遞歸 就是指函數直接或間接的調用本身,遞歸是基於棧來實現的。遞歸的經典例子就是 斐波拉契數列(Fibonacci)。通常若是能用遞歸來實現的程序,那它也能用循環來實現。用遞歸來實現的話,代碼看起來更清晰一些,但遞歸的性能並不佔優點,時間複雜度甚至也會更大一些。算法
上圖爲 斐波拉契數列 圖例。
要實現遞歸,必須知足2個條件:編程
下面仍是以 斐波拉契數列(Fibonacci)爲例,咱們來理解一下遞歸:
斐波拉契數列就是由數字 1,1,2,3,5,8,13…… 組成的這麼一組序列,特色是每位數字都是前面相鄰兩項之和。若是咱們但願得出第N位的數字是多少?數組
能夠看出,這個邏輯是知足上面2個基本條件,假如求解 f(3),那 f(3)=f(2)+f(1),所以咱們得繼續去求解f(2),而 f(2)=f(1)+f(0),所以整個求解過程其實就在不斷的分解問題的過程,將大問題f(3),分解爲f(2)和f(1)的問題,以此類推。既然能夠分解成子問題,而且子問題的解決方法與大問題一致,所以這個問題是知足「可調用本身」的遞歸要求。
同時,咱們也知道應該在什麼時候中止調用本身,即當子問題變成了f(0)和f(1)時,就再也不須要往下分解了,所以也知足遞歸中「可中止調用本身」的這個要求。
因此,斐波拉契數列問題能夠採用遞歸的方式去編寫代碼,先看圖:微信
咱們將代碼寫出來:
int Fb(int n){
if(n<=1) return n==0?0:1;
return Fb(n-1)+Fb(n-2); //這裏就是函數本身調用本身
}
從上面的例子能夠看出,咱們寫遞歸代碼最重要的就是寫2點:數據結構
2、「 遞歸 」的算法實踐?
咱們看看常常涉及到 遞歸 的 算法題(來源leetcode):
算法題:實現 pow(x, n) ,即計算 x 的 n 次冪函數。
說明:
-100.0 < x < 100.0
n 是 32 位有符號整數,其數值範圍是 [−2^31, 2^31 − 1]
示例:
輸入: 2.00000, 10
輸出: 1024.00000
解題思路:
方法一:
暴力解法,直接寫一個循環讓n個x相乘嘛,固然了這種方式就沒啥技術含量了,時間複雜度O(1),代碼省略了。
方法二:
基於遞歸原理,很容易就找出遞推公式 f(n)=x*f(n-1),再找出遞歸中止條件即n==0或1的狀況就能夠了。不過稍微須要注意的是,由於n的取值能夠是負數,因此當n小於0的時候,就要取倒數計算。代碼以下:
class Solution {
public double myPow(double x, int n) {
if(n==0) return 1;
if(n==1) return x;
if(n<0) return 1/(x*myPow(x,Math.abs(n)-1));
return x*myPow(x,n-1);
}
}
這個方法其實也有問題,當n的數值過大時,會堆棧溢出的,看來也是不最佳解,繼續往下看。
方法三:
利用分治的思路,將n個x先分紅左右兩組,分別求每一組的值,而後再將兩組的值相乘就是總值了。即 x的n次方 等於 x的n/2次方 乘以 x的n/2次方。以此類推,左右兩組其實還能夠分別各自繼續往下分組,就是一個遞推思想了。可是這裏須要考慮一下當n是奇數的狀況,作一個特殊處理便可,代碼以下:
class Solution {
public double myPow(double x, int n) {
//若是n是負數,則改成正數,但把x取倒數
if(n<0) {
n = -n;
x = 1/x;
}
return pow(x,n);
}
private double pow(double x, int n) {
if(n==0) return 1;
if(n==1) return x;
double half = pow(x,n/2);
//偶數個
if(n%2==0) {
return half*half;
}
//奇數個
return half*half*x;
}
}
這種方法的時間複雜度就是O(logN)了。
以上,就是對數據結構中「 遞歸 」的一些思考。架構
碼字不易啊,喜歡的話不妨轉發朋友吧。😊
本文原創發佈於微信公衆號「 不止思考 」,歡迎關注。涉及 思惟認知、我的成長、架構、大數據、Web技術 等。函數