二叉樹是經常使用的一種數據結構,今天記錄一下學習到的二叉樹的遍歷方法,其中包括遞歸方式和非遞歸方式的遍歷,這是在遍歷方法上的分類。在遍歷順序上分類,二叉樹的遍歷能夠分爲前序、中序、後序遍歷。所謂的前中後是指什麼時候訪問中間節點,即前序遍歷,則遍歷節點的順序爲:中-》左-》右;而中序遍歷,則遍歷節點的順序爲:左-》中-》右;以此類推,後序遍歷的節點的順序爲:左-》右-》中。數據結構
通常經常使用的二叉樹遍歷的方法爲遞歸的方式,由於其思路簡單,且代碼實現也十分簡易,這裏再也不贅述,直接上代碼:post
//後序遍歷,左=》右=》根 public void postorder_Traversal(TreeNode root) { if(root==null)return; postorder_Traversal(root.left); postorder_Traversal(root.right); //訪問節點的邏輯代碼塊 System.out.print(root.val+" "); } //前序遍歷,根=》左=》右 public void preorder_Traversal(TreeNode root) { if(root==null)return; //訪問節點的邏輯代碼塊 System.out.print(root.val+" "); preorder_Traversal(root.left); preorder_Traversal(root.right); } //中序遍歷,左=》根=》右 public void inorder_Traversal(TreeNode root) { if(root==null)return; inorder_Traversal(root.left); //訪問節點的邏輯代碼塊 System.out.print(root.val+" "); inorder_Traversal(root.right); }
接下來要講解的是非遞歸方式的遍歷二叉樹,相比遞歸方式,雖然非遞歸的方式遍歷二叉樹的思路比較複雜,而且實現也不易,可是非遞歸方式在效率方面,卻比遞歸方式高出很多,特別是當二叉樹十分龐大的時候,非遞歸方式能夠很快的遍歷徹底整棵樹,而遞歸方式則須要較長時間才能層層挖掘整棵樹,那麼接下來挨個上代碼,經過代碼講解非遞歸方式。學習
//前序非遞歸遍歷,根=》左=》右 public void preorder(TreeNode root) { Stack<TreeNode> stack=new Stack<>(); while(root!=null||!stack.isEmpty()) { //當前節點不爲空,則入棧,確保最後遍歷到的節點沒有左子節點 //由於是前序遍歷,因此再遍歷到每一個節點的時候,均可以先訪問,再尋找其左右子節點。 while(root!=null) { System.out.print(root.val+" "); stack.push(root); root=root.left; } if(!stack.empty()) { //把這兩步當作是一步,找到右節點,並把已處理的中節點從stack當中去除 root=stack.pop(); root=root.right; } } }
從代碼當中能夠看出,咱們藉助棧這個先進後出的特性,來存儲咱們遍歷過的節點。過程以下:spa
1.每遍歷一個節點的時候,先節點入棧,而後尋找當前節點的左子節點。(由於是前序遍歷,因此在節點入棧以前就能夠對節點進行訪問)code
2.當某個節點的左子節點,當左子節點不爲空的時候,重複過程1.blog
3.當左子節點爲空的時候將當前節點出棧,而且經過其尋找右子節點,右子節點不爲空的時候,重複過程1-2遞歸
4.當右子節點也爲空的時候,則跳回上一個該節點的父節點(即由於當前節點已經出棧,因此如今在棧中最上層的節點是當前節點的父節點)class
在瞭解完前序遍歷以後,咱們再看中序遍歷代碼:效率
//中序遍歷非遞歸 public void Inorder(TreeNode root) { Stack<TreeNode> stack=new Stack<>(); while(root!=null||!stack.isEmpty()) { //當前節點不爲空,則入棧,確保最後遍歷到的節點沒有左子節點 while(root!=null) { stack.push(root); root=root.left; } //經過當前節點,跳到當前節點的右節點,由於是中序遍歷,因此當前節點沒有左節點的時候,就能夠訪問當前節點 if(!stack.empty()) { root=stack.pop(); System.out.print(root.val+" "); root=root.right; } } }
經過對比兩個方法,發現代碼幾乎如出一轍,但惟一的不一樣的是,訪問節點的位置不同,中序遍歷是當左子節點被訪問過,或者不存在的時候,才能夠訪問中間節點,因此再該處,訪問節點的位置放在了當左子節點不存在的時候,即節點出棧的時候,便是左子節點不存在的時候進行訪問。二叉樹
最後咱們來看看爲難的非遞歸後序遍歷,與前兩個方法不一樣,後序遍歷是須要左右子節點都遍歷事後,才能訪問中間節點,那麼前邊的一個棧就沒法知足咱們的要求了,由於咱們須要一個標識位去肯定是不是已經完成了左右子節點的遍歷,結合代碼,來理解這個方法:
//後序遍歷非遞歸 public void Postorder(TreeNode root) { Stack<TreeNode> s =new Stack<>(); Stack<Integer> s2 =new Stack<>(); Integer i=new Integer(1); while(root!=null||!s.isEmpty()) { //只要當前節點非空,就入棧 while(root!=null) { s.push(root); s2.push(new Integer(0)); root=root.left; } //s2當中若是存1,則意味着當前s1對應的節點的左右子節點都已經遍歷過了。 while(!s.empty()&&s2.peek().equals(i)) { s2.pop(); System.out.print(s.pop().val+" "); } if(!s.isEmpty()) { s2.pop(); s2.push(new Integer(1)); root=s.peek(); root=root.right; } } }
從代碼當中能夠看出,咱們藉助兩個棧來存儲咱們的節點以及標示位,過程以下:
1.每遍歷一個節點的時候,先節點入棧s,而且s2入棧一個標識位0,而後尋找當前節點的左子節點。
2.當某個節點的左子節點,當左子節點不爲空的時候,重複過程1.
3.當左子節點爲空的時候將當前節點peek出(即將節點拿出,但棧中仍是有該節點),而且此時將s2對應棧頂的標識位改成1,經過其尋找右子節點,右子節點不爲空的時候,重複過程1-2
4.當右子節點也爲空的時候,而且s2對應的標識符爲1的時候,則彈出s1棧頂的當前節點,而且將s2的標識符彈出(即由於當前節點尚未出棧,因此如今在棧中最上層的節點是當前節),注意s1彈出當前節點並訪問,可是不賦值給root,在這個root此時仍是null
5.進入過程3,此時root被peek賦值到當前節點的父節點(由於在過程4當中,已經pop出了當前節點,因此s1棧頂是當前節點的父節點)的右子節點。
6.重複過程1-5
可能看了這個過程講解仍是有點不清楚,可是這個流程經過多讀幾回代碼,相信將來的本身仍是能讀懂的。
至此,二叉樹的遍歷就講解完畢。