数据结构碎碎念(一)
碎碎念
在大一學習C語言的時候,舉過一個用棧實現的括號匹配算法,當時覺得很難,不過現在回顧起來,這個算法也算是比較簡單的一個關于棧的應用了。而現在所常見的算法問題也都是什么中綴表達式轉后綴表達式,雙棧找最小值之類的。難度比之括號匹配稍有提升,不過倒也算是必須要掌握的算法。
上述所說的表達式求值在程序設計語言中是一個最基本的問題,也是棧的實現的一個典型范例。
為什么說是最基本? 我們知道,中綴表達式對于人來說是比較友好的,學過四則運算就可以對其求值,然而對于計算機來說,雖然也可以想辦法計算,但是卻算不上友好了;相反,后綴表達式雖然對人不友好,但是卻是計算機所喜歡的。
(話說,后綴表達式在編譯原理中的重要性也是能棲身前列的。)
棧
在C語言入門的時候,我們就會通過遞歸來求斐波那契數列,很簡單:
int fibonacci(int n) {if (n==0 || n==1) return 1;return fibonacci(n-1) + fibonacci(n-2); } 復制代碼但是那時候還不懂原理,僅僅知道,遞歸就是函數調用其本身,但是接觸到數據結構的時候,再一次提出了遞歸的概念。
什么是遞歸?遞歸就是函數調用其本身。
reverse(know) { // 1. go onif (you know) return you know; // 2. look 4else back to see the 1; // 3. go back to 1. } // 4. you know what is recurision now. 復制代碼這時候,我們不僅知道遞歸真正的用法,同時也知道了一個事實,即遞歸程序的開銷通常很大,但與之相反的,其代碼量又是非常少的。
通常情況下,我們會選擇將遞歸程序改寫成非遞歸程序,即所謂消除遞歸,但是當改寫后和改寫前的程序并不會有太大的性能提升,我們也沒有必要去改寫,比如:cout << fibonacci(5); ,為了這樣的調用去消除遞歸,有必要嗎?
可實際情況是,一個應用所要處理的數據并不算小,消除遞歸是不可避免的。
遞歸的精髓在于能否將原始的問題轉換為屬性相同,但問題規模較小的問題,學過算法就知道,這同樣也是貪心策略和動態規劃的本質。
優化
對于遞歸程序的優化,我們通常會選擇棧做輔助,為什么?我們知道,在操作系統中,有一種叫做**“函數調用堆棧”**的名詞,大概的解釋就是:當在某一函數A中調用另一函數B時,我們將A中的內容保存后,壓入系統堆棧(你可以說這是在創建還原點,也可以說這個是現場保護,開心就好。),然后執行函數B的內容,當函數B執行結束后,將A從系統堆棧中彈出,繼續從斷點處執行,同時銷毀之前申請的棧空間。
同時,我們要知道,操作系統的主存是由空間上限的,不可能是無限的。系統堆棧的大小自然是受操作系統存儲空間大小的約束的,而且絕對小于系統存儲空間(不可能等于)。所以,當遞歸程序不斷申請棧空間到達系統棧所能分配的上限時,就有了所謂的“系統堆棧溢出”,即我們通常所說的“爆棧”。
- 斐波那契函數n=6時,遞歸調用樹
-
n=3時,棧的申請情況
-
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;} } 復制代碼暫時就說這么多,至于后面的,那就后面再說吧,畢竟這也只是(一)嘛。
總結
以上是生活随笔為你收集整理的数据结构碎碎念(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: WPF无边框拖动、全屏、缩放
- 下一篇: 从0到1学习Vue.js,包含例子及实战