1. 程式人生 > >遞迴:累加求和,累積求和,斐波那契疏忽列

遞迴:累加求和,累積求和,斐波那契疏忽列

遞迴
遞迴(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(小規模);     // 遞到最深處後,不斷地歸來
    }
}