有关树遍历的javascript实现
前言
二月的第一天,总结一下近段时间刷的有关树遍历的leetcode算法题,希望写完这篇文章的我和看完文章的你都有些收获吧。全篇主要讲的是有关树的遍历,使用前端javascript语言实现。当然有关树的操作还有很多需要的深入理解和总结的。今天就暂时先把树遍历理解全吧。🧐🧐
开干,先放导图:
1、树和二叉树
1.1、 树
1.1.1 树的基本概念
树是一种数据结构,它是由n(n>=1)个有限结点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:
每个结点有零个或多个子结点;没有父结点的结点称为根结点;每一个非根结点有且只有一个父结点;除了根结点外,每个子结点可以分为多个不相交的子树。
在javascript中并没有树,但是我们可以Object 和 Array构建树,比如构建上图的树:
const tree = {
val:'a',
children:[
{
val:'b',
children:[
{
val:'d',
children:[],
},
{
val:'e',
children:[],
}
]
},
{
val:'c',
children:[
{
val:'f',
children:[],
},
{
val:'g',
children:[],
}
]
}
]
}
树的常用操作: 深度/广度优先遍历,先中后序遍历(二叉树)
1.1.2 树的深度优先遍历(DFS)
尽可能深的搜索树的分支
1、访问根节点
2、对根节点的children挨个进行深度优先遍历
👉 (递归)
- 代码实现
(这里操作的树就是我们上面用javascript代码创建的树)
const dfs = (root) => {
console.log(root.val)
root.children.forEach(dfs)
// root.children.forEach((child) => {dfs(child)})
// 遍历节点的每个孩子节点,并且在孩子节点上使用dfs递归 继续遍历
}
dfs(tree);
操作台结果
1.1.3 树的广度优先遍历(BFS)
先访问离节点最近的节点
先遍历兄弟节点,在遍历子节点。
采用队列实现,出队时添加子节点。
-
操作思想
1、新建一个队列,把根节点入队
2、把对头出队,并访问
3、把对头的children挨个入队
4、重复第二,三步,直到队列为空
👉(队列)
-
代码操作
(这里操作的树就是我们上面用javascript代码创建的树)
const bfs = (root) => {
//1、新建一个队列,把根节点入队
const q = [root]
//4、重复第2,3步,直到队列为空
while (q.length > 0) {
//2、把对头出队,并访问
const n = q.shift();
console.log(n.val);
// 3、把对头的children挨个入队
n.children.forEach(child => {
q.push(child);
});
}
}
bfs(tree);
操作台打印的结果:
1.2、二叉树
1.2.1 二叉树的基本概念
二叉树是一个每个结点最多只能有两个分支的树,左边的分支称之为左子树,右边的分支称之为右子树。
用javascript创建一棵二叉树
代码如下:
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
},
},
};
1.2.2 二叉树的先序遍历
递归版
1、访问根节点
2、对根节点的左子树进行先序遍历
3、对根节点的右子树进行先序遍历
✍️ 1-2-4-5-3-6-7
代码:
const preorder = (root) => {
if(!root) { return }
console.log(root.val);
preorder(root.left);
preorder(root.right)
};
结果:
非递归版
用堆栈来模拟先序遍历(根,左,右)递归
的过程
用栈这个数据结构
1、新建一个stack代表函数的调用堆栈,刚开始堆栈里面存放root根节点
2、访问根节点的值,用pop()方法弹出
3、先把right右节点推到栈中,再把left左节点推到栈中(因为栈是后进先出),我们需要得到的是先访问左节点再访问右节点
4、当stack中有值的时候,一直循环2,3操作
代码:
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);
}
}
1.2.3 二叉树的中序遍历
递归版
1、对根节点的左子树进行中序遍历
2、访问根节点
3、对根节点的右子树进行中序遍历
✍️ 4-2-5-1-6-3-7
代码:
const inorder = (root) => {
if(!root){return}
inorder(root.left);
console.log(root.val);
inorder(root.right);
}
非递归版
用堆栈来模拟中序遍历(左,根,右)递归
的过程
1、新建一个stack代表函数的调用堆栈
2、对于中序遍历来说,最开始的时候就是递归root.left,因此我们用栈模拟的时候,第一步就是要把所有的左子树都丢到栈中,使用指针p,最开始为root
3、当p有值的情况下,让p= p.left,在遍历的同时,把它推到栈中
4、把最近左节点弹出来,访问它
5、访问右节点,将指针指向右节点p = n.right;
6、再做一次循环while (stack.length || p){}
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;
}
}
1.2.4 二叉树的后序遍历
递归版
1、对根节点的左子树进行后序遍历
2、对根节点的右子树进行后序遍历
3、访问根节点
✍️ 4-5-2-6-7-3-1
代码:
const postorder = (root) => {
if(!root) { return }
postorder(root.left)
postorder(root.right);
console.log(root.val);
}
非递归版
用堆栈来模拟后序遍历(左,右,根)递归
的过程
我们知道,先序遍历是(根左右)
因此,我们可以先做先序遍历,再使用入栈操作,再利用栈的后进先出,(这里要注意哦,我们需要把先序遍历变成根右左,因此我们还需要改动一下)
1、创建一个栈stack,代表函数的调用堆栈
2、创建一个栈outputStack 实现倒置的栈
调用先序遍历的方法
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);
}
3、将先序遍历的 根右左
变成根左右
if(n.left) stack.push(n.left);
if(n.right) stack.push(n.right);
4、将节点的左右子节点都压入栈
5、用pop()把存放在栈outputStack的节点都弹出来
代码:
const postorder = (root) => {
if(!root) {return}
const stack = [root];
const outputStack = [];
while(stack.length){
const n = stack.pop();
//console.log(n.val);//访问这个节点
outputStack.push(n); //把先序遍历访问这个节点的值,变成把节点push推入栈中
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);
}
}
2、 leetcode算法题
94、 二叉树的中序遍历
- 递归版
/**
* @param {TreeNode} root
* @return {number[]}
*/
var inorderTraversal = function(root) {
const res = []
const rec = (n) => {
if(!n) {return}
rec(n.left);
res.push(n.val)
rec(n.right)
}
rec(root);
return res
};
- 非递归(栈)
/**
* @param {TreeNode} root
* @return {number[]}
*/
var inorderTraversal = function(root) {
const res = [];
//模拟堆栈
const stack = [];
let p = root;//指针
while(stack.length || p) {
while(p) {
//先把左子树全部丢在栈里面去
stack.push(p);
p = p.left;//将指针指向left左节点,并把它推到栈中
}
//把最近左节点弹出来(栈顶的节点元素)
const n = stack.pop();
res.push(n.val);
//访问右节点
p = n.right
}
return res
};
102、 二叉树的层序遍历
/**
* @param {TreeNode} root
* @return {number[][]}
*/
var levelOrder = function(root) {
if (!root) {return [];}
const q = [[root,0]];
const res = [];//存取结构
while(q.length) {
const [n,l] = q.shift();
// console.log(n.val,l);
//当存取结果的数组res没有东西的时候,也就是为空[],把节点的值push到里面
//其他的值就根据层级放
if(!res[l]){
res.push([n.val]);
} else{
res[l].push(n.val)
}
if(n.left) q.push([n.left,l + 1]);
if(n.right) q.push([n.right,l + 1]);
//如果当前节点存在左右节点,那么遍历这两个节点时的层级要加一,
//因为他们在当前节点的下一级
}
return res;
};
104、二叉树的最大深度
题目:二叉树的最大深度
🥭深度优先
/**
* @param {TreeNode} root
* @return {number}
*/
var maxDepth = function (root) {
let res = 0;
//l 表示层
const dfs = (n,l) => {
if(!n) {return;}
if(!n.left && !n.right) {
res = Math.max(res,l);
}
console.log(n,l);
dfs(n.left,l+1);
dfs(n.right,l+1);
};
dfs(root,1);
return res
}
111、 二叉树的最小深度
🥭 广度优先
题目:二叉树的最小深度
/**
* @param {TreeNode} root
* @return {number}
*/
//二叉树最小深度 ---广度优先
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]);
}
};
112、路径总和
/**
* @param {TreeNode} root
* @param {number} targetSum
* @return {boolean}
*/
var hasPathSum = function(root, targetSum) {
if (!root) return false;
let res = false;//标志,判断是否满足targetSum
const dfs = (n,s) => {
if (!n.left && !n.right && s === targetSum) {
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;
};
3、前端有关
遍历JSON的所有值
Object.keys()
Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组
//深度优先遍历
const json = {
a:{
b:{
c:1
}
},
d:[1,2]
}
const dfs = (n) => {
console.log(n); //{ a: { b: { c: 1 } }, d: [ 1, 2 ] }
console.log(Object.keys(n)) //[ 'a', 'd' ]
Object.keys(n).forEach(k => {
dfs(n[k])
})
}
dfs(json)
结果:
{ b: { c: 1 } }
[ 'b' ]
{ c: 1 }
[ 'c' ]
1
[]
[ 1, 2 ]
[ '0', '1' ]
1
[]
2
[]
总结
好啦,今天的有关树的遍历就说到这里。就当是总结,leetcode的题目也没有说的那么详细,可以参考一下前面的树的深度/广度优先遍历,和二叉树的先中后序遍历。
😁 二月,祝你好运!
😳 卑微码字人,求赞
💌 炒鸡感谢
🙈 笔芯