本文目錄
0 前言
遞歸和回溯不是一個數據結構,但是它們是很經典很實用的經典算法,使用遞歸和回溯可以更加簡潔高效的解決我們的問題。
1 遞歸
1.1 什麼是遞歸
任何調用自身的函數稱爲遞歸。用遞歸方法求解問題,要點在於遞歸函數調用自身去解決一個規模比原始問題小一些的問題。這個過程稱爲遞歸步驟。遞歸步驟會導致更多的遞歸調用。因此,保證遞歸能夠終止是很重要的。每次函數都會用筆原問題規模更小的問題來調用自身。問題隨着規模不斷變小必須能最終收斂到基本情形。
1.2 爲什麼要用遞歸
在程序中,遞歸代碼通常比迭代代碼更加簡潔易懂。一般來說,在編譯或解釋時,循環會轉化爲遞歸函數。當任務能夠被相似的子任務定義時,採用遞歸處理十分有效。例如,排序,搜索和遍歷等問題往往有簡潔的遞歸解決方案。
1.3 遞歸函數的格式
遞歸函數在執行一個任務時,需要調用函數自身來完成一些子任務。在某些時候,函數不需要繼續調用函數自身就可以完成當前子任務。函數不再遞歸的情況稱作基本情形。而函數調用自身來執行子任務的情況就稱作遞歸情形。遞歸的格式如下:
if(判斷是否爲基本情形){
return 該基本情形時函數的值;
}else if(判斷是否爲另一種基本情形){
return 該基本情形時函數的值;
}else{
//遞歸情形
return (執行某些工作並遞歸調用);
}
1.4 遞歸和迭代
遞歸通過類似鏡像的方式來解決問題。當問題沒有明顯的答案是,遞歸方法通過簡化問題來解決它。但是,每次遞歸調用都會增加開銷(棧空間開銷)。遞歸和迭代對比描述如下:
1.4.1 遞歸
(1)當到達基本情形時,遞歸終止;
(2)每次遞歸調用都需要額外的空間用於棧幀(內存)開銷;
(3)如果出現無窮遞歸,程序可能會耗盡內存,並出現棧溢出;
(4)某些問題採用遞歸方法更容易解決。
1.4.2 迭代
(1)當循環條件爲假時,迭代終止;
(2)每次迭代不需要任何額外的空間開銷;
(3)由於沒有額外的空間開銷,所有若出現死循環,則程序會一直循環執行;
(4)採用迭代求解問題可能沒有遞歸解決方案那樣顯而易見。
1.5 遞歸算法的經典用例
常常需要用到遞歸來輔助解決問題有以下方面:
(1)斐波那契數列、階乘;
(2)歸併排序、快速排序;
(3)二分查找;
(4)樹的遍歷和許多樹問題:中序遍歷、前序遍歷、後序遍歷;
(5)圖的遍歷:深度優先搜索、廣度優先搜索;
(6)動態規劃例子;
(7)分治算法;
(8)漢諾塔;
(9)回溯算法。
2 回溯
2.1 什麼是回溯
回溯是一種採用分治策略進行窮舉搜索的方法。
(1)有時求解一個問題的最好算法是嘗試所有的可能性;
(2)這種方法通常很慢,但有標準工具能夠輔助該過程;
(3)工具:生成基本對象的算法,例如二進制串、排序、組合、一般字符串等;
(4)通過剪枝回溯可以加速的窮舉搜索。
2.2 回溯算法的經典用例
回溯算法經典用例有如下:
(1)二進制串:產生所有的二進制串;
(2)生成k進制串;
(3)揹包問題;
(4)廣義字符串;
(5)哈密頓迴路;
(6)圖着色問題。