遞迴演算法就是通過解決同一問題的乙個或多個更小的例項來最終解決乙個大問題的演算法。為了在c語言中實現遞迴演算法,常常使用遞迴函式,也就是說能呼叫自身的函式。遞迴程式的基本特徵:它呼叫自身(引數的值更小),具有終止條件,可以直接計算其結果。
在使用遞迴程式時,我們需要考慮程式設計環境必須能夠保持乙個其大小與遞迴深度成正比例的下推棧。對於大型問題,這個棧需要的空間可能妨礙我們使用遞迴的方法。
乙個遞迴模型為分治法,最本質的特徵就是:把乙個問題分解成獨立的子問題。如果子問題並不獨立,問題就會複雜的多,主要原因是即使是這種最簡單演算法的直接遞迴實現,也可能需要難以想象的時間,使用動態規劃技術就可以避免這個缺陷。
例如,斐波那契數列的遞迴實現如下:
int f(int i)
千萬不要使用這樣的程式,因為它的效率極低,需要指數級時間。相比之下,如果首先計算前n個斐波那契數,並把它們儲存在乙個陣列中,就可以使用線性時間(與n成正比)計算f。
f[0] = 0;f[1] = 1;for(i = 2; i <= n; i++)
f[i] = f[i-1] + f[i-2];
這個技術(dp)給了我們乙個獲取任何遞迴關係數值解的快速方法。在斐波那契數的例子中,我們甚至可以捨棄陣列,只需要儲存前兩個值,這種思想有時能大幅**,例如hdu-1003的第二種**。
由上面的討論我們可以得出這樣的結論:我們可以按照從最小開始的順序計算所有函式值來求任何類似函式的值,在每一步使用先前已經計算出的值來計算當前值,我們稱這項技術為自底向上的動態規劃。只要有儲存已經計算出的值的空間,就能把這項技術應用到任何遞迴計算中,就能把演算法從指數級執行時間向線性執行時間改進。
自頂向下的動態規劃甚至是乙個更簡單的技術,這項技術允許我們執行函式的代價與自底向上的動態規劃一樣(或更小),但是它的計算是自動的。我們實現遞迴程式來儲存它所計算的每乙個值(正如它最末的步驟),並通過檢查所儲存的值,來避免重新計算它們的任何項(正如它最初的步驟)。這種方法有時也稱作為備忘錄法(記憶化搜尋)。
斐波那契數--dp+記憶化搜尋
通過把所計算的值儲存在遞迴過程的外部陣列中,明確地避免重複計算。這一程式計算的時間與n成正比。
int f(int i)
性質:動態規劃降低了遞迴函式的執行時間,也就是減少了計算所有小於或等於給定引數的遞迴呼叫所要求的時間,其中處理一次遞迴呼叫的時間為常量。
我們不需要把遞迴引數限制到單整形引數的情況。當有乙個帶有多個整形引數的函式時,可以把較小子問題的解儲存在多維陣列中,乙個引數對應陣列的一維。其他那些完全不涉及整形引數的情形,就使用抽象的離散問題公式,它能讓我們把問題分解為乙個個的小問題。
在自頂向下的動態規劃中,我們儲存已知的值;在自底向上的動態規劃中,我們預先計算這些值。
我們常常選擇自頂向下的動態規劃而不選自底向上動態規劃,其原因如下:
1 自頂向下的動態規劃是乙個自然的求解問題的機械轉化。
2 計算子問題的順序能自己處理。
3 我們可能不需要計算所有子問題的解。
我們不能忽視至關重要的一點是,當我們需要的可能的函式值的數目太大以至於不能儲存(自頂向下)或預先計算(自底向上)所有值時,動態規劃就會變得低效。自頂向下動態規劃確實是開發高效的遞迴演算法實現的基本技術,這類演算法應納入任何從事演算法設計與實現所需的工具箱。
對遞迴的理解
昨天和宿舍一哥們討論二叉樹中求最近公共父節點問題時,才發現原來對遞迴的理解都是錯的,其實在程式內部分配的棧和資料結構的棧功能基本一樣,當然前者的棧還涉及到棧幀,函式內部訪問某個棧幀的元素並不一定是從棧頂訪問。舉乙個簡單的例子,遍歷乙個二叉樹,無論是非遞迴演算法和遞迴演算法時間複雜度都是o n 以前認...
對遞迴模型的理解
一.遞迴模型 分而治之的思想也就是典型的遞迴思想,遞迴思想的核心就是 遞迴模型 的建立,遞迴模型 就是處理這類問題的乙個 相同的框架,這個框架不僅僅是處理總問題的框架,也是處理組成總問題的子問題的框架,這個框架具有公用性,要適用這種公用性,就可以推斷得出,這類問題的結構就有遞迴性質 從前有座上,山上...
對尾遞迴的理解
遞迴,在程式執行過程中呼叫自己,每一級遞迴都需要呼叫函式,會建立新的棧空間,隨著遞迴深度的增加,建立的棧越來越多,造成棧的 尾遞迴基於函式的尾呼叫,每一級呼叫直接返回函式的返回值更新呼叫棧,而不是建立新的呼叫棧,類似迭代的實現,時間和空間上均優化了一般的遞迴 存在的問題,python不支援尾遞迴,遞...