1.树是什么
- 一种
分层
数据的抽象模型 - 前端工作中常见的树包括:DOM树,级联选择(省市区),树形控件,…
- javascript中没有树,但是可以用Object和Array构建树
4.树的常用操作:深度/广度优先遍历,先中后序遍历
深度 / 广度遍历
深度优先遍历:尽可能深的搜索树的分支。如下图深度访问顺序:
广度优先遍历:先访问离跟节点最近的节点。
标题,目录,深入看每个目录下的小节。
深度优先遍历算法口诀:(其实就是一个递归)
1.访问根节点。
2.对根节点的children挨个进行深度优先遍历。
只有2步,写代码也只有2行代码,但是这2行代码实现了深度优先递归,在遍历的过程中被反复调用很多次。
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 = function (tree) {
console.log(tree.val)
// root.children.forEach((child) => dfs(child))
root.children.forEach(dfs)
}
广度优先遍历算法口诀(对列)
1.新建一个队列,把根节点入队
2.把队头出队并访问
3.把队头的children挨个入队
4.重复第2,3步,知道队列为空。
const root = {
val: 'a',
children: [
{
val: 'b',
children: [
{
val: 'd',
children: []
},
{
val: 'e',
children: []
}
]
},
{
val: 'c',
children: [
{
val: 'f',
children: []
},
{
val: 'g',
children: []
}
]
}
]
}
const bfs = function (root) {
const q = [root]
while(q.length > 0) {
const n = q.shift()
console.log(n.val)
if (n.children) {
n.children.forEach(child => {
q.push(child)
})
}
}
}
二叉树的先,中, 后序的三种遍历(递归)
1.二叉树:树中每个树的节点最多有2个节点
2.在js中通常用Object来模拟二叉树
先序遍历:(根,左,右)
1.访问根节点
2.对结节点的左子树进行先序遍历
3.对根节点的右子树进行先序遍历
const bt = {
val: 1,
left: {
val: 2,
left: {
val: 4,
left: {},
right: {}
},
right: {
val: 5,
left: {},
right: {}
}
},
right: {
val: 3,
left: {
val: 6,
left: {},
right: {}
},
right: {
val: 7,
left: {},
right: {}
}
}
}
const preorder = function (root) {
// 保证程序的健壮性
if (!root) return
// 访问根节点
console.log(root.val)
// 访问左子树
preorder(root.left)
// 访问右子树
preorder(root.right)
}
// 输出结果:1,2, 4, 5,3, 6, 7
中序遍历(左根右)
- 对根节点的左子树进行遍历
- 访问根节点
- 对根节点的右子树进行遍历
const bt = {
val: 1,
left: {
val: 2,
left: {
val: 4,
left: {},
right: {}
},
right: {
val: 5,
left: {},
right: {}
}
},
right: {
val: 3,
left: {
val: 6,
left: {},
right: {}
},
right: {
val: 7,
left: {},
right: {}
}
}
}
const inorder = function (root) {
if (!root) return
// 访问左子树
preorder(root.left)
// 访问根节点
console.log(root.val)
// 访问右子树
preorder(root.right)
}
// 4 2 5 1 6 3 7
后序遍历 (左右根)
- 对根节点的左子树进行遍历
- 对根节点的右子树进行遍历
- 访问根节点
const bt = {
val: 1,
left: {
val: 2,
left: {
val: 4,
left: {},
right: {}
},
right: {
val: 5,
left: {},
right: {}
}
},
right: {
val: 3,
left: {
val: 6,
left: {},
right: {}
},
right: {
val: 7,
left: {},
right: {}
}
}
}
const postorder = function (root) {
if (!root) return
// 访问左子树
preorder(root.left)
// 访问右子树
preorder(root.right)
// 访问根节点
console.log(root.val)
}
// 4 5 2 6 7 3 1
二叉树的先,中, 后序的三种遍历(非递归)
面试时一般要求写非递归版本的先中后序遍历。
我们可以用栈模拟非递归的过程。
先序遍历
const preorder = function (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)
}
}
中序遍历
- 同样还是用栈
- 对于中序遍历第一步就是把所有的左子树丢到栈里面,那么这怎么做呢,我们需要用到一个指针§,刚开始指针的值是root,每遍历一个就把它放到栈里面,然后移动我们的指针,
- 需要把栈定的元素弹出,访问,访问完之后,我们要访问它的右节点,我们直接把指针指向右节点就行了,那么我们还要在做一次循环, while (stack.length || p) {},
const inorder = function (root) {
if (!root) return
// 创建栈,用来模拟函数调用堆栈
const stack = []
// 指针(对于中序遍历来说,刚开始堆栈就堆了很多了,
// 因为中序遍历刚开始就又调用了一个中序遍历,并把他的左子树传递进去了,如果还有左子树就不断的累加对栈)
let p = root
while (stack.length || p) {
// 1.对于中序遍历第一步就是把所有的左子树丢到栈里面,那么这怎么做呢,我们需要用到一个指针(p),刚开始指针的值是root,每遍历一个就把它放到栈里面,然后移动我们的指针,
while (p) {
stack.push(p)
p = p.left
}
// 2.需要把栈定的元素弹出,访问,访问完之后,我们要访问它的右节点,我们直接把指针指向右节点就行了
const n = stack.pop()
console.log(n.val)
p = n.right
}
}
后序遍历
1.把后序遍历的顺序给倒置一下,改为:根 右 左,利用先序遍历的算法逻辑逆序的访问。
2.然后利用栈的(先进后出)特性,把刚才先序遍历的顺序倒过来,重新访问一下就好了。
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);
}
};
leetCode 104 二叉树的最大深度
给一个二叉树,找出其最大的深度。
二叉树的深度:根节点到叶子结点的最长路径上的节点数。
求最大深度,考虑使用深度优先遍历。
var maxDepth = function (root) {
let res = 0
// 深度优先编辑
const dfs = (n, l) => {
if (!n) return
// 判断是否为叶子结点
if (!n.left && !n.right) {
res = Math.max(res, l)
}
console.log(n.val, l)
dfs(n.left, l + 1)
dfs(n.right, l + 1)
}
// l : 层级 ,初始化是1
dfs(root, l = 1)
return res
}
leetCode.111二叉树的最小深度
最小深度,广度优先遍历。
给定一个二叉树,找出最小深度。
最小深度:从根节点到最近叶子节点的最短路径上的节点数量。
叶子节点:没有子节点的节点。
const minDepth = (root) => {
if (!root) return
const q = [[root, 1]]
while (q.length) {
const [n, l] = q.shift()
// 输出当前值,和层级
console.log(n.val, l)
if (n.left) q.push([n.left, l+ 1])
if (n.right) q.push([n.right, l + 1])
}
}
const minDepth = (root) => {
if (!root) return
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])
}
}
leetCode. 102.二叉树的层序遍历
给你一个二叉树,请你返回按【层序遍历】得到的的节点值,(即逐层的从左到右访问所有节点)。
广度优先遍历。
var bfs = (root) => {
if (!root) return
const res = []
const q = [[root, 0]]
while (q.length) {
const [n, level] = q.shift()
// console.log([n.val, level])
if (!res[level]) {
res.push([n.val])
} else {
res[level].push(n.val)
}
if (n.left) q.push([n.left, level + 1])
if (n.right) q.push([n.right, level + 1])
}
return res
}
console.log(bfs(bt))
leetCode 94.二叉树的中序遍历
给定一个二叉树,返回中序遍历
递归算法很简单,你可以使用迭代完成么?
// 递归
const inorder = function (root) {
const res = []
const rec = (n) => {
if (!n) return
rec(n.left)
res.push(n.val)
rec(n.right)
}
rec(root)
return res
}
迭代
const inorder = function (root) {
const res = []
const stack = []
let p = root
while(stack.length || p){
while(p) {
stack.push(p)
p = p.left
}
const n = stack.pop()
res.push(n.val)
p = p.right
}
}
leetCode 112.路径总和
给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值都相加等于目标和。
var fasPathSum = function (root, num) {
if (!root) return false
let res = false
const dfs = (n, s) => {
if (!n.left && !n.right && s == num) {
console.log('1', s, )
res = true
}
if (n.left) dfs(n.left, s + n.left.val)
if (n.right) dfs(n.right, s + n.right.val)
}
dfs(root, root.val)
return res
}
console.log(fasPathSum(bt, 7))
遍历JSON的所有节点值
const json = {
a: { b: { c: 1 } },
d: [1, 2],
};
const dfs = (n, path) => {
// 访问当前节点
console.log(n, path);
// Object.keys(n) 获取所有的key
Object.keys(n).forEach(k => {
dfs(n[k], path.concat(k));
});
};
dfs(json, []);
渲染antd中的树组件 (深度优先遍历)
const json = [
{ title: 'titl1', key: 'key1', children: [] },
{ title: 'titl2', key: 'key2', children: [] },
]
class Demo extends. React.Component{
dfs = (n) => {
return <TreeNode title={n.title} key={n.key}>
{n.children.map(this.dfs)}
</TreeNode>
}
render() {
return <Tree>{json.map(this.dfs)}</Tree>
}
}