前面總結了一下個人對遞歸的理解,接下來本來繼續記錄下遞歸與樹這種常用數據結構的恩怨情仇。
一、樹的概念
恩,話不多說,理解樹最好的方案之一就是看下面的丑圖:
恩,沒錯,樹,其實可以看成是一個鏈表,只不過每個鏈表節點有三個point罷了。(當然,用數組也可以實現樹,這個不討論。)
上面這種樹叫做三叉樹,就是每個樹幾點下面有三個樹節點。其他概念理解(層數,根節點,父子關系等請看圖)
當然,其實三茶樹或者說n叉樹用途並不是很廣,下面主要討論二叉樹的相關知識。
二叉樹就是每個節點有倆個指針指向下一個節點或者null(某個節點的point可以不指向節點的)
二、二叉樹的遍歷操作
說到樹,最常用的功能估計是遍歷操作了吧,下面簡單總結下遍歷樹的幾種方式。(主要說二叉樹)
遍歷分為四種:前序,中序,后序以及層序;
前、中、后序的定義是根據節點與左右子樹之間的先后順序進行定義區分的:如果節點先遍歷,然后遍歷左邊的子樹,然后再右邊的子樹,那么就是前序;類似地,如果節點遍歷順序是在右節點后左節點前,則是中序遍歷;如果節點遍歷順序在最后,則是后序遍歷。
1、下面是各個遍歷方法的示意圖,放在一起方便比較區別。
前序的節點遍歷順序圖 中序遍歷示意圖
后序遍歷的順序示意圖 層序遍歷其實就是每一層地遍歷,依次從左往右遍歷;
2、由示意圖,我們大概也清楚了樹的遍歷的大概方法了,下面討論下遍歷的實現方法(主要是前中后序的實現代碼,由於層序是用隊列來實現的,所以這里不討論)
其實,無論前序中序還是后序,遍歷時都是遞歸,前中后的區分只是遍歷時是先遞歸嵌套調用函數還是先遍歷當前值問題罷了。
A、前序遍歷的具體代碼:
//前序遍歷 static Stack<Integer> stack = new Stack<Integer>(); public void traversePreorder(TreeNodeInt node){ //先遍歷操作(這里將它打印出來並壓入棧) System.out.println(node.value); stack.push(node.value); //然后遞歸調用遍歷左節點 //先定義左子樹遞歸停止條件 if(node.left==null){ return ; }else{ traversePreorder(node.left); } //定義右字樹遞歸停止條件如下 if(node.right==null){ return ; }else{ traversePreorder(node.right); } }
B、中序遍歷代碼如下:
public void traversePreorder(TreeNodeInt node){ //先遍歷左節點 //先定義左子樹遞歸停止條件 if(node.left==null){ return ; }else{ traversePreorder(node.left); } //左邊字樹遍歷完,執行遍歷中間的操作 System.out.println(node.value); stack.push(node.value); //最后遍歷右節點 //定義右字樹遞歸停止條件如下 if(node.right==null){ return ; }else{ traversePreorder(node.right); } }
C、后序遍歷的代碼:
public void traversePreorder(TreeNodeInt node){ //先遍歷左節點 //先定義左子樹遞歸停止條件 if(node.left==null){ return ; }else{ traversePreorder(node.left); } //然后遍歷右節點 //定義右字樹遞歸停止條件如下 if(node.right==null){ return ; }else{ traversePreorder(node.right); } //左邊字樹遍歷完,最后執行遍歷中間的操作 System.out.println(node.value); stack.push(node.value); }
觀察上面三個遍歷方法的代碼,我們可以發現:他們其實是非常相似的(其實我都是根據前序copy過來的),只是將代碼執行的順序交換一下而已。
很明顯,遞歸它高度地反映了顯示的模型,這里中間節點與左右子樹被遍歷的順序體現在:是先執行遍歷操作還是先執行遞歸調用操作,代碼的順序正體現了我們思考的方式。所以說,從這個角度講,遞歸其實確實是簡潔而好理解。
好吧,遞歸和樹的關系大概就總結到這里,其實遞歸的作用遠遠不止解決這些問題,后面我會繼續整理相關例子,畢竟,要真的理解遞歸和將遞歸運用起來,的確不是一件容易的事情!