二叉樹是一種非常重要的數據結構,很多其它數據結構都是基於二叉樹的基礎演變而來的。對於二叉樹,有深度遍歷和廣度遍歷,深度遍歷有前序、中序以及後序三種遍歷方法,廣度遍歷即我們平常所說的層次遍歷。因爲樹的定義本身就是遞歸定義,因此採用遞歸的方法去實現樹的三種遍歷不僅容易理解而且代碼很簡潔,而對於廣度遍歷來說,需要其他數據結構的支撐,比如堆了。所以,對於一段代碼來說,可讀性有時候要比代碼本身的效率要重要的多。
四種主要的遍歷思想爲:
前序遍歷:根結點 ---> 左子樹 ---> 右子樹
中序遍歷:左子樹---> 根結點 ---> 右子樹
後序遍歷:左子樹 ---> 右子樹 ---> 根結點
層次遍歷:只需按層次遍歷即可
例如,求下面二叉樹的各種遍歷
前序遍歷:1 2 4 5 7 8 3 6
中序遍歷:4 2 7 5 8 1 3 6
後序遍歷:4 7 8 5 2 6 3 1
層次遍歷:1 2 3 4 5 6 7 8
一、前序遍歷
1)根據上文提到的遍歷思路:根結點 ---> 左子樹 ---> 右子樹,很容易寫出遞歸版本:
- public void preOrderTraverse1(TreeNode root) {
- if (root != null) {
- System.out.print(root.val+" ");
- preOrderTraverse1(root.left);
- preOrderTraverse1(root.right);
- }
- }
2)現在討論非遞歸的版本:
根據前序遍歷的順序,優先訪問根結點,然後在訪問左子樹和右子樹。所以,對於任意結點node,第一部分即直接訪問之,之後在判斷左子樹是否爲空,不爲空時即重複上面的步驟,直到其爲空。若爲空,則需要訪問右子樹。注意,在訪問過左孩子之後,需要反過來訪問其右孩子,所以,需要棧這種數據結構的支持。對於任意一個結點node,具體步驟如下:
a)訪問之,並把結點node入棧,當前結點置爲左孩子;
b)判斷結點node是否爲空,若爲空,則取出棧頂結點並出棧,將右孩子置爲當前結點;否則重複a)步直到當前結點爲空或者棧爲空(可以發現棧中的結點就是爲了訪問右孩子才存儲的)
代碼如下:
- public void preOrderTraverse2(TreeNode root) {
- LinkedList<TreeNode> stack = new LinkedList<>();
- TreeNode pNode = root;
- while (pNode != null || !stack.isEmpty()) {
- if (pNode != null) {
- System.out.print(pNode.val+" ");
- stack.push(pNode);
- pNode = pNode.left;
- } else { //pNode == null && !stack.isEmpty()
- TreeNode node = stack.pop();
- pNode = node.right;
- }
- }
- }
二、中序遍歷
1)根據上文提到的遍歷思路:左子樹 ---> 根結點 ---> 右子樹,很容易寫出遞歸版本:
- public void inOrderTraverse1(TreeNode root) {
- if (root != null) {
- inOrderTraverse1(root.left);
- System.out.print(root.val+" ");
- inOrderTraverse1(root.right);
- }
- }
2)非遞歸實現,有了上面前序的解釋,中序也就比較簡單了,相同的道理。只不過訪問的順序移到出棧時。代碼如下:
- public void inOrderTraverse2(TreeNode root) {
- LinkedList<TreeNode> stack = new LinkedList<>();
- TreeNode pNode = root;
- while (pNode != null || !stack.isEmpty()) {
- if (pNode != null) {
- stack.push(pNode);
- pNode = pNode.left;
- } else { //pNode == null && !stack.isEmpty()
- TreeNode node = stack.pop();
- System.out.print(node.val+" ");
- pNode = node.right;
- }
- }
- }
三、後序遍歷
1)根據上文提到的遍歷思路:左子樹 ---> 右子樹 ---> 根結點,很容易寫出遞歸版本:
- public void postOrderTraverse1(TreeNode root) {
- if (root != null) {
- postOrderTraverse1(root.left);
- postOrderTraverse1(root.right);
- System.out.print(root.val+" ");
- }
- }
2)非遞歸的代碼,暫且不寫
四、層次遍歷
層次遍歷的代碼比較簡單,只需要一個隊列即可,先在隊列中加入根結點。之後對於任意一個結點來說,在其出隊列的時候,訪問之。同時如果左孩子和右孩子有不爲空的,入隊列。代碼如下:
- public void levelTraverse(TreeNode root) {
- if (root == null) {
- return;
- }
- LinkedList<TreeNode> queue = new LinkedList<>();
- queue.offer(root);
- while (!queue.isEmpty()) {
- TreeNode node = queue.poll();
- System.out.print(node.val+" ");
- if (node.left != null) {
- queue.offer(node.left);
- }
- if (node.right != null) {
- queue.offer(node.right);
- }
- }
- }
五、深度優先遍歷
其實深度遍歷就是上面的前序、中序和後序。但是爲了保證與廣度優先遍歷相照應,也寫在這。代碼也比較好理解,其實就是前序遍歷,代碼如下:
- public void depthOrderTraverse(TreeNode root) {
- if (root == null) {
- return;
- }
- LinkedList<TreeNode> stack = new LinkedList<>();
- stack.push(root);
- while (!stack.isEmpty()) {
- TreeNode node = stack.pop();
- System.out.print(node.val+" ");
- if (node.right != null) {
- stack.push(node.right);
- }
- if (node.left != null) {
- stack.push(node.left);
- }
- }
- }
終於整理完了!