除了常规的树形结构,为了提高运算和搜索效率,还有一个非常常用的树形结构:二叉树。
注意:这里的 F 是右子节点,用数组表示 C 的子节点时,可以用
null
为左子节点占位,如 上图树结构可以用数组表示为[A, B, C, D, E, null, F, G, H, null, null, I]
对于不同的二叉树形式可以使用不同的存储方式。
value
表示值,left
、right
表示左右子节点。深度优先搜索算法(DFS:Depth First Search)
广度优先搜索算法(BFS:Breadth First Search)
根节点最先进行操作,按 根节点 -> 左子树 -> 右子树 顺序进行遍历。
A -> B -> D -> G -> H -> E -> C -> F -> I
根节点在中间进行操作,按 左子树 -> 根节点 -> 右子树 顺序进行遍历。
G -> D -> H -> B -> E -> A -> C -> I -> F
,请注意 F 是 C 的右子节点,所以此处 C 优先于 F 操作。根节点在最后进行操作,按 左子树 -> 右子树 -> 根节点 顺序进行遍历。
G -> H -> D -> E -> B -> I -> F -> C -> A
逐层地,从左到右访问所有节点。
A -> B -> C -> D -> E -> F -> G -> H
链接:144. 二叉树的前序遍历
给你二叉树的根节点 root
,返回它节点值的前序遍历。
示例:
// 示例1:
1
\
2
/
3
// 注意 2 是右子节点
输入: root = [1, null, 2, 3]
输出: [1, 2, 3]
// 示例2
输入: root = []
输出: []
// 示例3
输入: root = [1]
输出: [1]
// 示例4
1
/
2
// 注意 2 是左子节点
输入: root = [1, 2]
输出: [1, 2]
// 示例5
1
\
2
// 注意 2 是右子节点
输入: root = [1, null, 2]
输出: [1, 2]
进阶:递归算法很简单,你可以通过迭代算法完成吗?
/**
* 树节点的结构
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var preorderTraversal = function (root) {
// 用于存储遍历的结果
const res = []
// 设置函数用于进行递归遍历
const preorder = root => {
// 当前节点为空时,无序进行递归操作
if (root === null) {
return
}
// 记录根节点值
res.push(root.val)
// 前序遍历左子树
preorder(root.left)
// 前序遍历右子树
preorder(root.right)
}
preorder(root)
return res
}
使用递归算法非常适合遍历树形结构,采用前序、中序、后序遍历的区别,就在于下面这段代码的执行顺序:
// 记录根节点值
res.push(root.val)
// 前序遍历左子树
preorder(root.left)
// 前序遍历右子树
preorder(root.right)
解题思路:
维护一个栈结构,由于前序遍历在操作完根节点后优先操作左子树,遍历节点的时候将右子节点(不论是否是 null
)入栈,栈中存储的就是等待操作的节点,当遍历完左子树,再从栈中提取右子节点依次操作,操作右子树也遵循前序遍历的顺序进行入栈出栈。
var preorderTraversal = function (root) {
const res = []
const stack = []
while (root !== null || stack.length) {
while (root !== null) {
// 右子节点入栈
stack.push(root.right)
// 记录根节点
res.push(root.val)
// 下一步处理左子节点
root = root.left
}
// 左子树处理完毕,将 stack 出栈,处理右子树
root = stack.pop()
}
return res
}
链接:104. 二叉树的最大深度
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
示例:
3
/ \
9 20
/ \
15 7
给定的二叉树 [3,9,20,null,null,15,7]
返回它的最大深度 3
解题思路:
之前递归前序遍历二叉树操作时使用的方式称为深度优先搜索算法,以找到最深层叶节点为目的,计算二叉树最大深度同样可以采用这种方式。
/**
* @param {TreeNode} root
* @return {number}
*/
var maxDepth = function (root) {
if (!root) {
return 0
}
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1
}
链接:102. 二叉树的层序遍历
给你二叉树的根节点root
,返回其节点值的层序遍历。(即逐层地,从左到右访问所有节点)。
注意:返回的是一个二维数组结构,每个元素是每层的节点组成的数组。
示例:
// 示例1
3
/ \
9 20
/ \
15 7
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]
// 示例2
输入:root = [1]
输出:[[1]]
// 示例3
输入:root = []
输出:[]
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[][]}
*/
var levelOrder = function (root) {
if (root === null) {
return []
}
const res = []
// 声明队列用于存储后续数据
const queue = [root]
// 遍历队列
while (queue.length) {
// 针对本轮操作,创建一个新的数组
const arr = []
// 记录本轮要遍历的节点数量
let len = queue.length
while (len-- > 0) {
// 将本次操作的节点出队
const node = queue.shift()
// 记录节点值
arr.push(node.val)
// 检测是否存在左右子节点,如果右,入队即可
node.left && queue.push(node.left)
node.right && queue.push(node.right)
}
// 记录本轮遍历的节点
res.push(arr)
}
return res
}
链接:98. 验证二叉搜索树
给你一个二叉树的根节点 root
,判断其是否是一个有效的二叉搜索树。
有效二叉搜索树的定义如下:
示例:
// 示例1
2
/ \
1 3
输入:root = [2,1,3]
输出:true
// 示例2
5
/ \
1 4
/ \
3 6
输入:root = [5,1,4,null,null,3,6]
输出:false
解释:根节点的值是 5 ,但是右子节点的值是 4 。
解题思路:
创建一个辅助函数,递归检测节点是否符合条件,向函数传入比较的上限和下限;对于左子节点,上限就是当前节点,下限就是当前节点检测的下限(即当前节点作为右子节点的父节点或无限);对于右子节点,上限就是当前节点检测的上限(即当前节点作为左子节点的父节点或无限),下限就是当前节点;根节点的上限为 Infinity
,下限为 -Infinity
。
/**
* @param {TreeNode} root
* @return {boolean}
*/
var isValidBST = function (root) {
return helper(root, -Infinity, Infinity)
}
/**
*
* @param {*} root 检测的节点
* @param {*} lower 下限
* @param {*} upper 上限
*/
function helper(root, lower, upper) {
if (root === null) {
return true
}
// 检测当前节点值是否超出边界
if (root.val >= upper || root.val <= lower) {
return false
}
// 当前节点通过检测,再检测左右子节点
return helper(root.left, lower, root.val) && helper(root.right, root.val, upper)
}
图例的中序遍历结果:[3, 7, 9, 10, 12, 15]
可以发现中序遍历二叉搜索树的结果是一个升序列表,所以要验证一个二叉搜索树,就可以对其进行中序遍历,下一个节点总是大于上一个节点,则验证成功,否则验证失败。
之前的解题思路是一个递归遍历方式,下面使用中序遍历的迭代方式进行解题:
var isValidBST = function (root) {
const stack = []
// 声明一个变量,记录当前操作的节点,用于与下次获取的节点进行比较
let oldNode = -Infinity
while (root !== null || stack.length) {
while (root !== null) {
stack.push(root)
root = root.left
}
root = stack.pop()
if (root.val <= oldNode) {
return false
}
oldNode = root.val
root = root.right
}
return true
}