【數據結構Java實現】鏈表和遞歸

遞歸是極其重要的組建程序邏輯的一種方式,是作爲程序員,編寫計算機程序,學習計算機科學繞不過的一個話題。後面的【數據結構Java實現】都和遞歸脫不開關係。

我不會將遞歸作爲算法的一個專欄,因爲遞歸實際上是編寫程序的一種結構,和循環是等價的。遞歸真正難的地方,在於與算法思想相結合——如排序,分治,搜索,圖論算法,動態規劃等;在於與數據結構相結合——如數組,鏈表,二叉樹,紅黑樹,圖等;在於與遞歸定義的問題相結合——如漢諾塔,斐波拉契數列等。

一、遞歸思想介紹

遞歸本質上,是將原來的問題,轉換爲更小的同一個問題。如數組求和:

e.g.e.g.

  • Sum(arr[0...n1])=arr[0]+Sum(arr[1...n1])Sum(arr[0...n-1]) = arr[0] + Sum(arr[1...n-1]);更小的同一問題。
  • Sum(arr[1...n1])=arr[1]+Sum(arr[2...n1])Sum(arr[1...n-1]) = arr[1] + Sum(arr[2...n-1]);更小的同一問題…
  • Sum(arr[n1...n1])=arr[n1]+Sum(arr[ ])=arr[n1]Sum(arr[n-1...n-1]) = arr[n-1] + Sum(arr[\ ]) = arr[n - 1];最小的基本問題。

或許這是個很簡單的問題,但是它對於理解遞歸很重要。

public class Sum {
	public static int sum(int[] arr) {
		return sum(arr, 0);
	}
	//計算arr[l...n)這個區間的所有數字的和
	public static int sum(int[] arr, int l) {
		if (l == arr.length) //<----求解基本問題,但基本問題不能自動求解
			return 0;
		return arr[l] + sum(arr, l + 1);
	}
}

尋找基本問題是很簡單的,但是難的是如何把原問題轉換爲更小的問題,以及從更小的問題構建出原問題的答案。我們需要注意遞歸函數的宏觀語義,遞歸函數就是一個函數,完成一個功能。

最重要的,就是多練,熟能生巧。

二、鏈表的天然遞歸結構性質

一個鏈表:

0->1->2->3->4->NULL
可以看做:
0->一個更短的鏈表(少了一個結點) 長鏈表可以看做頭結點0連接一個短鏈表
0->[1->2->3->4->NULL]
...
最後有:
0->[1->[2->[3->[4->[NULL]]]]]

最後,NULLNULL本身也是一個鏈表,而且是最平凡最基本的鏈表,可以當做鏈表遞歸的邊界

我們在處理0->[1->2->3->4->NULL]這個長鏈表時,可以先處理[1->2->3->4->NULL]這個短鏈表,遞歸解決這個更小的鏈表中的問題……對於4->[NULL],我們先處理短鏈表[NULL],然後處理4這個頭結點……最後處理0這個頭結點。將得到的短鏈表和0組合起來,就是原問題的答案。

理解了上面的一段話,就明白了鏈表的天然結構性質,可以做一做題,203.remove linkedlist elements,解答在LeetCode C++ 203. Remove Linked List Elements【LinkedList】【遞歸】。當然,如果做不出來,也沒什麼,下面還會有更多的用遞歸解決的問題。

三、推而廣之

其實,我們也可以看出,數組也是一個遞歸數據結構,當然由於數組是連續的空間,用遞歸處理有些問題時會很麻煩。但是也因此有它的優點,比如可以很方便的分而治之,像堆排序、快速排序都是這種策略的典型使用。

像這樣的一維線性結構,都是遞歸數據結構。

但是由於線性結構太簡單了,一般用循環就可以很容易的做出答案,而用遞歸可能還需要想一想。難道遞歸這麼沒用嗎?絕對不是!等我們後面介紹到了非線性結構,介紹到了算法思想的時候,就會發現,很多時候用遞歸比用循環簡單太多了

四、總結

如果可以的話,我可能會把前面的單鏈表的操作全部補上遞歸的版本。

此外,還可能要實現的有雙鏈表,循環雙鏈表、數組鏈表等。

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