@TOC
树是什么?
- 一种分层数据的抽象模型。
- 前端工作中常见的树包括:DOM树、级联选择、树形控件......
- JS中没有树,但是可以用Object和Array构建树
-
树的常用操作:深度/广度优先遍历、先中后序遍历
什么是深度/广度优先遍历?
-
深度优先遍历,尽可能深的搜索树的分支
-
广度优先遍历:先访问离根节点最近的节点
深度优先遍历算法口诀
-
访问根节点
-
对根节点的children挨个进行深度优先遍历
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);
广度优先遍历算法口诀
-
新建一个队列,把根节点入队。
-
把队头出队并访问
-
把队头的children挨个入队
-
重复第二、三步,直到队列为空
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);
二叉树的先中后序遍历
二叉树是什么
- 树中每个节点最多只能有两个子节点
- 在JS中通常用Object来模拟二叉树
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;
先序遍历算法口诀(根 > 左 > 右)
-
访问根节点
-
对根节点的左子树进行先序遍历
-
对根节点的右子树进行先序遍历
const bt = require('./bt');
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);
// }
// };
preorder(bt);
中序遍历算法口诀(左 > 根 > 右)
- 对根节点的左子树进行中序遍历
- 访问根节点
- 对根节点的右子树进行中序遍历
const bt = require('./bt');
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;
// }
// };
inorder(bt);
后序遍历算法口诀(左 > 右 > 根)
- 对根节点的左子树进行后序遍历
- 对根节点的右子树进行后序遍历
- 访问根节点
const bt = require('./bt');
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);
// }
// };
postorder(bt);
LeetCode:104.二叉树的最大深度
使用深度优先遍历解决
解题思路
求最大深度,考虑使用深度优先遍历
在深度优先遍历过程中,记录每个节点所在的层级,找出最大的层级即可
解题步骤
新建一个变量,记录最大深度
深度优先遍历整棵树,并记录每个节点的层级,同时不断刷新最大深度这个变量
遍历结束返回最大深度这个变量
时间复杂度O(n),n是整棵树的节点数
有函数调用堆栈,函数没有执行完变量不能释放,所以空间复杂度为O(最大深度),好的情况是每个都有两个叉,空间复杂度为O(log(n)),最坏的情况是节点数等于最大深度,只在一个叉上不断延续的二叉树,空间复杂度为O(n)
LeetCode:111.二叉树的最小深度
使用广度优先遍历解决
解题思路 求最小深度,考虑使用广度优先遍历 在广度优先遍历过程中,遇到叶子节点,停止遍历,返回节点层级 解题步骤 广度优先遍历整棵树,并记录每个节点的层级 遇到叶子节点,返回节点层级,停止遍历
时间复杂度O(n),n是整棵树的节点数 空间复杂度为O(n)
LeetCode:102.二叉树的层序遍历
解题思路
层序遍历顺序就是广度优先遍历
不过在遍历时候需要记录当前节点所处的层级,方便将其添加到不同的数组中
解题步骤
广度优先遍历二叉树
遍历过程中,记录每个节点的层级,并将其添加到不同的数组中
- 法一
- 法二
时间复杂度O(n),空间复杂度为O(n)
LeetCode:94.二叉树的中序遍历
递归版
非递归版
时间复杂度O(n),空间复杂度为O(n)
LeetCode:112.路径总和
解题思路
在深度优先遍历的过程中,记录当前路径的节点值的和
在叶子节点处,判断当前路径的节点值的和是否等于目标值
解题步骤
深度优先遍历二叉树,在叶子节点处,判断当前路径的节点值的和是否等于目标值,是就返回true
遍历结束,如果没有匹配,就返回false
时间复杂度O(n),n为树的节点数,空间复杂度为O(树的高度),最坏为O(n),最好为O(log(n))
前端与树:遍历JSON的所有节点值
const json = {
a: { b: { c: 1 } },
d: [1, 2],
};
const dfs = (n, path) => {
console.log(n, path);
Object.keys(n).forEach(k => {
dfs(n[k], path.concat(k));
});
};
dfs(json, []);
前端与树:渲染Antd中的树组件
技术要点
树是一种分层数据的抽象模型,在前端广泛应用 树的常用操作:深度/广度优先遍历、先中后序遍历......
思考题
1、用React或Vue编写一个省市区级联选择 2、用React或Vue编写一个树插件,要求可以将json渲染到页面上即可