漫談遞歸思想

原文轉自:http://www.cnblogs.com/BLoodMaster/archive/2010/03/23/1692641.html

漫談遞歸思想

編程裏面估計最讓人摸不着頭腦的基本算法就是遞歸了。很多時候我們看明白一個複雜的遞歸都有點費時間,尤其對模型所描述的問題概念不清的時候,想要自己設計一個遞歸那麼就更是有難度了。今天我也花費了半個小時來搞明白二叉樹的平衡性的遞歸模型,首先我不明白什麼叫做平衡性,所以花費的時候大部分實在試探理解平衡性的含義。在搞明白的時候,我突然想到假如讓我來設計,在我知道平衡性的前提下,我是否可以建立如此簡潔的遞歸模型。爲此,我遇到的問題是我們到底在什麼情況下適用遞歸模型,並且遞歸模型如何建立。
數學都不差的我們,第一反應就是遞歸在數學上的模型是什麼。畢竟我們對於問題進行數學建模比起代碼建模拿手多了。 (當然如果對於問題很清楚的人也可以直接簡歷遞歸模型了,運用數模做中介的是針對對於那些問題還不是很清楚的人)自己觀察遞歸,我們會發現,遞歸的數學模型其實就是歸納法,這個在高中的數列裏面是最常用的了。

回憶一下歸納法:

歸納法適用於想解決一個問題轉化爲解決他的子問題,而他的子問題又變成子問題的子問題,而且我們發現這些問題其實都是一個模型,也就是說存在相同的邏輯歸納處理項。當然有一個是例外的,也就是遞歸結束的哪一個處理方法不適用於我們的歸納處理項,當然也不能適用,否則我們就無窮遞歸了。這裏又引出了一個歸納終結點以及直接求解的表達式。

如果運用列表來形容歸納法就是:

步進表達式:問題蛻變成子問題的表達式
結束條件:什麼時候可以不再是用步進表達式
直接求解表達式:在結束條件下能夠直接計算返回值的表達式
邏輯歸納項:適用於一切非適用於結束條件的子問題的處理,當然上面的步進表達式其實就是包含在這裏面了。
這樣其實就結束了,遞歸也就出來了。

遞歸算法的一般形式:

void func( mode)
{ 
	if(endCondition)
	{ 
	constExpression //基本項 
	} 
	else
	{
		accumrateExpreesion //歸納項
		mode = expression //步進表達式
		func(mode) //調用本身,遞歸
	}
}
最典型的就是N!算法,這個最具有說服力。理解了遞歸的思想以及使用場景,基本就能自己設計了,當然要想和其他算法結合起來使用,還需要不斷實踐與總結了。

例如:返回一個二叉樹的深度:

int depth(Tree t)
{ 
	if(!t) 
		return 0; 
	else 
	{ 
		int a = depth(t.right); 
		int b = depth(t.left); 
		return (a>b)?(a+1):(b+1); 
	} 
}

判斷一個二叉樹是否平衡:
int isB(Tree t)
{ 
	if(!t) 
	<span style="white-space:pre">	</span>return 0; 
	int left = isB(t.left); 
	int right = isB(t.right); 
	if(left>=0&&right>=0&&left-right<=1||left -right>=-1) 
		return (left<right)?(right +1):(left + 1); 
	else 
		return -1; 
}

上面這兩個遞歸的難易程度就不一樣了,第一個關於深度的遞歸估計只要瞭解遞歸思想的都可以短時間設計出來,但第二個估計就要長點時間了。純遞歸問題的難易主要糾結於一些條件表達式的構造以及初值的設置(上面的爲直接表達式值的設定)。

最後需要補充的是,很多不理解遞歸的人(今天在csdn裏面看到一個初學者的留言),總認爲遞歸完全沒必要,用循環就可以實現,其實這是一種很膚淺的理解。因爲遞歸之所以在程序中能風靡並不是因爲他的循環,大家都知道遞歸分兩步,遞和歸,那麼可以知道遞歸對於空間性能來說,簡直就是造孽,這對於追求時空完美的人來說,簡直無法接接受,如果遞歸僅僅是循環,估計現在我們就看不到遞歸了。遞歸之所以現在還存在是因爲遞歸可以產生無限循環體,也就是說有可能產生100層也可能10000層for循環。例如對於一個字符串進行全排列,字符串長度不定,那麼如果你用循環來實現,你會發現你根本寫不出來,這個時候就要調用遞歸,而且在遞歸模型裏面還可以使用分支遞歸,例如for循環與遞歸嵌套,或者這節枚舉幾個遞歸步進表達式,每一個形成一個遞歸。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章