前言:二叉樹的遍歷形式有不少,好比前序、中序、後序、層序遍歷,在最近的一次面試中,面試官要求手寫層序遍歷代碼(非遞歸的形式),因而可知遍歷的重要性.本篇博客咱們就來看一下二叉樹的幾種遍歷方式.本篇博客語言均採用java實現:java
目錄:node
維基百科中對二叉樹是這樣定義的:(英文名:Binary tree)是每一個節點最多隻有兩個分支(即不存在分支度大於2的節點)的樹結構。一般分支被稱做「左子樹」或「右子樹」,二元樹的分支具備左右次序,不能隨意顛倒.以下圖,是一顆標準的二叉樹(其中標明瞭節點的屬性,好比左1、右二等,下面用到的示例均採用該樹)面試
二叉樹的遍歷是指按照必定的規則訪問二叉樹的每個結點而且查看它的值。有不少常見的順序來訪問全部的結點,並且每一種都有有用的性質。通常分爲前序遍歷(根 左 右),中序遍歷(左 根 右),後序遍歷(左 右 根)數據結構
前序遍歷是指按照根左右的順序依次遍歷,使用非遞歸遍歷,通常會用到棧,利用先進後出的特性來達到訪問二叉樹節點目的。來看一下post
①:首先將根節點放入到stack中存儲學習
②:遍歷棧,若是stack不爲空,直接彈出根節點測試
③:若是右節點不爲空,將右節點放入到棧中spa
④:若是左節點不爲空,將左節點放入到棧中code
解釋:由於棧都是後進先出的,因此在遍歷子樹的時候應該先將右節點放入棧中,再把左節點放入棧中.blog
/** * 前序遍歷 (根 左 右) * * * @param head */ public List<Integer> preOrderIteration(TreeNode head) { if (head == null) { return new ArrayList<>(); } // 結果集 List<Integer> resultList = new ArrayList<>(); // 棧 Stack<TreeNode> stack = new Stack<>(); // 首先放入頭節點 stack.push(head); while (!stack.isEmpty()) { TreeNode node = stack.pop(); resultList.add(node.val); // 放入右節點 if (node.right != null) { stack.push(node.right); } // 放入左節點 if (node.left != null) { stack.push(node.left); } } return resultList; }
按照上圖給的二叉樹節點,走完測試用例結果:
①一直遞歸的遍歷左子節點,而後彈出左子節點,直到不爲null
② 而後彈出最近的一個左節點,若是它的right節點不爲null,將當前的節點置爲right
③ 而後依次繼續①的步驟 遍歷到左子樹爲null中止
代碼實現:
/** * 中序遍歷 (左 根 右) * * @param head */ public static List<Integer> inOrderIteration(TreeNode head) { if (head == null) { return new ArrayList<>(); } // 結果集 List<Integer> resultList = new ArrayList<>(); TreeNode cur = head; Stack<TreeNode> stack = new Stack<>(); while (!stack.isEmpty() || cur != null) { // 一直先把左節點依次放入棧中 while (cur != null) { stack.push(cur); cur = cur.left; } TreeNode node = stack.pop(); resultList.add(node.val); if (node.right != null) { cur = node.right; } } return resultList; }
按照上圖給的二叉樹節點,走完測試用例結果:
後序遍歷須要用到兩個棧,能夠想想爲何須要兩個棧? stack1負責將節點的值依次存儲,stack2負責存儲stack1彈出的節點值 ,由於後序遍歷的根節點是最後遍歷的順序,所以須要一箇中轉的棧首先將根放入,而後再放入右節點,再放入左節點.最後再逆序(pop)出去就是後序遍歷的順序了。
①首先將根節點放入到stack1中,再彈出根節點,放入到stack2,保證stack2存儲的第一個節點是根節點
②再將根節點的左右節點依次放入到stack1中,注意這裏是先加入左節點,再加入右節點
③最終再將stack2的節點依次pop出去加入到結果集裏面
/** * 後序遍歷 (左 右 根) * * * @param head */ public static List<Integer> postOrderIteration(TreeNode head) { if (head == null) { return new ArrayList(); } List<Integer> resultList= new ArrayList<>(); Stack<TreeNode> stack1 = new Stack<>(); Stack<TreeNode> stack2 = new Stack<>(); stack1.push(head); while (!stack1.isEmpty()) { TreeNode node = stack1.pop(); stack2.push(node); if (node.left != null) { stack1.push(node.left); } if (node.right != null) { stack1.push(node.right); } } while (!stack2.isEmpty()) { resultList.add(stack2.pop().val); } return resultList; }
棧中節點的變化圖:
①聲明一個隊列,首先添加進去根節點
②彈出根節點,再依次加入左節點,再加入右節點
③依次循環,再彈出上一次的左節點,再加入左節點的左節點、左節點的右節點
/** * 層序遍歷 * @param root * @return */ public List<List<Integer>> levelOrder(TreeNode root) { // 返回的結果集 List<List<Integer>> res = new ArrayList<>(); // 隊列 Queue<TreeNode> queue = new LinkedList<>(); queue.add(root); while (!queue.isEmpty()) { List<Integer> temp = new ArrayList<>(); int size = queue.size(); for (int i = 0; i < size; i++) { TreeNode poll = queue.poll(); temp.add(poll.val); if (poll.left != null) { queue.add(poll.left); } if (poll.right != null) { queue.add(poll.right); } } res.add(temp); } return res; }
隊列中的數據變化圖(從下往上看):
二叉樹做爲一種經典的數據結構, 在實現遍歷的時候,採用遞歸的方式很是簡單。可是在面試中,一旦讓面試者寫非遞歸的實現方式,不少人就望而卻步了。對於藉助於棧仍是隊列按照何種規律來巧妙實現全部的遍歷,其實也是值得深刻學習的。本篇博客就分析了二叉樹的四種遍歷方式,所有采用非遞歸的方法,但願你們對於二叉樹的最基本操做遍歷有一個深入的理解。