遞歸
若一個對象部分的包含自己,或用他自己給自己定義,被稱爲遞歸。
簡而言之,就是自己調用自己。
問大家一個問題,你認爲可以無限遞歸嗎?
首先,遞歸調用會進行入棧和棧幀回退,消耗棧空間;其次,我們棧空間有限,Win下的VS2008編譯器默認堆棧分配大小是1M,當棧空間不足時,會產生棧溢出錯誤。
最後,它不同於死循環,比如while(1),會一直佔用CPU,直至操作系統時間片用完,會由執行狀態轉換到就緒態。
接下來先看一段代碼
struct Node
{
int m;
Node node;
//Node* node1;
};
int main()
{
Node n;
return 0;
}
在你看來,這段代碼可以運行嗎?
答案是否定的,Node node;這行代碼會造成程序無限遞歸下去,編譯都無法通過。
編譯器會提示“不允許使用不完整的類型”,從根本上杜絕了你的錯誤,因爲我們在設計時,類中不能包含自己的實體。一般都會用註釋那段代碼,Node* node1。
斐波拉契數列在c語言中算是比較經典的,有遞歸和非遞歸實現方法,
斐波拉契數列,例1, 1, 2, 3, 5, 8, 13, 21…
//遞歸實現
int Fac(int n)
{
if(n == 1||n == 2)
return 1;
else
return Fac(n-1)+Fac(n-2);
}
遞歸下來,經測試,n=10時,Fac(10) = 55,需要函數調用109次;
n=20時,Fac(20) = 6765,需要函數調用13529次。
//非遞歸
int Fac1(int n)
{
int a = 1;
int b = 1;
int c = 1;
if(n <= 2)
return a;
for(int i = 3;i <= n;++i)
{
a = b + c;
b = c;
c = a;
}
return a;
}
非遞歸下來,相當於迭代,時間複雜度是O(n),效率高。
兩種方法對比一看,遞歸實現效率太低,很多步重複計算,n越大,重複計算量呈指數增長,所以一般斐波拉契數列不建議使用這種遞歸方法。
如果我們這裏把非遞歸實現中的循環改爲遞歸,情況會怎麼樣呢?
//循環改爲遞歸
int fac(int a,int b,int n)
{
if(n<=2)
return a;
else
return fac(a+b,a,n-1);
}
int fun(int n)
{
int a=1,b=1;
return fac(a,b,n);
}
測試數據後,效率和非遞歸實現相同,時間複雜度是O(n)。
還有一個問題是,我們一般輸出數組元素,簡單地循環輸出就可以,如果改成遞歸輸出,會是怎麼樣呢?
//n是數組br長度
void Print(int *br,int n)
{
if(n > 0)
{
cout<<br[n-1]<<" ";
Print_Array(br,n-1);
}
}//34,23,12
void Print_Array1(int *br,int n)
{
if(br == NULL || n < 1)//需進行參數檢查
return;
Print(br,n);
}
int main()
{
int arr[]={12,23,34};
int n = sizeof(arr)/sizeof(arr[0]);
Print_Array(arr,n);
return 0;
}
這樣輸出後,數組元素是逆序的,遞歸調用是先輸出數組元素,然後函數壓入棧,遞歸條件結束,最後棧回退。我們這裏可以將輸出和函數調用順序替換,這樣就會順序輸出數組元素。
void Print1(int *br,int n)
{
if(n > 0)
{
Print1(br,n-1);
cout<<br[n-1]<<" ";
}
}
將上邊這段代碼稍加修改,n-1處改爲–n,結果會是什麼?
void Print2(int *br,int n)
{
if(n > 0)
{
Print2(br,--n);
cout<<br[n-1]<<" ";
}
}
看到--n,不由地就想到了前置操作及特性,遞歸進行到結束條件,輸出br[n-1]會造成數組越界,輸出br[-1],是個隨機數,接着是數組前兩個元素。
再加修改,把–n改成n–,又會怎麼樣呢?相信這會的已經被各種–整懵了,那就猜一下結果。
void Print3(int *br,int n)
{
if(n > 0)
{
Print3(br,n--);
cout<<br[n-1]<<" ";
}
}
編譯器在處理後置時:是將值放入棧中,在輸出時直接出棧就行;
在處理前置時:是等運算完成後,直接從n的地址中取值。
這段代碼運行會奔潰,出現棧溢出錯誤。後置–會在函數調用完後執行–,n一直等於3,會無限遞歸下去,直至棧空間不足,出現棧溢出,程序奔潰。
引用是經常會使用到的,這裏對代碼再加修改,參數列表中進行引用n。
void Print4(int *br,int &n)
{
if(n > 0)
{
Print4(br,--n);
cout<<br[n]<<" ";
}
}
結果是12 12 12,先是會進行--操作,然後遞歸調用,遞歸條件結束時,n=0,這裏是引用n,是n的地址,輸出br[n]會連續輸出br[0]。
如果這裏是後置–,程序會編譯無法通過。因爲內置類型產生的臨時量放在寄存器中,具有常性,所以是不能改變的,不能使用普通的引用。
(只是平時學習的一些總結,如果有什麼錯誤或者問題,大佬們可以指點留言,互相交流學習嘛,謝謝支持!)