1. 程式人生 > >java遞迴和迴圈

java遞迴和迴圈

功能:求1-100的累加和

  • 方案1:使用迴圈
    • 方案2:使用遞迴
    • 遞迴:遞迴的基本思想就是“自己呼叫自己”,一個使用遞迴技術的方法將會直接或者間接的呼叫自己。
    • StackOverflowError:當應用程式遞迴太深而發生堆疊溢位時,丟擲該錯誤。
    • 遞迴結構
    • 1:遞迴盡頭:什麼時候不呼叫自己,如果沒有頭,將陷入死迴圈
    • 常見的遞迴頭:就是if判斷
    • 2:遞迴體
    • 什麼時候需要呼叫自身方法。
    • 注意:就算沒有遞迴頭,java中遞迴不會無限的遞迴下去,遞迴太深,堆疊記憶體會溢位
    • 遞迴次數和計算機本身的硬體有關係,以及程式本身,
    • 計算機越好,次數越多
    • 程式越簡單,次數越多
    • 面試題:迴圈個遞迴區別?
    • 遞迴演算法:
    • 優點:程式碼簡潔、清晰,並且容易驗證正確性。(如果你真的理解了演算法的話,否則你更暈)
    • 缺點:它的執行需要較多次數的函式呼叫,如果呼叫層數比較深,需要增加額外的堆疊處理,比如引數傳遞需要壓棧等操作,會對執行效率有一定影響。但是,對於某些問題,如果不使用遞迴,那將是極端難看的程式碼。
    • 迴圈演算法:
    • 優點:速度快,結構簡單。
    • 缺點:並不能解決所有的問題。有的問題適合使用遞迴而不是迴圈。如果使用迴圈並不困難的話,最好使用迴圈。
    • 遞迴演算法 和迴圈演算法總結
    • 一般遞迴呼叫可以處理的演算法,也通過迴圈去解決常需要額外的低效處理 。
    • 現在的編譯器在優化後,對於多次呼叫的函式處理會有非常好的效率優化,效率未必低於迴圈。

public class Demo {

	
	
	 static int i=1;
	
	public static void show(int sum){
		sum=sum+i; //業務程式碼1
		//遞迴頭
		if(i==100){
			System.out.println(sum);
			return;
		}
		i++;   //業務程式碼2
		show(sum); //遞迴體
	}
	
	
	public static void main(String[] args) {
		int sum = 0;
		show(sum);
	}

}

1,迴圈(loop,指的是在滿足條件的情況下,重複執行同一段程式碼。比如,while語句。 迴圈則技能對應集合,列表,陣列等,也能對執行程式碼進行操作。

2,迭代(iterate),**指的是按照某種順序逐個訪問列表中的每一項。比如,for語句。   迭代只能對應集合,列表,陣列等。不能對執行程式碼進行迭代。

3,遍歷(traversal),指的是按照一定的規則訪問樹形結構中的每個節點,而且每個節點都只訪問一次。 遍歷同迭代一樣,也不能對執行程式碼進行遍歷。

4,遞迴(recursion),指的是一個函式不斷呼叫自身的行為。

(1),通俗的解釋:遞迴就像往存錢罐裡存錢,先往裡邊塞錢,2塊,5塊,10塊這樣的塞,叫入棧。取錢的時候,後塞進去的先取出來,這叫出棧。具體多少錢,要全部出棧才知道。

(2),遞迴分類:線性遞迴和尾遞迴。

遞迴與迴圈的區別

1。遞迴演算法與迭代演算法的設計思路區別在於:函式或演算法是否具備收斂性,當且僅當一個演算法存在預期的收斂效果時,採用遞迴演算法才是可行的,否則,就不能使用遞迴演算法。

當然,從理論上說,所有的遞迴函式都可以轉換為迭代函式,反之亦然,然而代價通常都是比較高的。但從演算法結構來說,遞迴宣告的結構並不總能夠轉換為迭代結構,原因在於結構的引申本身屬於遞迴的概念,用迭代的方法在設計初期根本無法實現,這就像動多型的東西並不總是可以用靜多型的方法實現一樣。這也是為什麼在結構設計時,通常採用遞迴的方式而不是採用迭代的方式的原因,一個極典型的例子類似於連結串列,使用遞迴定義及其簡單,但對於記憶體定義(陣列方式)其定義及呼叫處理說明就變得很晦澀,尤其是在遇到環鏈、圖、網格等問題時,使用迭代方式從描述到實現上都變得很不現實。

2。遞迴其實是方便了程式設計師難為了機器。它只要得到數學公式就能很方便的寫出程式。優點就是易理解,容易程式設計。但遞迴是用棧機制實現的(c++),每深入一層,都要佔去一塊棧資料區域,對巢狀層數深的一些演算法,遞迴會力不從心,空間上會以記憶體崩潰而告終,而且遞迴也帶來了大量的函式呼叫,這也有許多額外的時間開銷。所以在深度大時,它的時空性就不好了。

迴圈其缺點就是不容易理解,編寫複雜問題時困難。優點是效率高。執行時間只因迴圈次數增加而增加,沒什麼額外開銷。空間上沒有什麼增加。

3。區域性變數佔用的記憶體是一次性的,也就是O(1)的空間複雜度,而對於遞迴(不考慮尾遞迴優化的情況),每次函式呼叫都要壓棧,那麼空間複雜度是O(n),和遞迴次數呈線性關係。

4。遞迴程式改用迴圈實現的話,一般都是要自己維護一個棧的,以便狀態的回溯。如果某個遞迴程式改用迴圈的時候根本就不需要維護棧,那其實這個遞迴程式這樣寫只是意義明顯一些,不一定要寫成遞迴形式。但很多遞迴程式就是為了利用函式自身在系統棧上的auto變數記錄狀態,以便回溯。

原理上講,所有遞迴都是可以消除的,代價就是可能自己要維護一個棧。而且我個人認為,很多情況下用遞迴還是必要的,它往往能把複雜問題分解成更為簡單的步驟,而且很能反映問題的本質。

首先,遞迴和遞推又一定的相似性(當然了,不然怎麼會提出這個問題?)

這兩個問題都可以描述為以下形式:

f(n)=g(f(n-1),…,f(0))

這是二者的共同特點。

不同點:

1,從程式上看,遞迴表現為自己呼叫自己,遞推則沒有這樣的形式。
2,遞迴是從問題的最終目標出發,逐漸將複雜問題化為簡單問題,最終求得問題
是逆向的。遞推是從簡單問題出發,一步步的向前發展,最終求得問題。是正向的。
3,遞迴中,問題的n要求是計算之前就知道的,而遞推可以在計算中確定,不要求計算前就知道n。
4,一般來說,遞推的效率高於遞迴(當然是遞推可以計算的情況下)

由於一切遞迴問題都可以轉化為迴圈求解,因此我們可以定義廣義遞迴:

如果轉化為迴圈後,需要自己維護堆疊,則仍稱為是遞迴的。

在這個定義下,有些問題適用於用遞迴求解,如梵塔問題有些問題適用於用遞推來做,如求滿足N!>M條件時最小的N。有些問題二者都可以,如給定N時的階乘問題。至於可讀性,與問題有關,不能一概而論。

遞迴其實就是利用系統堆疊,實現函式自身呼叫,或者是相互呼叫的過程。在通往邊界的過程中,都會把單步地址儲存下來,知道等出邊界,再按照先進後出的進行運算,這正如我們裝木桶一樣,每一次都只能把東西方在最上面,而取得時候,先放進取的反而最後取出。遞迴的資料傳送也類似。但是遞迴不能無限的進行下去,必須在一定條件下停止自身呼叫,因此它的邊界值應是明確的。就向我們裝木桶一樣,我們不能總是無限制的往裡裝,必須在一定的時候把東取出來。比較簡單的遞迴過程是階乘函式,你可以去看一下。但是遞迴的運算方法,往往決定了它的效率很低,因為資料要不斷的進棧出棧。這時遞推便表現出它的作用了,所謂遞推,就是免除了資料進出棧的過程。也就是說,不需要函式不斷的向邊界值靠攏,而直接從邊界出發,直到求出函式值。比如,階乘函式中,遞迴的資料流動過程如下:

f(3){f(i)=f(i-1)*i}–>f(2)–>f(1)–>f(0){f(0)=1}–>f(1)–>f(2)–f(3){f(3)=6}

而遞推如下:

f(0)–>f(1)–>f(2)–>f(3)

由此可見,遞推的效率要高一些,在可能的情況下應儘量使用遞推。但是遞迴作為比較基礎的演算法,它的作用不能忽視。所以,在把握這兩種演算法的時候應該特別注意。