一文了解树🌳在前端中的应用,掌握数据结构中树的生命线(一)

简介: 在接下来的这篇文章中,将讲解树这个数据结构的一些基本操作,以及树在前端中的应用。

0.png🏕️序言


在我们的日常生活中,无时无刻都会看到树。比如,在街上行走时,就有着一排排的树。那么,树在前端中,都有哪些应用呢?

事实上,前端在写页面时,每个页面就有它对应的 DOM 树、 CSSOM 树等等。除此之外呢,像我们写级联选择器时,它也是一层叠一层的,就像一棵树一样。

在接下来的这篇文章中,将讲解树这个数据结构的一些基本操作,以及树在前端中的应用。

一起来学习叭~🧐


🌲一、树是什么?


  • 树是一种具有分层数据功能的抽象模型。
  • 前端工作中常用的树包括: DOM 树、级联选择、树形空间……。
  • JS 中没有树,但是可以用 ObjectArray 构建树。
  • 树的常用操作:深度/广度优先遍历、先中后序遍历。


🌴二、深/广度优先遍历


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)总结

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

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

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



相关文章
|
5天前
|
存储 机器学习/深度学习
【数据结构】二叉树全攻略,从实现到应用详解
本文介绍了树形结构及其重要类型——二叉树。树由若干节点组成,具有层次关系。二叉树每个节点最多有两个子树,分为左子树和右子树。文中详细描述了二叉树的不同类型,如完全二叉树、满二叉树、平衡二叉树及搜索二叉树,并阐述了二叉树的基本性质与存储方式。此外,还介绍了二叉树的实现方法,包括节点定义、遍历方式(前序、中序、后序、层序遍历),并提供了多个示例代码,帮助理解二叉树的基本操作。
34 13
【数据结构】二叉树全攻略,从实现到应用详解
|
6天前
|
存储 Java 索引
【数据结构】链表从实现到应用,保姆级攻略
本文详细介绍了链表这一重要数据结构。链表与数组不同,其元素在内存中非连续分布,通过指针连接。Java中链表常用于需动态添加或删除元素的场景。文章首先解释了单向链表的基本概念,包括节点定义及各种操作如插入、删除等的实现方法。随后介绍了双向链表,说明了其拥有前后两个指针的特点,并展示了相关操作的代码实现。最后,对比了ArrayList与LinkedList的不同之处,包括它们底层实现、时间复杂度以及适用场景等方面。
27 10
【数据结构】链表从实现到应用,保姆级攻略
|
2天前
|
存储 算法 C语言
数据结构基础详解(C语言): 二叉树的遍历_线索二叉树_树的存储结构_树与森林详解
本文从二叉树遍历入手,详细介绍了先序、中序和后序遍历方法,并探讨了如何构建二叉树及线索二叉树的概念。接着,文章讲解了树和森林的存储结构,特别是如何将树与森林转换为二叉树形式,以便利用二叉树的遍历方法。最后,讨论了树和森林的遍历算法,包括先根、后根和层次遍历。通过这些内容,读者可以全面了解二叉树及其相关概念。
|
2天前
|
存储 机器学习/深度学习 C语言
数据结构基础详解(C语言): 树与二叉树的基本类型与存储结构详解
本文介绍了树和二叉树的基本概念及性质。树是由节点组成的层次结构,其中节点的度为其分支数量,树的度为树中最大节点度数。二叉树是一种特殊的树,其节点最多有两个子节点,具有多种性质,如叶子节点数与度为2的节点数之间的关系。此外,还介绍了二叉树的不同形态,包括满二叉树、完全二叉树、二叉排序树和平衡二叉树,并探讨了二叉树的顺序存储和链式存储结构。
|
2天前
|
存储 C语言
数据结构基础详解(C语言): 树与二叉树的应用_哈夫曼树与哈夫曼曼编码_并查集_二叉排序树_平衡二叉树
本文详细介绍了树与二叉树的应用,涵盖哈夫曼树与哈夫曼编码、并查集以及二叉排序树等内容。首先讲解了哈夫曼树的构造方法及其在数据压缩中的应用;接着介绍了并查集的基本概念、存储结构及优化方法;随后探讨了二叉排序树的定义、查找、插入和删除操作;最后阐述了平衡二叉树的概念及其在保证树平衡状态下的插入和删除操作。通过本文,读者可以全面了解树与二叉树在实际问题中的应用技巧和优化策略。
|
3天前
|
Java
【数据结构】栈和队列的深度探索,从实现到应用详解
本文介绍了栈和队列这两种数据结构。栈是一种后进先出(LIFO)的数据结构,元素只能从栈顶进行插入和删除。栈的基本操作包括压栈、出栈、获取栈顶元素、判断是否为空及获取栈的大小。栈可以通过数组或链表实现,并可用于将递归转化为循环。队列则是一种先进先出(FIFO)的数据结构,元素只能从队尾插入,从队首移除。队列的基本操作包括入队、出队、获取队首元素、判断是否为空及获取队列大小。队列可通过双向链表或数组实现。此外,双端队列(Deque)支持两端插入和删除元素,提供了更丰富的操作。
10 0
【数据结构】栈和队列的深度探索,从实现到应用详解
|
11天前
|
开发者 存储 API
Xamarin 开发者的社区资源概览:从官方文档到GitHub示例,全面探索提升开发技能与解决问题的多元化渠道与实用工具
【8月更文挑战第31天】Xamarin 开发者社区资源概览旨在提升开发效率与解决问题,涵盖官方文档、社区论坛、GitHub 项目等。官方文档详尽,涵盖 Xamarin.Forms 使用、性能优化等;社区论坛供交流心得;GitHub 提供示例代码。此外,第三方博客、视频教程及 Xamarin University 等资源也丰富多样,适合各阶段开发者学习与提升。通过综合利用这些资源,开发者可不断进步,应对技术挑战。
26 0
|
11天前
|
iOS开发 Android开发 MacOS
从零到全能开发者:解锁Uno Platform,一键跨越多平台应用开发的神奇之旅,让你的代码飞遍Windows、iOS、Android、macOS及Web,技术小白也能秒变跨平台大神!
【8月更文挑战第31天】从零开始,踏上使用Uno Platform开发跨平台应用的旅程。只需编写一次代码,即可轻松部署到Windows、iOS、macOS、Android及Web(通过WASM)等多个平台。Uno Platform为.NET生态带来前所未有的灵活性和效率,简化跨平台开发。首先确保安装了Visual Studio或VS Code及.NET SDK,然后选择合适的项目模板创建新项目。项目结构类似传统.NET MAUI或WPF项目,包含核心NuGet包。通过简单的按钮示例,你可以快速上手并构建应用。Uno Platform让你的技术探索之旅充满无限可能。
17 0
|
11天前
|
前端开发 JavaScript 开发者
JSF与WebSockets,打造实时通信魔法!让你的Web应用秒变聊天室,用户体验飞升!
【8月更文挑战第31天】在现代Web应用开发中,实时通信对于提升用户体验至关重要。本文探讨了如何在主要面向Web应用开发的JSF(JavaServer Faces)框架中引入WebSockets支持,以实现客户端与服务器之间的全双工通信。通过具体示例展示了在JSF应用中实现WebSockets的基本步骤:添加依赖、创建服务器端点以及在前端页面中嵌入JavaScript客户端代码。尽管这一过程中可能会遇到一些挑战,如复杂代码编写和额外配置需求,但借助AWS等云服务平台,开发者仍能高效地完成部署和管理工作,从而增强Web应用的实时通信能力。
15 0
|
11天前
|
API UED 开发者
如何在Uno Platform中轻松实现流畅动画效果——从基础到优化,全方位打造用户友好的动态交互体验!
【8月更文挑战第31天】在开发跨平台应用时,确保用户界面流畅且具吸引力至关重要。Uno Platform 作为多端统一的开发框架,不仅支持跨系统应用开发,还能通过优化实现流畅动画,增强用户体验。本文探讨了Uno Platform中实现流畅动画的多个方面,包括动画基础、性能优化、实践技巧及问题排查,帮助开发者掌握具体优化策略,提升应用质量与用户满意度。通过合理利用故事板、减少布局复杂性、使用硬件加速等技术,结合异步方法与预设缓存技巧,开发者能够创建美观且流畅的动画效果。
34 0