二叉樹三種遍歷的遞歸和迭代解法


關於二叉樹的定義,以及什么是二叉樹的三種遍歷(先序遍歷,中序遍歷,后序遍歷),不是本文關注的重點,請自行查閱相關資料。本文的重點是如何用遞歸和迭代分別實現二叉樹的三種遍歷

leetcode上有三道題分別求三種遍歷結果:Binary Tree Preorder TraversalBinary Tree Inorder TraversalBinary Tree Postorder Traversal

遞歸###


遞歸解法不用多說,只需在遞歸部分的不同位置將節點value置入數組即可:

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
function dfs(root, ans) {
  if (!root) return;

  // 先序
  // ans.push(root.val);

  dfs(root.left, ans);

  // 中序
  // ans.push(root.val);
  dfs(root.right, ans);

  // 后序
  // ans.push(root.val);
}

var preorderTraversal = function(root) {
  var ans = [];
  dfs(root, ans);
  return ans;
};

迭代###


難點是迭代。

如果把二叉樹看做圖,那么二叉樹的遍歷其實就是圖的深度優先遍歷,而圖的深度優先遍歷能用手動模擬棧來解,那么二叉樹的遍歷也是可以的。

用棧模擬圖的深度優先遍歷,每次把父親節點的兒子節點的一個入棧,並刪除該兒子節點

當父親節點的所有兒子節點都被刪除時,父親節點出棧

我們以下面一棵二叉樹舉例:

      1
    /  \
   2    5
  / \
 3   4

如何用棧模擬遍歷該二叉樹?我們用 stack 數組模擬棧。

  1. 將root入棧。此時 stack = [1]
  2. 遍歷root的子節點,將左子節點入棧。 stack = [1, 2]
  3. 此時棧頂元素為 2,開始遍歷它的子節點。左子節點為 3, 入棧。 stack = [1, 2, 3]
  4. 此時棧頂元素為 3它沒有子節點,將它出棧。 stack = [1, 2]
  5. 此時棧頂元素為 2,遍歷它的子節點。左子節點已經被遍歷,於是右子節點,入棧。 stack = [1, 2, 4]
  6. 此時棧頂元素為 4,和 3 一樣它也沒有左右子節點,出棧。stack = [1, 2]
  7. 此時棧頂元素為 2 , 當父親節點的所有兒子節點都被刪除時,父親節點出棧,於是它出棧。 stack = [1]
  8. 此時棧頂元素為 1,它的左子節點已經遍歷,遍歷右子節點,將 5 入棧。 stack = [1, 5]
  9. 此時棧頂元素為 5, 它沒有子節點,出棧。 stack = [1]
  10. 此時棧頂元素為 1, 它的子節點都被遍歷過了,出棧。 stack=[]
  11. 此時stack數組長度為0,迭代結束。
  • 先序遍歷

先序遍歷只需按照如上的步驟模擬棧,在每次入棧的時候將節點的value值放入ans數組即可。

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */

var preorderTraversal = function(root) {
  if (!root) return [];

  var stack = []  // 棧模擬
    , ans = [];

  stack.push(root);
  ans.push(root.val);

  while (stack.length) {
    var elem = stack[stack.length - 1];
    if (elem.left) {
      ans.push(elem.left.val);
      stack.push(elem.left);
      elem.left = null;
    } else if (elem.right) {
      ans.push(elem.right.val);
      stack.push(elem.right);
      elem.right = null;
    } else
      stack.pop();
  }

  return ans;
};

還有一種更簡潔的代碼寫法,因為二叉樹父節點最多就兩個子節點,所以直接遍歷兩個節點,然后在棧中刪除父節點即可。

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */

var preorderTraversal = function(root) {
  if (!root) return [];

  var stack = []  // 棧模擬
    , ans = [];

  stack.push(root);

  while (stack.length) {
    var elem = stack.pop();
    ans.push(elem.val);
    
    if (elem.right)
      stack.push(elem.right);

    if (elem.left)
      stack.push(elem.left);
  }

  return ans;
};
  • 后序遍歷

和先序遍歷略有不同的是,先序遍歷是先遍歷父節點,所以父節點的value值要在入棧的時候就放入ans數組,而后序遍歷是最后遍歷父節點,所以當父節點出棧時(此時左右子樹都已經遍歷完畢),把節點的value值放入ans數組即可:

var postorderTraversal = function(root) {
  if (!root) return [];

  var stack = []  // 棧模擬
    , ans = [];

  stack.push(root);

  while (stack.length) {
    var elem = stack[stack.length - 1];
    if (elem.left) {
      stack.push(elem.left);
      elem.left = null;
    } else if (elem.right) {
      stack.push(elem.right);
      elem.right = null;
    } else {
        var a = stack.pop();
        ans.push(a.val);
      }
  }
  return ans;
};
  • 中序遍歷

中序遍歷是三大遍歷里最復雜的。先序遍歷是先遍歷父節點,所以節點入棧時存儲value值,后序遍歷是最后遍歷父節點,所以節點出棧時存儲value值,中序遍歷呢?

在leetcode中,中序遍歷這題比先序和后序多了一個tag - hash table,而如何hash也正是本題難點。中序遍歷是在父節點的左子樹遍歷完后,將父節點的value值存入ans數組的,那么如何判斷左子樹已經遍歷完了呢?

比如下面這棵二叉樹:

      1
    /  \
   2    5
  / \
 3   4

當遍歷到 2 這個節點時,它有左節點,按照先序和后序遍歷的做法,將左節點入棧,同時將 2 所在節點的left置為null,當節點 3 出棧后,判斷 2 節點的左右子節點,這時發現左節點為null,說明已經遍歷過了,於是 value=2 存入ans數組,然后 4 所在節點入棧,然后 4 再出棧,這時棧頂元素又是2,而這時再次判斷左右子節點,發現左子節點為null,認為左子節點遍歷過了,value=2再次存入ans數組!看到這里,你或許有點眉目了,我們不能用置為null來表示節點已經遍歷,而應該用正確的hash方式,這里我用 elem.left=1 表示elem的左節點已經被遍歷過了,用 elem.left=0 表示elem節點所在的值已經存入ans數組了。

var inorderTraversal = function(root) {
  if (!root) return [];
  
  var stack = [], ans = [];

  stack.push(root);

  while (stack.length) {
    var elem = stack[stack.length - 1];

    if (elem.left === 1) {
      elem.left = 0;
      ans.push(elem.val);
    } else if (elem.left) {
      stack.push(elem.left);
      elem.left = 1;
    } else if (elem.left === null) {
      elem.left = 1;
    } else if (elem.right) {
      stack.push(elem.right);
      elem.right = null;
    } else {
      stack.pop();
    }
  }

  return ans;
};


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM