數據結構碎碎念(一)

碎碎念


在大一學習C語言的時候,舉過一個用棧實現的括號匹配算法,當時以爲很難,不過如今回顧起來,這個算法也算是比較簡單的一個關於棧的應用了。而如今所常見的算法問題也都是什麼中綴表達式轉後綴表達式,雙棧找最小值之類的。難度比之括號匹配稍有提高,不過倒也算是必需要掌握的算法。java

上述所說的表達式求值在程序設計語言中是一個最基本的問題,也是棧的實現的一個典型範例。c++

爲何說是最基本? 咱們知道,中綴表達式對於人來講是比較友好的,學過四則運算就能夠對其求值,然而對於計算機來講,雖然也能夠想辦法計算,可是卻算不上友好了;相反,後綴表達式雖然對人不友好,可是倒是計算機所喜歡的。算法

(話說,後綴表達式在編譯原理中的重要性也是能棲身前列的。)數據結構


在C語言入門的時候,咱們就會經過遞歸來求斐波那契數列,很簡單:函數

int fibonacci(int n) {
	if (n==0 || n==1) return 1;
	return fibonacci(n-1) + fibonacci(n-2);
}
複製代碼

可是那時候還不懂原理,僅僅知道,遞歸就是函數調用其自己,可是接觸到數據結構的時候,再一次提出了遞歸的概念。性能

什麼是遞歸?遞歸就是函數調用其自己學習

reverse(know) {	// 1. go on
	if (you know) return you know; // 2. look 4
	else back to see the 1; // 3. go back to 1.
} // 4. you know what is recurision now. 
複製代碼

這時候,咱們不只知道遞歸真正的用法,同時也知道了一個事實,即遞歸程序的開銷一般很大,但與之相反的,其代碼量又是很是少的。優化

一般狀況下,咱們會選擇將遞歸程序改寫成非遞歸程序,即所謂消除遞歸,可是當改寫後和改寫前的程序並不會有太大的性能提高,咱們也沒有必要去改寫,好比:cout << fibonacci(5); ,爲了這樣的調用去消除遞歸,有必要嗎?ui

可實際狀況是,一個應用所要處理的數據並不算小,消除遞歸是不可避免的。spa

遞歸的精髓在於可否將原始的問題轉換爲屬性相同,但問題規模較小的問題,學過算法就知道,這一樣也是貪心策略和動態規劃的本質。

優化

對於遞歸程序的優化,咱們一般會選擇棧作輔助,爲何?咱們知道,在操做系統中,有一種叫作**「函數調用堆棧」**的名詞,大概的解釋就是:當在某一函數A中調用另外一函數B時,咱們將A中的內容保存後,壓入系統堆棧(你能夠說這是在建立還原點,也能夠說這個是現場保護,開心就好。),而後執行函數B的內容,當函數B執行結束後,將A從系統堆棧中彈出,繼續從斷點處執行,同時銷燬以前申請的棧空間。

同時,咱們要知道,操做系統的主存是由空間上限的,不多是無限的。系統堆棧的大小天然是受操做系統存儲空間大小的約束的,並且絕對小於系統存儲空間(不可能等於)。因此,當遞歸程序不斷申請棧空間到達系統棧所能分配的上限時,就有了所謂的「系統堆棧溢出」,即咱們一般所說的「爆棧」。

  • 斐波那契函數n=6時,遞歸調用樹

斐波那契函數n=6時,遞歸調用樹

  • n=3時,棧的申請狀況

    n=3時,棧的申請狀況

  • n=6時,棧的申請狀況

    n=6時,棧的申請狀況

java中,異常java.lang.StackOverflowError就是一種堆棧溢出錯誤,不過,能夠經過修改JVM參數來增大虛擬機棧空間,如-vm args-Xss128k;但這也只是權宜之計,治標不治本吶。

可是呢,一個遞歸程序並不必定非要用棧輔助改寫成非遞歸程序(即消除遞歸),有時候,一個循環就夠了。

int main() {
	int n, i=j=1, tmp=0;
	cin >> n;
	while (n--) {
		tmp = i+j;
		i = j;
		j = tmp;
	}
}
複製代碼

暫時就說這麼多,至於後面的,那就後面再說吧,畢竟這也只是(一)嘛。

相關文章
相關標籤/搜索