遞迴:累加求和,累積求和,斐波那契疏忽列
遞迴
遞迴(recursion )是指在定義自身的同時又出現了對自身的引用。
如果一個演算法直接或間接地呼叫自己,則稱這個演算法是一個遞迴演算法。
任何一個有意義的遞迴演算法總是由兩部分組成:遞迴呼叫與遞迴終止條件。
德羅斯特效應(Droste effect)是遞迴的一種視覺形式,是指一張圖片的某個部分與整張圖片相同,如此產生無限迴圈。這種圖片可以通過名為 Mathmap的數學軟體製作出來
注意:其實上面不是遞迴,遞迴不是無限迴圈。遞迴有去有回,上圖有去無回。
示例1:1+2+3+4......+100=? 演算法1:迴圈演算法 int sum = 0; for(int i=1;i<=100;i++){ sum += i; } System.out.println(sum); 時間複雜度T(n) =O(n) 空間複雜度S(n) =O(1) 演算法2:簡單演算法 System.out.println((1+100)*100/2); System.out.println(100*101/2);//0+1+2+..100=? 時間複雜度T(n) =O(1) 空間複雜度S(n) =O(1) 演算法3:遞迴演算法 sum(100) = sum(99) + 100 sum(99) = sum(98) + 99 ... sum(3) = sum(2) + 3; sum(2) = sum(1) +2; sum(1) = 1; public static int sum(int n){ if(n>1){ return sum(n-1)+n; }else{ return 1; } } 時間複雜度T(n) =O(n) 空間複雜度S(n) =O(n)
總結
1.遞迴的呼叫過程
每遞迴呼叫一次方法,都會在記憶體中分配空間
每執行完一次方法,都會釋放相應的空間
2.遞迴的優缺點
缺點:佔用記憶體多,效率低下
優點:思路和程式碼簡單
3.遞迴的適用場合
1.一個問題可被分解為若干層簡單的子問題
2.子問題和其上層問題的解決方案一致
3.外層問題的解決依賴於子問題的解決
4.一定要有遞迴的結束條件*
示例2:獲取斐波那契數列的第n項 * “兔子數列”,指的是這樣一個數列:1、1、2、3、5、8、13、21、34、…… * 1 2 3 4 5 6 7 8 9 * F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2) public class TestRecursion2 { public static void main(String[] args) { //使用迴圈實現 int numn_2 = 1; int numn_1 = 1; int numn=0 ; int n= 40; long startTime = System.currentTimeMillis(); for(int i=3;i<= n;i++ ){ //得到i項的值 numn = numn_1+ numn_2; //改變numn_2和numn_1的值 numn_2 = numn_1; numn_1 = numn; } long endTime = System.currentTimeMillis(); System.out.println("迴圈花費的時間:"+(endTime - startTime)); System.out.println(n+" "+numn); //使用遞迴實現 startTime = System.currentTimeMillis(); System.out.println(n+" "+fib(n)); endTime = System.currentTimeMillis(); System.out.println("遞迴 花費的時間:"+(endTime - startTime)); } public static int fib(int n){//1,2,3.....n //給結果指定初始值 int result = 0; //使用遞迴求結果 if(n==1 || n==2){ result = 1; }else{ result = fibo(n-2) + fibo(n-1); } //返回結果 return result; } }
* 總結
* 1.任何可用遞迴解決的問題也能使用迭代解決。
* 2.在要求高效能的情況下儘量避免使用遞迴,遞迴既花時間又耗記憶體。
遞迴的更多使用場合
1.二叉樹的定義和遍歷
2.遍歷圖
3.某些查詢演算法,比如折半查詢、二叉查詢樹
4.某些排序演算法,比如歸併和快速排序
5.複製/刪除資料夾(包括子資料夾),而不是複製/刪除檔案;dir /s 而不是 dir
6.字元全排列、字元迴文、漢諾塔
7.動態生成樹形結構的選單來實現資料的管理;或者是需要動態生成樹形的圖表結構。這些樹形結構往往沒有層級限制
閱讀資料
另類的說法
遞迴,顧名思義,其包含了兩個意思:遞 和 歸,這正是遞迴思想的精華所在
正如上面所描述的場景,遞迴就是有去(遞去)有回(歸來),如下圖所示。
“有去”是指:遞迴問題必須可以分解為若干個規模較小,與原問題形式相同的子問題,這些子問題可以用相同的解題思路來解決,
就像上面例子中的鑰匙可以開啟後面所有門上的鎖一樣;
“有回”是指 : 這些問題的演化過程是一個從大到小,由近及遠的過程,並且會有一個明確的終點(臨界點),
一旦到達了這個臨界點,就不用再往更小、更遠的地方走下去。
最後,從這個臨界點開始,原路返回到原點,原問題解決。
作業:
1.趣味問題——年齡。
有5個人坐在一起,問第五個人多少歲?他說比第4個人大2歲。
問第4個人歲數,他說比第3個人大2歲。問第三個人,又說比第2人大兩歲。
問第2個人,說比第一個人大兩歲。最後問第一個人,他說是10歲。
請問第n個人多大?用遞迴和迴圈演算法分別實現
2.使用遞迴和非遞迴實現字元迴文判斷
遞迴的作用在於把問題的規模不斷縮少,直到問題縮少到能簡單地解決
問:如何縮少問題規模?
答:通過觀察可以知道,一個迴文字串其中內部也是迴文。所以,我們只需要以去掉兩端的字元的形式一層層檢查,每一次的檢查都去掉了兩個字元,這樣就達到了縮少問題規模的目的。
新問題與原問題有著相同的形式
當去掉兩端字元後的字串,其產生的新問題同樣是檢查這個字串是否迴文。
遞迴的結束需要簡單情景
1. 字串長度可能會奇數或偶數:
如果字串長度是奇數,字串會剩下最中間那位字元,但其不影響迴文。當檢查到長度為1的時候即代表此字串是迴文
如果字串長度是偶數,當兩端的字串兩兩比較檢查後不會剩下字元。即檢查到長度為0的時候即代表此字串是迴文
2. 如果檢查到兩端兩個字元不相同。則說明此字串不是迴文,直接返回0,不需要繼續檢查
12345654321 ---->1(234565432)1---->2(3456543)2---->3(45654)3---->4(565)4---->5(6)5----6
1234554321 ---->1(23455432)1---->2(345543)2---->3(4554)3---->4(55)4---->55-----
3.使用遞迴和非遞迴實現字元全排列問題
比如a、b、c三個字元的全排列是
a b c
a c b
b a c
b c a
c b a
c a b
遞迴演算法:
由於全排列就是從第一個數字起每個數分別與它後面的數字交換
擴充套件:考慮有重複字元
斐波那契數列:
第一種寫法:
package com.bjsxt.com;
public class TestDiGui {
public static void main(String[] args) {
System.out.println(Fun(8));
}
private static int Fun(int i) {
if(i==1||i==2) {
return 1;
}else {
return Fun(i-1)+Fun(i-2);
}
}
}
第二種寫法:
package com.bjsxt.com;
public class TestDiGui {
public static void main(String[] args) {
System.out.println(fun(1, 1, 8));
}
private static int fun(int first,int second,int i) {
if(i>0) {
if(i==1) {
return first;
}else if(i==2) {
return second;
}else if(i==3){
return first+second;
}
return fun(second,first+second,(i-1));
}
return -1;
}
}
第三種使用迴圈方法:
package com.bjsxt.com;
public class TestDiGui {
public static void main(String[] args) {
System.out.println(fun(8));
}
private static int fun(int n) {
int first=1;
int second=1;
int sum=0;
for (int i=3;i<=n;i++) {
sum=first+second;
first=second;
second=sum;
}
return sum;
}
}
第四種:使用陣列的方式:
package com.bjsxt.com;
public class TestDiGui {
public static void main(String[] args) {
fun(9);
}
private static void fun(int n) {
int arr []=new int [20];
arr[1]=1;
arr[2]=1;
arr[3]=arr[1]+arr[2];
for(int i=3;i<n;i++) {
arr[i]=arr[i-1]+arr[i-2];
System.out.println(arr[i]);
}
}
}
遞迴求和:
package com.bjsxt.com;
public class TestDiGui {
public static void main(String[] args) {
System.out.println(fun(8));
}
private static int fun(int i) {
if(i==1) {
return 1;
}else {
return fun(i-1)+i;
}
}
}
遞迴求階乘:
①:使用遞迴的方式:
package com.bjsxt.com;
public class TestDiGui {
public static void main(String[] args) {
System.out.println(fun(5));
}
private static int fun(int i) {
if(i==1) {
return 1;
}else {
return fun(i-1)*i;
}
}
}
②:使用迴圈的方式:
package com.bjsxt.com;
public class TestDiGui {
public static void main(String[] args) {
System.out.println(fun(8));
}
private static int fun(int i) {
int sum=1;
while(i>0) {
sum=sum*i;
i--;
}
return sum;
}
}
總之:
1、遞迴的優點:思路和程式碼簡單;缺點:佔用時間和記憶體均比較大,耗時耗力。
2、大多數使用遞迴能夠解決的問題,都可以使用迴圈來解決
3、遞迴的書寫步驟:
先寫結束條件,再寫非結束條件;
function recursion(大規模){
if (end_condition){ // 明確的遞迴終止條件
end; // 簡單情景
}else{ // 在將問題轉換為子問題的每一步,解決該步中剩餘部分的問題
solve; // 遞去
recursion(小規模); // 遞到最深處後,不斷地歸來
}
}