基础
概念
二叉树是一种 每个节点最多有两个子树 的树状数据结构。
常见的遍历顺序
-
深度遍历
前序:中→左→右
中序:左→中→右
后序:左→右→中(删除二叉树时会使用的遍历)
-
广度遍历
层序遍历(也可以使用深度遍历的思想)
遍历解析
首先定义树形结构,后面的遍历函数都是以此为基础的输入
/**
* 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) {
let result = []
var preOrderTraverseNode = (node) => {
if(node) {
// 先根节点
result.push(node.val)
// 然后遍历左子树
preOrderTraverseNode(node.left)
// 再遍历右子树
preOrderTraverseNode(node.right)
}
}
preOrderTraverseNode(root)
return result;
};
非递归版本
// 利用栈辅助
const preOrderTraverseUnRecur = (root) => {
let res = [];
let stack = [root];
let node = null;
while(stack.length !== 0) {
node = stack.pop();
// 第一步的时候,先访问的是根节点
res.push(node.val);
if(node.right) {
stack.push(node.right)
}
// 因为pop是取出最后一个元素,所以我们要确保首先拿到的是左节点
if(node.left) {
stack.push(node.left)
}
}
return res;
}
中序遍历
递归版本
var inorderTraversal = function(root) {
let result = []
var preOrderTraverseNode = (node) => {
if(node) {
preOrderTraverseNode(node.left);
result.push(node.val);
preOrderTraverseNode(node.right);
}
}
preOrderTraverseNode(root)
return result;
};
非递归版本
function inorderTraversal (root) {
let res = [];
let stack = [];
while(stack.length > 0 || root){
if(root){
stack.push(root);
root = root.left;
} else {
root = stack.pop();
if(root.val) res.push(root.val);
root = root.right;
}
}
return res;
}
后序遍历
递归版本
var inorderTraversal = function(root) {
let result = []
var preOrderTraverseNode = (node) => {
if(node) {
preOrderTraverseNode(node.left)
preOrderTraverseNode(node.right)
result.push(node.val)
}
}
preOrderTraverseNode(root)
return result;
};
非递归版本
var postorderTraversal = function(root) {
let res = [];
let stack = [];
if(root) stack.push(root);
while(stack.length){
root = stack.pop();
if(root.val) res.unshift(root.val);
if(root.left) stack.push(root.left);
if(root.right) stack.push(root.right);
}
return res;
};
// 解题思路: 后序遍历与前序遍历不同的是:
// 后序遍历是左右根
// 而前序遍历是根左右
// 如果我们把前序遍历的 list.push(node.val) 变更为 list.unshift(node.val) (遍历结果逆序),那么遍历顺序就由 根左右 变更为 右左根
// 然后我们仅需将 右左根 变更为 左右根 即可完成后序遍
应用
常见考察题目
二叉树最大深度
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
思路:
- 深度遍历
记录和比较每条路径的深度,取最大值
// 深度遍历,比较每条路径
var maxDepth = function(root) {
if(!root) return 0;
let maxDep = 0;
const deep = function(root, n){
if(root.val) n++;
maxDep = Math.max(maxDep, n);
if(root.left) deep(root.left, n);
if(root.right) deep(root.right, n);
}
deep(root, 0);
return maxDep;
};
- 广度遍历
由上自下,记录层数,直到没有下一层节点
// 广度遍历,记录每层的树节点
var maxDepth = function(root) {
if(!root) return 0;
let maxDep = 0;
let queue = [root];
while(queue.length){
maxDep++; // 层数
let curQue = []; // 记录下一层遍历的根节点
while(queue.length){
let node = queue.shift();
if(node.left) curQue.push(node.left);
if(node.right) curQue.push(node.right);
}
queue = curQue;
}
return maxDep;
};
对称二叉树
给定一个二叉树,检查它是否是镜像对称的。
思路:
- 递归
比较 左子树的左子树和右子树的右子树 & 左子树的右子树和右子树的左子树(看清楚哦)
// 递归
var isSymmetric = function(root) {
if (root === null) return true;
const isMirror = function(left, right){
if(left === null && right === null) return true;
if(left === null || right === null) return false;
return (left.val === right.val) && isMirror(left.left, right.right) && isMirror(left.right, right.left);
}
return isMirror(root.left, root.right);
};
- 迭代
记录每层的节点数据,比较reverse之后的节点数组和原始数组,一致则说明对称。 注意: 记得处理null的情况。
// 迭代
var isSymmetric = function(root) {
if (root == null) return true
let queue = [root]
while(queue.length){
let curArr = [];
let nextQue = [];
while(queue.length){
let node = queue.shift();
if(node === null){
curArr.push('null');
} else {
curArr.push(node.val);
nextQue.push(node.left);
nextQue.push(node.right);
}
}
if(Array.from(curArr).reverse().toString() !== curArr.toString()) return false;
queue = nextQue;
}
return true;
};
路径总和
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。
叶子节点 是指没有子节点的节点。
var hasPathSum = function(root, targetSum) {
if(root === null) return false;
// 判断暂停条件-路径走完,到叶子节点啦
if(root.val === targetSum && root.left == null && root.right == null) return true;
targetSum = targetSum - root.val;
return hasPathSum(root.left, targetSum) || hasPathSum(root.right, targetSum);
};