数据结构-二叉树一

638 阅读7分钟

定义

二叉树的定义

二叉树(binary tree)是指树中节点的度不大于2的有序树,它是一种最简单且最重要的树。二叉树的递归定义为:二叉树是一棵空树,或者是一棵由一个根节点和两棵互不相交的,分别称作根的左子树和右子树组成的非空树;左子树和右子树又同样都是二叉树

  • 根节点:二叉树最顶层的节点
  • 分支节点:除了根节点以外且拥有叶子节点的节点
  • 叶子节点:没有子节点

满二叉树

一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。

完全二叉树

若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数(即1~h-1层为一个满二叉树),第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。满二叉树和完全二叉树的定义可以看出, 满二叉树是完全二叉树的特殊形态, 即如果一棵二叉树是满二叉树, 则它必定是完全二叉树。堆一般都由完全二叉树实现。

接下来是笔者自己对leetcode中关于二叉树的一些题做的分类,分三个系列。

数据结构系列-二叉树二

数据结构系列-二叉树三

二叉树的遍历

二叉树的前序遍历

给定一个二叉树,返回它的前序遍历。

输入: [1,null,2,3]  
   1
    \
     2
    /
   3 

输出: [1,2,3]

来源: leetcode-cn.com/problems/bi…

递归实现:

var preorderTraversal = function(root) {
    if(root === null) return [];
    let arr = [];
    let help = (root) => {
        if(!root) return;
        arr.push(root.val);
        help(root.left);
        help(root.right);
    }
    help(root);
    return arr;
};

迭代实现:

var preorderTraversal = function(root) {
    let nums = [];
    let stack = [];
    if (root) stack.push(root);
    while (stack.length) {
        root = stack.pop();
        nums.push(root.val);
        root.right && stack.push(root.right);
        root.left && stack.push(root.left);
    }
    return nums;
};

二叉树的中序遍历

给定一个二叉树,返回它的中序遍历。

输入: [1,null,2,3]
   1
    \
     2
    /
   3

输出: [1,3,2]

来源: leetcode-cn.com/problems/bi…

递归实现:

var inorderTraversal = function(root) {
    let arr = [];
    let traverse = (node) => {
        if(!node) return null;
        traverse(node.left);
        arr.push(node.val);
        traverse(node.right)
    }
    traverse(root)
    return arr;
};

迭代实现:

var inorderTraversal = function(root) {
    if(root === null) return root;
    let res = [],stack = [];
    stack.push(root);
    while (stack.length){
        while(root !== null){
            stack.push(root);
            root = root.left;
        }
        let node = stack.pop()
        res.push(node.val);
        root = node.right;
    }
    //根节点添加了两次
    return res.slice(0,res.length-1);
};

二叉树的后序遍历

给定一个二叉树,返回它的后序遍历。

输入: [1,null,2,3]  
   1
    \
     2
    /
   3 

输出: [3,2,1]

来源: leetcode-cn.com/problems/bi…

递归实现:

var postorderTraversal = function(root) {
    if(root === null) return [];
    let arr = [];
    let help = (root) => {
        if(!root) return;
        help(root.left);
        help(root.right);
        arr.push(root.val);
    }
    help(root);
    return arr;    
};

迭代实现:

var postorderTraversal = function(root) {
    if(root === null) return root;
    let res = [],stack = [];
    stack.push(root);
    while (stack.length){
        let node = stack.pop();
        res.push(node.val);
        node.left && stack.push(node.left);
        node.right && stack.push(node.right);
    }
    return res.reverse();
};

二叉树的层序遍历

给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。

输入: [3,9,20,null,null,15,7]

    3
   / \
  9  20
    /  \
   15   7

[
  [3],
  [9,20],
  [15,7]
]

来源: leetcode-cn.com/problems/bi…

BFS实现:

var levelOrder = function(root) {
    if(!root) return [];
    let queue = [];
    let res = [];
    let level = 0;
    queue.push(root);
    let temp;
    while(queue.length) {
        res.push([]);
        let size = queue.length;
        // 注意一下: size -- 在层次遍历中是一个非常重要的技巧
        while(size --) {
            // 出队
            let front = queue.shift();
            res[level].push(front.val);
            // 入队
            if(front.left) queue.push(front.left);
            if(front.right) queue.push(front.right);
        }
        level++;
    }
    return res;
};

DFS实现:

var levelOrder = function(root) {
    let res = [];
    let dfs = (node, level) => {
        if(!node) return;
        if(!res[level]) res[level] = [];
        res[level].push(node.val);
        dfs(node.left, level + 1);
        dfs(node.right, level + 1);
    }
    dfs(root, 0);
    return res;
};

二叉树的锯齿形层次遍历

给定一个二叉树,返回其节点值的锯齿形层次遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。

输入: [3,9,20,null,null,15,7]

    3
   / \
  9  20
    /  \
   15   7

[
  [3],
  [20,9],
  [15,7]
]

来源: leetcode-cn.com/problems/bi…

BFS实现:

var zigzagLevelOrder = function(root) {
    if(!root) return [];
    let queue = [root], res = [], level = 0;
    while(queue.length) {
        res.push([]);
        let size = queue.length;
        while(size--) {
            let front = queue.shift();
            res[level].push(front.val);
            if(front.left) queue.push(front.left);
            if(front.right) queue.push(front.right);
        }
        if(level % 2) res[level].reverse();
        level++;
    }
    return res;
};

DFS实现:

var zigzagLevelOrder = function(root) {
    var res = [];
    dfs(0, root);
    return res;
    function dfs(i, root){
        if(!root) return;
        if(!res[i]) res[i] = [];
        if(i & 1) res[i].unshift(root.val);
        else res[i].push(root.val);
        dfs(i+1, root.left);
        dfs(i+1, root.right);
    }
};

二叉树的层平均值

给定一个非空二叉树, 返回一个由每层节点平均值组成的数组。

输入: [3,9,20,null,null,15,7]

    3
   / \
  9  20
    /  \
   15   7

[3, 14.5, 11]
解释:第 0 层的平均值是 3 ,  第1层是 14.5 , 第2层是 11 。因此返回 [3, 14.5, 11] 。

来源: leetcode-cn.com/problems/av…

// BFS实现
var averageOfLevels = function(root) {
    let res = [];
    if(!root) return res;
    let queue = [root];
    while(queue.length) {
        let len = queue.length;
        let sum = 0;
        for (let i = 0; i < len; i++) {
            let node = queue.shift();
            sum += node.val;
            if(node.left) queue.push(node.left);
            if(node.right) queue.push(node.right);
        }
        res.push(sum / len)
    }
    return res;
};

二叉树的右视图

给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

输入: [1,2,3,null,5,null,4]
输出: [1, 3, 4]

来源: leetcode-cn.com/problems/bi…

// BFS实现
var rightSideView = function(root) {
    if(!root) return [];
    let queue = [root];
    let res = [];
    while(queue.length) {
        let path = [];
        let size = queue.length;
        while(size--) {
            let front = queue.shift();
            path.push(front.val);
            if(front.left) queue.push(front.left);
            if(front.right) queue.push(front.right);
        }
        res.push(path.pop());
    }
    return res;
};

从上到下打印二叉树

从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。

输入: [3,9,20,null,null,15,7]

    3
   / \
  9  20
    /  \
   15   7

[3,9,20,15,7]

来源: leetcode-cn.com/problems/co…

// BFS实现
var levelOrder = function(root) {
    if(!root) return [];
    let res = [];
    const queue = [root];
    while(queue.length) {
        const front = queue.shift();
        res.push(front.val);
        front.left && queue.push(front.left);
        front.right && queue.push(front.right);
    }
    return res;
};

N叉树

N叉树的前序遍历

给定一个N叉树,返回它的前序遍历。

输出: [1,3,5,6,2,4]

来源: leetcode-cn.com/problems/n-…

// 递归实现
var preorder = function(root) {
    let res = [];
    let help = (root) => {
        if(!root) return;
        res.push(root.val);
        for (let i = 0; i < root.children.length; i++) {
            help(root.children[i])
        }
    }
    help(root);
    return res;
};

N叉树的后序遍历

给定一个N叉树,返回它的后序遍历。

输出: [5,6,3,2,4,1]

来源: leetcode-cn.com/problems/n-…

// 递归实现
var postorder = function(root) {
    let res = [];
    let help = (root) => {
        if(!root) return;
        for (let i = 0; i < root.children.length; i++) {
            help(root.children[i])
        }
        res.push(root.val)
    }
    help(root);
    return res;   
};

N叉树的层序遍历

给定一个N叉树,返回它的层序遍历。

输出: 
[
     [1],
     [3,2,4],
     [5,6]
]

来源: leetcode-cn.com/problems/n-…

// BFS实现
var levelOrder = function(root) {
    if(!root) return [];
    let queue = [root], ans = [], level = 0;
    while(queue.length) {
        ans[level] = [];
        let levelNum = queue.length;
        while(levelNum--) {
            let front = queue.shift();
            ans[level].push(front.val);
            if(front.children && front.children.length > 0) {
                queue.push(...front.children);
            }
        }
        level++;
    } 
    return ans;   
};

N叉树的最大深度

给定一个 N 叉树,找到其最大深度。最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。

输出: 3

来源: leetcode-cn.com/problems/ma…

var maxDepth = function(root) {
    if(!root) return 0;
    let num = 0;
    if(root.children) {
        root.children.forEach(item => {
            let max = maxDepth(item);
            num = Math.max(max, num)
        })
    } 
    return num + 1;   
};

二叉树的序列化

二叉树的序列化与反序列化

请实现两个函数,分别用来序列化和反序列化二叉树。

    1
   / \
  2   3
     / \
    4   5     序列化为 "[1,2,3,null,null,4,5]"

来源: leetcode-cn.com/problems/xu…

var serialize = function(root) {
    if(!root) return [];
    let arr = [];
    let queue = [root];
    while(queue.length) {
        let node = queue.shift();
        if(!node) arr.push(null);
        else {
            arr.push(node.val);
            queue.push(node.left);
            queue.push(node.right);
        }
    } 
    return arr;   
};

var deserialize = function(data) {
    if(data.length === 0) return null;
    let root = new TreeNode(data.shift());
    let queue = [root];
    while(queue.length > 0) {
        let node = queue.shift();
        if(data.length <= 0) break;
        let left = data.shift();
        if(left === null) {
            node.left = null;
        } else {
            node.left = new TreeNode(left);
            queue.push(node.left)
        }
        if(data.length <= 0) break;
        let right = data.shift();
        if(right === null) {
            node.right = null;
        } else {
            node.right = new TreeNode(right);
            queue.push(node.right)
        }
    }
    return root;
};

验证二叉树的前序序列化

序列化二叉树的一种方法是使用前序遍历。当我们遇到一个非空节点时,我们可以记录下这个节点的值。如果它是一个空节点,我们可以使用一个标记值记录,例如 #

输入: "9,3,4,#,#,1,#,#,2,#,6,#,#"
输出: true    
     _9_
    /   \
   3     2
  / \   / \
 4   1  #  6
/ \ / \   / \
# # # #   # #

来源: leetcode-cn.com/problems/ve…

var isValidSerialization = function(preorder) {
    preorder = preorder.substring(0, preorder.length).split(',');
    let stack = [];
    for (let i = 0; i < preorder.length; i++) {
        stack.push(preorder[i]);
        while(stack[stack.length - 1] === '#' && stack[stack.length - 2] === '#') {
            stack.pop();
            stack.pop();
            stack[stack.length - 1] = '#';
        }
    }
    return stack.length === 1 && stack[0] === '#';
};

平衡/对称二叉树

平衡二叉树

给定一个二叉树,判断它是否是高度平衡的二叉树。本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点的左右两个子树的高度差的绝对值不超过1。

    3
   / \
  9  20
    /  \
   15   7
true

来源: leetcode-cn.com/problems/ba…

var isBalanced = function(root) {
    let ans = true;
    const dfs = (root) => {
        if(!root) return 0;
        let left = dfs(root.left);
        let right = dfs(root.right);
        if(Math.abs(left - right) > 1) ans = false;
        return Math.max(left, right) + 1;
    }
    dfs(root);
    return ans;
};

将二叉搜索树变平衡

给你一棵二叉搜索树,请你返回一棵 平衡后 的二叉搜索树,新生成的树应该与原来的树有着相同的节点值。

输入:root = [1,null,2,null,3,null,4,null,null]
输出:[2,1,3,null,null,null,4]
解释:这不是唯一的正确答案,[3,1,4,null,2,null,null] 也是一个可行的构造方案。

来源: leetcode-cn.com/problems/ba…

var balanceBST = function(root) {
    let valArr = [];
    let help = (root) => {
        if(!root) return;
        help(root.left);
        valArr.push(root.val);
        help(root.right);
    }
    help(root);
    let createTree = (l, r) => {
        if(l > r) return null;
        let mid = (l + r) >> 1;
        let t = new TreeNode(valArr[mid]);
        t.left = createTree(l, mid - 1);
        t.right = createTree(mid + 1, r);
        return t;
    }
    let res = createTree(0, valArr.length - 1);
    return res;
};

对称的二叉树

请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。

    1
   / \
  2   2
 / \ / \
3  4 4  3

来源: leetcode-cn.com/problems/sy…

var isSymmetric = function(root) {
    if(!root) return true;
    let queue = [root.left, root.right], node1, node2;
    while(queue.length) {
        node1 = queue.pop();
        node2 = queue.pop();
        if(!node1 && !node2) continue;
        if(!node1 || !node2 || node1.val !== node2.val) return false;
        queue.push(node1.left);
        queue.push(node2.right);
        queue.push(node1.right);
        queue.push(node2.left);
    }
    return true;
};

深度/宽度问题

二叉树的最大深度

给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

输入: [3,9,20,null,null,15,7]
输出: 3
    3   
   / \
  9  20
    /  \
   15   7

来源: leetcode-cn.com/problems/ma…

递归实现:

var maxDepth = function(root) {
    if(!root) return 0;
    return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
};

非递归实现:

var maxDepth = function(root) {
    if(root == null) return 0;
    let queue = [root];
    let level = 0;
    while(queue.length) {
        let size = queue.length;
        while(size --) {
            let front = queue.shift();
            if(front.left) queue.push(front.left);
            if(front.right) queue.push(front.right);
        }
        // level ++ 后的值代表着现在已经处理完了几层节点
        level ++;
    }
    return level;
};

二叉树的最小深度

给定一个二叉树,找出其最小深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

输入: [3,9,20,null,null,15,7]
输出: 2
    3   
   / \
  9  20
    /  \
   15   7

来源: leetcode-cn.com/problems/mi…

递归实现:

var minDepth = function(root) {
    if(!root) return 0;
    if(!root.left) return minDepth(root.right) + 1;
    if(!root.right) return minDepth(root.left) + 1;
    return Math.min(minDepth(root.left), minDepth(root.right)) + 1;
};

非递归实现:

var minDepth = function(root) {
    if(root == null) return 0;
    let queue = [root];
    let level = 0;
    while(queue.length) {
        let size = queue.length;
        while(size --) {
            let front = queue.shift();
            // 找到叶子节点
            if(!front.left && !front.right) return level + 1;
            if(front.left) queue.push(front.left);
            if(front.right) queue.push(front.right);
        }
        // level ++ 后的值代表着现在已经处理完了几层节点
        level ++;
    }
    return level;
};

N叉树的最大深度

给定一个 N 叉树,找到其最大深度。最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。

输出:3

来源: leetcode-cn.com/problems/ma…

var maxDepth = function(root) {
    if(!root) return 0;
    let num = 0;
    if(root.children) {
        root.children.forEach(item => {
            let max = maxDepth(item);
            num = Math.max(max, num)
        })
    } 
    return num + 1;   
};

二叉树的最大宽度

给定一个二叉树,编写一个函数来获取这个树的最大宽度。树的宽度是所有层中的最大宽度。这个二叉树与满二叉树(full binary tree)结构相同,但一些节点为空。 每一层的宽度被定义为两个端点(该层最左和最右的非空节点,两端点间的null节点也计入长度)之间的长度。

输入: 

           1
         /   \
        3     2
       / \     \  
      5   3     9 

输出: 4
解释: 最大值出现在树的第 3 层,宽度为 4 (5,3,null,9)。

来源: leetcode-cn.com/problems/ma…

var widthOfBinaryTree = function(root) {
    if(!root) return 0;
    let maxWidth = 0, res = [];
    help(root, 0, 0);
    return maxWidth;

    function help(root, level, num) {
        if(res[level]) {
            res[level].push(num)
        } else {
            res[level] = [num]
        }
        let tmpArr = res[level];
        let tmpWidth = tmpArr[tmpArr.length - 1] - tmpArr[0] + 1;
        if(tmpWidth > maxWidth) {
            maxWidth = tmpWidth
        }
        if(root.left) {
            help(root.left, level + 1, num * 2 + 1)
        }
        if(root.right) {
            help(root.right, level + 1, num * 2 + 2)
        }
    }
};