基本概念
二叉树是一种重要的树形数据结构,在计算机科学中广泛应用。它由节点(也称为顶点)和边组成,每个节点最多有两个子节点,通常称为左子节点和右子节点。
满二叉树
如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。比如这种

满二叉树,也可以说深度为k,有2^k-1个节点的二叉树
完全二叉树
完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层(h从1开始),则该层包含 1~ 2^(h-1) 个节点。
换句话说,完全二叉树可以看作是满二叉树从右向左删除若干节点得到的。
存储
二叉树存储可以链式存储,也可以顺序存储,链式存储用的是指针,而顺序存储用的就是数组,链式存储是通过指针将分布在各个地址的节点串联在一起,而顺序存储就是存储的元素在内存中是连续分布的。链式存储比较简单了,这里看下顺序存储,也就是数组存储。
看下这个图

有一个规律就是:父节点数组下标是i,那么左孩子就是i^2+1,右孩子就是i^2+2
上面说的只是索引,也就是在数组中的位置,这个索引和二叉树的值没有任何关系,只是在将数组转成二叉树时,会根据索引来确定节点关系,比如跟节点索引是0,那么它的左孩子索引必然是i^2+1,也就是1,右孩子就是i^2+2,也就是2,当然这里的1和2,就是索引,取得就是数组中对应索引的值。
接下来写一个将数组转为树的方法。
ini
class TreeNode {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
}
function arrayToCompleteBinaryTree(arr, index = 0) {
if (index >= arr.length) {
return null;
}
const root = new TreeNode(arr[index]);
root.left = arrayToCompleteBinaryTree(arr, 2 * index + 1);
root.right = arrayToCompleteBinaryTree(arr, 2 * index + 2);
return root;
}
遍历
二叉树主要有两种遍历方式:
- 深度优先遍历:先往深走,遇到叶子节点再往回走。主要有前序遍历、中序遍历、后序遍历
- 广度优先遍历:一层一层的去遍历。一般有层序遍历
前序遍历
首先了解下:这里的前中后序都是中间根节点的顺序,先遍历根节点就是前序遍历,
前序遍历遍历的顺序就是根节点、左孩子、右孩子
scss
function inOrderTraversal(root) {
if (root === null) {
return;
}
inOrderTraversal(root.left);
console.log(root.value);
inOrderTraversal(root.right);
}
中序遍历
中序遍历就是根节点在中间,左孩子、根节点、右孩子
scss
function inOrderTraversal(root) {
if (root === null) {
return;
}
inOrderTraversal(root.left);
console.log(root.value);
inOrderTraversal(root.right);
}
后序遍历
后续遍历就是根节点在最后,左孩子、右孩子、根节点
scss
function postOrderTraversal(root) {
if (root === null) {
return;
}
inOrderTraversal(root.left);
inOrderTraversal(root.right);
console.log(root.value);
}
练习
144.二叉树的前序遍历
给你二叉树的根节点 root
,返回它节点值的 前序 遍历。
就按照前面描述的,前序就是节点、左孩子、右孩子
scss
var preorderTraversal = function(root) {
let result = [];
function traverse(node) {
if (node === null) return; // 如果节点为 null,直接返回
result.push(node.val); // 访问当前节点
traverse(node.left); // 递归访问左子树
traverse(node.right); // 递归访问右子树
}
traverse(root); // 从根节点开始遍历
return result; // 返回遍历结果
};
145.二叉树的后序遍历
给你一棵二叉树的根节点 root
,返回其节点值的 后序遍历 。
也比较简单,就按照后续遍历顺序,左孩子、右孩子、中节点
scss
var postorderTraversal = function(root) {
let result = []
function traverse(node){
if(node == null) return
traverse(node.left)
traverse(node.right)
result.push(node.val)
}
traverse(root)
return result
};
94.二叉树的中序遍历
有了前面两个,这个也比较简单,一遍过
遍历顺序就是左孩子、中、右孩子
scss
var inorderTraversal = function(root) {
let result = []
function traversal(node){
if(node === null) return
traversal(node.left)
result.push(node.val)
traversal(node.right)
}
traversal(root)
return result
};
set HTTP_PROXY=http://127.0.0.1:7890
set HTTPS_PROXY=http://127.0.0.1:7890
层序遍历
这个层序遍历就是遍历二叉树广度优先的一种方式,
ini
function levelOrder(root) {
if (!root) return [];
const result = [];
const queue = [root];
while (queue.length > 0) {
const levelSize = queue.length;
const currentLevel = [];
for (let i = 0; i < levelSize; i++) {
const node = queue.shift();
currentLevel.push(node.val);
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
result.push(currentLevel);
}
return result;
}
这里就是先定义一个队列,然后开始迭代这个队列,迭代条件就是队列的长度,因为后续会向这个队列中塞入[root],[root.left,root.right]
, 然后挨个从队列中取出节点,将结果保存在currentLevel
中。
开始练习下
练习
102.二叉树的层序遍历
给你二叉树的根节点 root
,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
这个和上面那个方法一样,一遍过
ini
var levelOrder = function(root) {
if(root?.val == null) return []
let result = []
let queen = [root]
while(queen.length >0){
let level = queen.length
let currentVal = []
for(let i=0; i< level; i++){
let node = queen.shift()
currentVal.push(node.val)
node.left && queen.push(node.left)
node.right && queen.push(node.right)
}
result.push(currentVal)
}
return result
};
107.二叉树的层次遍历II
给你二叉树的根节点 root
,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
第一想法就是就正常的层序遍历,然后将结果数组reverse翻转下应该可以的
ini
var levelOrderBottom = function(root) {
if(root?.val == null) return []
let result = []
let queue = [root]
while(queue.length > 0){
let level = queue.length
let currentVal = []
for(let i=0;i<level;i++){
let node = queue.shift()
currentVal.push(node.val)
node.left && queue.push(node.left)
node.right && queue.push(node.right)
}
result.push(currentVal)
}
return result.reverse()
};
也是一遍过
看了题解,发现一种更好的方法,就是深度优先,深度优先遍历都是从底层往上遍历的,很适合这个题目
scss
var levelOrderBottom = function(root) {
const result = [];
function dfs(node, depth) {
if (!node) return; // 递归终止条件
// 如果当前深度未初始化,先创建一个空数组
if (!result[depth]) {
result[depth] = [];
}
result[depth].push(node.val); // 将节点值存入对应深度
// 递归处理左右子树
dfs(node.left, depth + 1);
dfs(node.right, depth + 1);
}
dfs(root, 0); // 从根节点开始,深度为 0
return result.reverse(); // 反转结果数组,实现自底向上
};
看下来也不算严格意义上的深度优先啊,就是记录一个depth,来记录遍历的层级,还是从最上面一层开始遍历的
199.二叉树的右视图
给定一个二叉树的 根节点 root
,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
没写出来,看了题解,评论中有一句话说的好,层序遍历的最后一个节点就是你想要的东西
ini
var rightSideView = function(root) {
if (root?.val == null) return [];
let result = [];
let queue = [root];
while (queue.length > 0) {
let levelSize = queue.length;
let rightMostValue = null; // 存储当前层的最右节点值
for (let i = 0; i < levelSize; i++) {
let node = queue.shift();
rightMostValue = node.val; // 更新为当前层的最后一个节点值
// 先加入左子节点,再加入右子节点,确保右子节点在队列末尾
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
result.push(rightMostValue); // 只加入当前层的最右节点值
}
return result;
};
用递归的思路,可以这样做:
scss
var rightSideView = function(root) {
if (!root) return [];
let result = [];
function dfs(node, level) {
if (!node) return;
// 如果当前层还没有记录节点值,则记录当前节点值
if (result[level] === undefined) {
result[level] = node.val;
}
// 先递归右子树,再递归左子树,确保右子树的节点值覆盖左子树
dfs(node.right, level + 1);
dfs(node.left, level + 1);
}
dfs(root, 0);
return result;
};
637.二叉树的层平均值
给定一个非空二叉树的根节点 root
, 以数组的形式返回每一层节点的平均值。与实际答案相差 10-5
以内的答案可以被接受。
常规的层序遍历,然后取平均值
先用队列写一个
ini
var averageOfLevels = function(root) {
if (!root) return [];
let result = [];
let queue = [root]; // 初始化队列,加入根节点
while (queue.length > 0) {
let levelSize = queue.length;
let levelSum = 0;
for (let i = 0; i < levelSize; i++) {
let node = queue.shift();
levelSum += node.val; // 累加当前层的节点值
// 将子节点加入队列
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
// 计算当前层的平均值并加入结果
result.push(levelSum / levelSize);
}
return result;
};
用递归的方法再写一个
ini
var averageOfLevels = function(root) {
const levelSums = []; // 存储每层的总和
const levelCounts = []; // 存储每层的节点数
// 深度优先搜索递归函数
const dfs = (node, level) => {
if (!node) return;
// 如果当前层还没有初始化,则初始化
if (level >= levelSums.length) {
levelSums.push(0);
levelCounts.push(0);
}
// 更新当前层的总和和节点计数
levelSums[level] += node.val;
levelCounts[level] += 1;
// 递归处理左右子树
dfs(node.left, level + 1);
dfs(node.right, level + 1);
};
// 从根节点开始遍历
dfs(root, 0);
// 计算每层的平均值
const averages = [];
for (let i = 0; i < levelSums.length; i++) {
averages.push(levelSums[i] / levelCounts[i]);
}
return averages;
};
429.N叉树的层序遍历
给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。
树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例)。

有点懵逼啊这个,N叉树如何去取下面层级的值呢,看了力扣的结构定义明白了,通过children
来将树节点关联起来,用队列的方法来试试:
和常规的层序遍历不太一样,常规的层序遍历直接将left``right
scss
var levelOrder = function(root) {
if(!root) return []
let result = []
let queue = [root]
while(queue.length){
let level = queue.length
let current = []
for(let i=0;i<level;i++){
let node = queue.shift()
current.push(node.val)
// queue.push(node.children)
if (node.children) {
for (let child of node.children) {
queue.push(child);
}
}
}
result.push(current)
}
return result
};
用递归的思路写一下
scss
var levelOrder = function(root) {
if(!root) return []
let result = []
function dfs(node,level){
if(node == null) return
if(!result[level]){
result[level] = []
}
result[level].push(node.val)
if(node.children){
node.children.map(item =>{
dfs(item,level + 1)
})
}
}
dfs(root,0)
return result
};
有了上面的迭代案例,递归就好些很多了,一遍过
515.在每个树行中找最大值
给定一棵二叉树的根节点 root
,请找出该二叉树中每一层的最大值。
和429题目差不多,在向result塞值的时候比较下当前值和数组中值,大了就push
队列思路写一下:
ini
var largestValues = function(root) {
if (root == null) return [];
let result = []
let queue = [root]
while(queue.length){
let level = queue.length
let currentMax = -Infinity
for(let i=0;i < level;i++){
let node = queue.shift()
node.val > currentMax && (currentMax = node.val)
// currentMax = Math.max(currentMax, node.val)
node.left && queue.push(node.left)
node.right && queue.push(node.right)
}
result.push(currentMax)
}
return result
};
和之前的思路大差不差,currentMax
保存每层遍历的最大值,然后push到result中
尝试下递归的写法
scss
var largestValues = function(root) {
if (root == null) return [];
let result = []
function dfs(node,level){
if(node == null) return
if (result[level] === undefined) {
result[level] = -Infinity;
}
// if(!result[level]){
// result[level] = -99999999
// }
result[level] = Math.max(result[level],node.val)
node.left && dfs(node.left,level +1)
node.right && dfs(node.right,level +1)
}
dfs(root,0)
return result
};
104.二叉树的最大深度(opens new window)
这个放在层序遍历这里,也是立马就有思路了,前面用递归写层序遍历时,会向递归函数写一个level,最后返回这个level就是最终的结果了
scss
var maxDepth = function(root) {
if(root == null) return 0
let result = []
let saveLevel = 0
function dfs(node,level){
if(node == null) return
if(result[level] == undefined){
result[level] = []
}
saveLevel = Math.max(level,saveLevel)
result[level].push(node.val)
node.left && dfs(node.left,level +1)
node.right && dfs(node.right,level +1)
}
dfs(root,0)
return saveLevel + 1
};
一遍过,总感觉这个题有更简单的做法,试着用队列的方法做一下
队列的方法也是一遍过,毕竟练习了这么多
ini
var maxDepth = function(root) {
if(root == null) return 0
let result = []
let queue = [root]
while(queue.length){
let level = queue.length
let current = []
for(let i=0;i<level;i++){
let node = queue.shift()
current.push(node.val)
node.left && queue.push(node.left)
node.right && queue.push(node.right)
}
result.push(current)
}
return result.length
};
看了题解有个很简单的写法:
ini
var maxDepth = function(root) {
if (root === null) return 0;
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
};
确实牛逼这种写法
111.二叉树的最小深度
有了最大深度的练习,应该很好写,发现我想多了,写不出来,问了ai才写出来,大概思路就是层序遍历,遇到第一个叶子节点就直接返回。
ini
var minDepth = function(root) {
if (root == null) return 0;
let queue = [[root, 1]];
while (queue.length > 0) {
let [node, depth] = queue.shift();
// 检查当前节点是否为叶子节点
if (node.left === null && node.right === null) {
return depth;
}
// 将子节点加入队列
if (node.left !== null) {
queue.push([node.left, depth + 1]);
}
if (node.right !== null) {
queue.push([node.right, depth + 1]);
}
}
return 0; // 如果没有叶子节点(理论上不会发生)
};