二叉树的基础算法: 前中后序遍历及应用

244 阅读4分钟

本文主要记录一些二叉树的基本算法,比较简单的入门的算法,比如【前中序遍历,最大直径,最大深度,翻转,拉平,填充】等总共8道题,每道题基本上都有2种方式,感兴趣的可以看看。对编程思考还是比较有好处的。也可以用于你面试之前的快速回忆!建议收藏❤️

  • 遍历二叉树- 回溯算法
  • 分解子问题- 动态规划

动态规划(Dynamic Programming,DP): 是运筹学的一个分支,通过分解问题,求解[决策过程]最优化的过程。

回溯算法: 回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优[搜索]法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。

1. 二叉树的前序遍历

方式一:动态规划

分解成子问题

var preorderTraversal = function(root) {
    // 动态规划
    let res = [];
    if(!root){
        return [];
    }
    res.push(root.val); 
    res = res.concat(preorderTraversal(root.left));
    res = res.concat(preorderTraversal(root.right));
    return res;

};

方式二:回溯算法

外部变量 + 辅助函数

var preorderTraversal = function(root) {
    // 回溯算法
    let res = [];
    const traverse = ((root)=>{
        if(!root){
            return;
        }
        res.push(root.val);
        traverse(root.left);
        traverse(root.right);
    })
    traverse(root);
    return res;
};

2. 二叉树的中序遍历

方式一:动态规划

var inorderTraversal = function(root) {
    // 动态规划 分解 
    var res =[]; 
    if(!root){
        return res; 
    }
    res = res.concat(inorderTraversal(root.left));
    res.push(root.val); 
    res = res.concat(inorderTraversal(root.right));
    return res; 

};

方式二:回溯算法

二叉树遍历函数 + 外部变量

var inorderTraversal = function(root) {
    // 回溯算法 辅助函数 + 变量
    var res =[]; 
    const traverse  = ((root)=>{
        if(!root){
            return;
        }
        traverse(root.left);
        res.push(root.val);
        traverse(root.right);
    })
    traverse(root);
    return res;

};

3. 二叉树的后序遍历

方式一:动态规划

遍历二叉树

var postorderTraversal = function(root) {
      let res = [];
      if(!root){
          return res;
      }
      res = res.concat(postorderTraversal(root.left));
      res = res.concat(postorderTraversal(root.right));
      res.push(root.val);
      
      return res;
};

方式二:回溯算法

辅助函数 + 外部变量

var postorderTraversal = function(root) {
    // 遍历二叉树 
    let res = [];
    const traverse =((root)=>{
        if(!root){
            return;
        }
        traverse(root.left);
        traverse(root.right);
        res.push(root.val);
    })
    traverse(root);
    return res;
};

4. 求二叉树的最大深度

方式一: 动态规划(广度优先 BFS) ⭐️⭐️

var maxDepth = function(root) {
    // 动态规划
    if(!root){
        // 递归结束条件
        return 0;
    }
    const left  = maxDepth(root.left); 
    const right = maxDepth(root.right);  
    return 1 + Math.max(left, right);
};

方式二:回溯算法(深度优先 DFS)

外部变量 + 辅助函数

function maxDepth(root) {
    let depth = 0;
    let res = 0;
    // 遍历二叉树
    function traverse(root) {
        if (root === null) {
            return;
        }

        // 前序遍历位置
        depth++;
        // 遍历的过程中记录最大深度
        res = Math.max(res, depth);
        traverse(root.left);
        traverse(root.right);
        // 后序遍历位置
        depth--;
    }
    traverse(root);
    return res;
}

5. 翻转二叉树

方式一: 分解

var invertTree = function(root) {
    // 动态规划
    if(!root){
        return null;
    }
    // 先翻转左边
    // 再翻转右边 
    let left = invertTree(root.left);
    let right = invertTree(root.right); 
    root.left = right; 
    root.right = left;
    return root;

};

方式二:遍历

var invertTree = function(root) {
    // 遍历
    if(!root){
        return null;
    }
    // 左右交换
    var temp = root.right;
    root.right = root.left;
    root.left = temp;
    invertTree(root.left);
    invertTree(root.right);
    return root;

};

6. 二叉树的直径

方式一:动态规划

var diameterOfBinaryTree = function(root) {
    var maxDiameter = 0;
    var maxDepth = function(root){
        if(!root){
            return 0 ;
        }
        const left  = maxDepth(root.left);
        const right = maxDepth(root.right); 
        // 后序位置
        maxDiameter = Math.max(maxDiameter, left+right); 
        return 1 + Math.max(left, right);
    }
    maxDepth(root); 
    return maxDiameter;

};

方式二:复杂度比较高,不推荐❌

var BadSolution = function() {};

BadSolution.prototype.diameterOfBinaryTree = function(root) {
    if (root == null) {
        return 0;
    }
    // 计算出左右子树的最大高度
    let leftMax = this.maxDepth(root.left);
    let rightMax = this.maxDepth(root.right);
    // root 这个节点的直径
    let res = leftMax + rightMax;
    // 递归遍历 root.left 和 root.right 两个子树
    return Math.max(res,
            Math.max(this.diameterOfBinaryTree(root.left),
                    this.diameterOfBinaryTree(root.right)));
};

BadSolution.prototype.maxDepth = function(root) {
    if (root == null) {
        return 0;
    }
    let leftMax = this.maxDepth(root.left);
    let rightMax = this.maxDepth(root.right);
    return 1 + Math.max(leftMax, rightMax);
};

7. 114. 二叉树展开为链表

方式一:分解方式

⚠️只有分解的方式(因为题目希望我们原地拉平,而且不希望我们有返回值,所以不能用遍历)

/**
 * @param {TreeNode} root
 * @return {void} Do not return anything, modify root in-place instead.
 */
var flatten = function(root) {
    if(!root){
        return [];
    }
    // 先拉平
    flatten(root.left); 
    flatten(root.right);
    // 先把左右存起来
    const left = root.left;
    const right = root.right;
    // 重置左右的值
    root.left  =  null; 
    root.right = left;
    // 
    var p = root;   // p 不是链表结构,所以没有next,但是有right
    
    while(p.right){
        p = p.right;        
    }
    p.right = right;
   
};

方式二:遍历方式(但不符合此题目要求)

// 虚拟头节点,dummy.right 就是结果
var dummy = new TreeNode(-1);
// 用来构建链表的指针
var p = dummy;

function traverse(root) {
    if (root == null) {
        return;
    }
    // 前序位置
    p.right = new TreeNode(root.val);
    p = p.right;

    traverse(root.left);
    traverse(root.right);
}

8. 填充每个节点的下一个右侧节点指针

方式一:遍历三叉树

⚠️ 只有遍历方式

var connect = function(root) {
    if (root === null) return null;
    // 三叉数 遍历框架
    const traverse = ((node1, node2)=>{
        if(!node1 || !node2){
            return
        }
        node1.next = node2;
        traverse(node1.left, node1.right);
        traverse(node1.right, node2.left);
        traverse(node2.left, node2.right);

    })
    // 遍历「三叉树」,连接相邻节点
    traverse(root.left, root.right);
    return root;  
};