一文了解树在前端中的应用,掌握数据结构中树的生命线_先序遍历在实际生活中的应用(5)

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化资料的朋友,可以戳这里获取

1、深度优先遍历

(1)定义
  • 深度优先遍历,即尽可能深的搜索树的分支。
(2)口诀
  • 访问根节点。
  • 对根节点的 children 挨个进行深度优先遍历。
(3)代码实现

接下来用 JS 来实现树的深度优先遍历。具体代码如下:

const tree = {
    val:'a',
    children:[
        {
            val:'b',
            children:[
                {
                    val:'d',
                    children:[]
                },
                {
                    val:'e',
                    children:[]
                }
            ]
        },
        {
            val:'c',
            children:[
                {
                    val:'f',
                    children:[]
                },
                {
                    val:'a',
                    children:[]
                }
            ]
        }
    ]
}

const dfs = (root) => {
    console.log(root.val);
    // 使用递归
    root.children.forEach(dfs);
}

/\*
打印结果:
a
b
d
e
c
f
a
\*/

通过以上代码我们可以知道,首先我们先定义一棵树 tree ,之后使用递归的方法,对树的 Children 挨个进行遍历,最终得到 abdecfa 的打印结果。

这个顺序怎么理解会更为容易一点呢?

在上面的知识点我们谈到,树是往深了遍历。那在我们这道题的 tree 树当中,我们总得先对第一层的遍历完,才能遍历第二层的。而第一层的内容又有很多层,那就先把它往深了遍历,等到第一层的深度遍历结束后,我们才开始遍历第二层的。

所以,我们先在来看一下,最上面的是 a ,接着就是第一层,第一层有 bde ,接下来是第二层,第二层就有 cfa 。因此,最终的顺序为 abdecfa

2、广度优先遍历

(1)定义
  • 广度优先遍历,即先访问根节点最近的节点
(2)口诀
  • 新建一个队列。
  • 把队头出队并访问。
  • 把队头的 children 挨个入队。
  • 重复第二步和第三步,直到队列为空。
(3)代码实现

接下来用 JS 来实现树的广度优先遍历。具体代码如下:

const tree = {
    val:'a',
    children:[
        {
            val:'b',
            children:[
                {
                    val:'d',
                    children:[]
                },
                {
                    val:'e',
                    children:[]
                }
            ]
        },
        {
            val:'c',
            children:[
                {
                    val:'f',
                    children:[]
                },
                {
                    val:'a',
                    children:[]
                }
            ]
        }
    ]
}

const bfs = (root) => {
    // 新建一个队列,并把根节点先放到队列里面
    const q = [root];
    while(q.length > 0){
        // 不断进行出队,访问
        const n = q.shift();
        // 边出队边访问
        console.log(n.val);
        // 把队头的children挨个入队,退到队列里面
        n.children.forEach(child => {
            q.push(child);
        });
    }
}

bfs(tree);

/\*
打印结果:
a
b
c
d
e
f
a
\*/

🌱三、二叉树

1、定义

  • 对于二叉树来说,树中的每个节点最多只能有两个子节点
  • JS 中没有二叉树,但通常用对象 Object 模拟二叉树。

2、二叉树的先/中/后序遍历

(1)先序遍历
  • 访问根节点。
  • 对根节点的左子树进行先序遍历。
  • 对根节点的右子树进行先序遍历。
(2)中序遍历
  • 对根节点的左子树进行中序遍历。
  • 访问根节点。
  • 对根节点的右子树进行中序遍历。
(3)后序遍历
  • 对根节点的左子树进行后序遍历。
  • 对根节点的右子树进行后序遍历。
  • 访问根节点。

3、JS实现先中后序三种遍历

下面我们用代码来实现二叉树的这三种遍历。接下来开始讲解~

(1)JS实现二叉树的先序遍历

对于二叉树的先序遍历来说,是先访问根节点,之后再访问左子树,最后访问右子树。下面我们用两种方式来实现先序遍历,第一种是递归版本,第二种是非递归版本

先定义一棵树:

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
        }
    }
}

递归版本实现:

// 递归版本实现
const preOrder1 = (root) => {
    if(!root){
        return;
    }
    console.log(root.val);
    preOrder1(root.left);
    preOrder1(root.right);
}

preOrder1(bt);
/\*\*打印结果:
1
2
4
5
3
6
7
\*/

非递归版本实现:

// 非递归版实现
/\*\*
 \* 思路:
 \* 1.新建一个栈模拟函数的调用堆栈;
 \* 2.对于先序遍历来说,需要先把根节点取出,然后再遍历左子树了右子树;
 \* 3.按照栈的先进后出特点,先把右子树放进栈里,再把左子树放进栈里,一一取出。
 \*/
const preOrder2 = (root) => {
    if(!root){
        return;
    }
    // 新建一个stack代表函数的调用堆栈
    const stack = [root];
    // console.log(stack)
    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);
        }

    }
}

preOrder2(bt);
/\*\*打印结果:
1
2
4
5
3
6
7
\*/

(2)JS实现二叉树的中序遍历

对于二叉树的中序遍历来说,是先访问子树,之后访问节点,最后再访问子树。下面我们用两种方式来实现中序遍历,第一种是递归版本,第二种是非递归版本

同样地,我们先来先定义一棵树:

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
        }
    }
}

递归版本实现:

// 递归版本实现
const inOrder1 = (root) => {
    if(!root){
        return;
    }
    inOrder(root.left);
    console.log(root.val);
    inOrder(root.right);
}

inOrder1(bt);
/\*\*打印结果:
4
2
5
1
6
3
7
\*/

非递归版本实现:

// 非递归版实现
/\*\*
 \* 思路:
 \* 1.新建一个栈模拟函数的调用堆栈;
 \* 2.对于中序遍历来说,需要先把左子树全部丢到栈里面;那么需要每当遍历一个,就推到栈里面
 \* 3.遍历完成之后,把最尽头的结点弹出,并访问它;此处最尽头的结点即尽头出的根节点,左根右
 \* 4.访问完左结点后,需要访问右结点;
 \*/
const inOrder2 = (root) => {
    if(!root){
        return;
    }
    let p = root;
    const stack = [];
    while(p || stack.length){
        while(p){
            // 先进栈
            stack.push(p);
            // 进栈完继续指向左子树
            p = p.left;
        }
        // 弹出最后一个
        const n = stack.pop();
        console.log(n.val);
        p = n.right;
    }
    
}

inOrder2(bt);
/\*\*打印结果:
4
2
5
1
6
3
7
\*/

(3)JS实现二叉树的后序遍历

对于二叉树的后序遍历来说,是先访问子树,之后访问子树,最后再访问节点。下面我们用两种方式来实现后序遍历,第一种是递归版本,第二种是非递归版本

首先同样地,先来定义一棵树:

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
        }
    }
}

递归版本实现:

// 递归版本实现
const postOrder1 = (root) => {
    if(!root){
        return;
    }
    postOrder1(root.left);
    postOrder1(root.right);
    console.log(root.val);
}

preOrder1(bt);
/\*\*打印结果:
1
2
4
5
3
6
7
\*/

非递归版本实现:

// 非递归版实现
/\*\*
 \* 思路:
 \* 1.建立一个空栈stack;
 \* 2.分别把左子树,右子树分别放入stack栈
 \* 3.建立一个倒序栈outputStack,先把根树放进,再一一放入右子树,右子树全部放完之后再放左子树
 \*/
const postOrder2 = (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);
    }
}
preOrder2(bt);
/\*\*打印结果:
1
2
4
5
3
6
7
\*/

(4)总结

看完上面的代码实现后,我们来做个总结。为什么这里要展示递归版本和非递归版本呢?

事实上,在我们的日常开发中,递归遍历是非常常见的。但试想一下,有时候我们的业务逻辑有可能很复杂,那这个时候前端从后端接收到的数据量是比较大的。这个时候如果用递归版本来处理的话,算法复杂度相对来说就会比较高了。

所以我们多了一种非递归版本的实现方式,非递归版本的实现方式,旨在以空间来换时间,减少代码的时间复杂度。

☘️四、leetcode经典题目剖析

接下来我们引用几道经典的 leetcode 算法,来巩固树和二叉树的知识。

1、leetcode104二叉树的最大深度(简单)

(1)题意

附上题目链接:leetcode104二叉树的最大深度

给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

输入输出示例:

  • 输入: 给定二叉树 [3,9,20,null,null,15,7]
  • 输出: 3

(2)解题思路

  • 求最大深度,考虑使用深度优先遍历
  • 在深度优先遍历过程中,记录每个节点所在的层级,找出最大的层级即可。

(3)解题步骤

  • 新建一个变量,记录最大深度。
  • 深度优先遍历整棵树,并记录每个节点的层级,同时不断刷新最大深度的这个变量。
  • 遍历结束返回最大深度的变量。

(4)代码实现

/\*\*
 \* @param {TreeNode} root
 \* @return {number}
 \*/
let maxDepth = function(root) {
    let res = 0;
    const dfs = (n, l) => {
        if(!n){
            return;
        }
        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;
}

2、leetcode111二叉树的最小深度(简单)

(1)题意

附上题目链接:leetcode111二叉树的最小深度

给定一个二叉树,找出其最小深度。

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

**说明:**叶子节点是指没有子节点的节点。

输入输出示例:

  • 输入: root = [3,9,20,null,null,15,7]
  • 输出: 2

(2)解题思路

  • 求最小深度,考虑使用广度优先遍历。
  • 在广度优先遍历过程中,遇到叶子节点,停止遍历,返回节点层级。

(3)解题步骤

  • 广度优先遍历整棵树,并记录每个节点的层级。
  • 遇到叶子节点,返回节点层级,停止遍历。

(4)代码实现

/\*\*
 \* @param {TreeNode} root
 \* @return {number}
 \*/

let 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]);
        }
    }
}

3、leetcode102二叉树的层序遍历(中等)

(1)题意

附上题目链接:leetcode102二叉树的层序遍历

给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。

输入输出示例:

  • 输入: 二叉树:[3,9,20,null,null,15,7]
    3
   / \
  9  20
    /  \
   15   7

  • 输出:
[
  [3],
  [9,20],
  [15,7]
]

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

 }
    if(n.right){
        q.push([n.right, l + 1]);
    }
}

}


### 3、leetcode102二叉树的层序遍历(中等)


**(1)题意**


附上题目链接:[leetcode102二叉树的层序遍历](https://bbs.csdn.net/topics/618545628)


给你一个二叉树,请你返回其按 **层序遍历** 得到的节点值。 (即逐层地,从左到右访问所有节点)。


**输入输出示例:**


* **输入**: 二叉树:`[3,9,20,null,null,15,7]`

 
3

/
9 20
/
15 7

* **输出**:

 

[
[3],
[9,20],
[15,7]
]




[外链图片转存中...(img-T8w0i3Qj-1715722010498)]
[外链图片转存中...(img-QOw7TzzB-1715722010499)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 15
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值