1.什么是树?
- 树是一种分层数据的抽象模型
- 前端常见的树包括:DOM树,级联选择,树形控件
2.二叉树常用操作
- 先序遍历
- 中序遍历
- 后序遍历
- 深度优先遍历
- 广度优先遍历
3.代码示例
定义一个二叉树文件
const bt = {
val: 1,
left: {
val: 2,
left: {
val: 4,
left: null,
right: null
},
right: {
val: 5,
left: null,
right: null
}
},
right: {
val: 3,
left: {
val: 6,
left: null,
right: null,
},
right: {
val: 7,
left: null,
right: null
}
}
}
module.exports = bt;
1.先序遍历
//递归版
const preorder = (root) => {
if(!root) {return;}
console.log(root.val);
preorder(root.left);
preorder(root.right);
}
//非递归版
const preorder = (root) => {
if(!root) {return;}
//先把根节点压入栈
const stack = [root];
while(stack.length) {
const n = stack.pop();
//先序遍历先访问根节点
console.log(n.val);
//然后依次访问左和右
//因为栈是先进后出,所以先把右节点压如,这样才能先访问到左节点
if(n.right) stack.push(n.right);
if(n.left) stack.push(n.left);
}
// 具体思路就是弹出根节点,压入右节点,压入左节点,反复循环
}
2.中序遍历
//递归版
const inorder = (root) => {
if(!root) {return;}
inorder(root.left);
console.log(root.val);
inorder(root.right);
}
//非递归版
const inorder = (root) => {
if(!root) {return;}
const stack = [];
//定义一个指针指向根节点
let p = root;
while(stack.length || p) {
// 先把根节点和左节点依次入栈
while(p) {
stack.push(p);
p = p.left;
}
// 弹出栈顶元素,也就是最尽头左边节点
const n = stack.pop();
console.log(n.val);
// 指针指向最尽头 左节点的右节点,如果有右节点就压入栈中(因为此时最尽头的左节点是他的右节点的中间节点)
// 此时对右节点来说又是一次全新先把所有左节点入栈过程
p = n.right;
// 具体思路就是把根节点入栈,再把所有左节点的全部入栈,把右节点当做一次全新的根节点
}
}
3.后序遍历
//递归版
const postorder = (root) => {
if(!root) {return;}
postorder(root.left);
postorder(root.right);
console.log(root.val);
}
//非递归版
const postorder = (root) => {
if(!root) {return;}
const outputStack = [];
const stack = [root];
while(stack.length) {
const n = stack.pop();
outputStack.push(n);
if(n.left) stack.push(n.left);
if(n.right) stack.push(n.right);
}
while(outputStack.length) {
const n = outputStack.pop();
console.log(n.val);
}
// 思路,要实现左右中,我们可以把倒过来变成中右左,就变成了一个和先序遍历很像的遍历
// 把先序遍历的访问操作改变为入栈操作,利用栈的先进后出,再把子节点全部逆序出来访问,就实现了后续遍历
}
5.深度优先遍历
//定义一个树
const tree = {
val: 'a',
children: [
{
val: 'b',
children: [
{
val: 'd',
children: []
},
{
val: 'e',
children: []
}
]
},
{
val: 'c',
children: [
{
val: 'f',
children: []
},
{
val: 'g',
children: []
}
]
}
]
}
//深度优先遍历
const dfs = (root) => {
console.log(root.val);
root.children.forEach(dfs)
}
dfs(tree);
6.广度优先遍历
const tree = {
val: 'a',
children: [
{
val: 'b',
children: [
{
val: 'd',
children: []
},
{
val: 'e',
children: []
}
]
},
{
val: 'c',
children: [
{
val: 'f',
children: []
},
{
val: 'g',
children: []
}
]
}
]
}
//广度优先遍历
const bfs = (root) => {
const q = [root];
while(q.length > 0) {
const n = q.shift();
console.log(n.val);
n.children.forEach(child => {
q.push(child);
})
}
}
bfs(tree);
4.LeetCode
接下来使用树这个数据结构来刷LeetCode有关树的题目,巩固提升对树的了解。
题号104.二叉树的最大深度(简单)
题目要求:
解题思路:
使用深度优先遍历,并记录每个节点所在层级,定义一个新的变量来记录根节点层级,当遇到新的根节点时,刷新这个变量为刷大值
编写代码:
var maxDepth = function(root) {
let res = 0;
//定义深度优先遍历
const dfs = (n,l) => {
if(!n) return;
//遇到叶子节点时刷新res
if(!n.left && !n.right) {
res = Math.max(res,l);
}
dfs(n.left,l+1);
dfs(n.right,l+1);
}
dfs(root,1);
return res;
};
复杂度分析
- 时间复杂度:O(n) n是树的结点数
- 空间复杂度:O(n) n是树的结点数
题号111.二叉树的最小深度(简单)
题目要求:
解题思路:
使用广度优先遍历,并记录深度,当找到第一个子节点时,就可以返回最小深度了
编写代码:
var minDepth = function(root) {
if (!root) { return 0; }
const q = [[root,1]];
while (q.length) {
const [n,l] = q.shift();
if( !n.left && !n.right) {
return l;
}
if(n.left) q.push([n.left, l + 1]);
if(n.right) q.push([n.right, l + 1]);
}
};
复杂度分析
- 时间复杂度:O(n)
- 空间复杂度:O(n)
题号102.二叉树的层序遍历(中等)
题目要求:
解题思路:
广度遍历,按层加入队列中,确保每层的元素都进入自己对应的数组后,再新建一个数组放下一层的元素。
编写代码:
var levelOrder = function(root) {
if(!root) return [];
const res = [];
const q = [root];
//当队列非空时,将队列压入返回值中
while(q.length) {
let len = q.length;
res.push([]);
//确保取到队列中同一层元素
while(len--) {
const n = q.shift();
res[res.length-1].push(n.val);
if(n.left) q.push(n.left);
if(n.right) q.push(n.right);
}
}
return res;
};
复杂度分析
- 时间复杂度:O(n) n是结点数
- 空间复杂度:O(n) n是结点数