JavaScript版数据结构与算法(一)栈、队列、链表、集合、树

news2024/11/15 22:53:00

一、前言

为什么要学习数据结构与算法?最重要的就是面试要考算法,另外就是如果在实际工作当中,能够使用算法优化代码,会提升代码质量和运行效率,作为一名前端人员可能在实际中用的并不是特别多。数据结构与算法是分不开的,数据结构是计算机存储、组织数据的方式,算法是一系列解决问题的清晰指令,程序就是数据结构+算法。算法刷题大家都知道,就是力扣。刷题顺序推荐按类型刷题,比如栈相关的题,一次刷好几道,巩固巩固。刷题过程中需要重点关注的是通用套路、时间/空间复杂度分析和优化。其实这跟高中初中做数学题很像,有通用的套路可循,但是需要多复习,多看错题,做题的时候尽量把这道题考察的知识点都总结出来。
要学习的数据结构大致分为下面几类:

  • 有序:栈、队列、链表
  • 无序:集合、字典
  • 有相互连接关系:树、堆(特殊的树)、图

学习的算法大致分为下面几类:

  • 链表:遍历链表、删除链表节点。
  • 树、图:深度/广度优先遍历。
  • 数组:冒泡/选择/插入/日并/快速排序、顺序/二分搜索。

关于时间复杂度:
用O()函数表示,定性描述算法运行时间

  • O(1)
    代码只会执行一次,没有任何循环
    let i = 1;
    i += 1;
    
  • O(n)
    for循环中的代码会执行n次
    for (let i = 0; i < n; i += 1) {
    	console. log(i);
    }
    
  • O(1) + O(n) = O(n)
    代码上下顺序执行是两个时间复杂度相加,只算增长趋势较大的时间复杂度,增长趋势较小的时间复杂度可以忽略
    let i = 1;
    i += 1;
    for (let j = 0; j < n; j += 1) {
    	console. log(i);
    }
    
  • O(n) * O(n) = O(n^2)
    两个方法嵌套的时间复杂度需要两个时间复杂度相乘
    for (let i=0;i<n;i+=1){
    	for (let j = 0; j < n; j += 1) {
    		console. log(i, j);
    	}
    }
    
  • O(logN)
    let i = 1;
    while(i < n){
    	console.log(i);
    	i *= 2;
    }
    

空间复杂度
用O()函数表示
算法在运行过程中临时占用存储空问大小的量度,越小越好

  • O(1)
    只有一个变量
    let i = 0;
    i += 1
    
  • O(n)
    在内存中声明了n个变量
    const list = [];
    for (let i=0; i<n;i+=1){
    	list.push(i);
    }
    
  • O(n^2)
    就是一个矩阵
    const matrix = [];
    for(let i = 0;i < n;i += 1){
    	matrix.push([])
    	for(let j=0; j<n; j +=1){
    		matrix[i].push(j)
    	}
    }
    

二、栈

栈就是一个后进先出的数据结构
在这里插入图片描述
javascript中没有栈这种数据结构,但是可以用Array实现栈,模仿栈的操作。

const stack = [];
// 入栈
stack.push(1);
stack.push(2);
// 出栈
// pop()将最后的元素删除,并且返回
const item1 = stack.pop()
console.log(item1)
// 出栈
const item2 = stack.pop()
console.log(item2)

什么场景下用栈?
所有后进先出的场景,例如:

  • 十进制转二进制
    需要用十进制数不断除以2,并且倒序取余数,后算出的余数要排在前面,因此可以将余数依次存入栈中,再出栈,就可以实现余数倒序输出
    在这里插入图片描述
/**
 * 十进制转二进制
 * */
// 1、数字除以2 得到商和余数
// 2、余数push到栈中
// 3、商继续除以2 得商和余数
// 4、余数继续入栈
const trans = function (num) {
    const stack = []
    let n = num;
    while (n > 0) {
        // 获取商
        const shang = Math.floor(n / 2);
        // 获取余数
        const yushu = n % 2;
        // 余数入栈
        stack.push(yushu);
        // n重新赋值
        n = shang;
    }
    return stack.reverse().join('')
}
  • 判断字符串的括号是否有效
    在敲代码的时候,代码编辑器经常要判断括号是否正常闭合,无效的括号会给我们错误提示。这个算法就是编辑器中常用的判断括号是否有效的算法。
    越靠后的左括号,对应的右括号越靠前。
    左括号入栈,右括号出栈,最后栈空了就是合法的。
    在这里插入图片描述

  • 函数调用堆栈
    最后调用的函数,是最先执行完的
    greeting() start -> [1] 操作 -> sayHi() start -> sayHi() end -> [2] 操作 -> greeting() end -> [3] 操作
    JS解释器使用栈来控制函数的调用顺序。
    在这里插入图片描述

三、队列

先进先出
javascript中没有队列,但能用数组实现队列

// 创建队列
const queue = [];
// 入队
queue.push(1)
queue.push(2)
// 出队
const item1 = queue.shift()
console.log(item1)
const item2 = queue.shift()
console.log(item2)

应用场景:
所有先进先出的场景
食堂打饭
js异步任务队列(事件循环)
异步任务会放在任务队列中,先放进队列的先执行
计算最近请求次数

四、链表

多个元素组成的链表
但是元素存储不连续,用 next 指针连在一起。

数组 VS 链表
数组:增删非首尾元素时往往需要移动元素。
链表:增删非首尾元素,不需要移动元素,只需要更改 next 的指向即可。

javascript中可以用 object 模拟链表

前端中的链表–原型链
原型链的本质是链表。
原型链上的节点是各种原型对象,比如Function.prototype, Object.prototype…
原型链通过_proto_属性连接各种原型对象。
原型链知识点
如果 A 沿着原型链能找到 B.prototype,那么A instanceof B 为true。
如果在 A 对象上没有找到x 属性,那么会沿着原型链找 x 属性。
面试题一
instanceof 的原理,并用代码实现。

// 如果 A 沿着原型链能找到 B.prototype,那么A instanceof B 为true。
function instanceOf(A, B){
    let p = A;
    while(p){
        if(p == B.prototype) return true;
        p = p.__proto__;
    }
    return false;
}

五、集合

集合是一种无序且唯一的数据结构。
栈、队列、链表都是有序的数据结构,并且元素都是可以重复的。
前端中的集合: Set
常用操作:
数组去重
[...new Set([1, 2, 3, 1, 2])]
判断某元素是否在集合中
set.has(3)
求两个集合的交集

const set = new Set([1,2,3,2,1])
const set2 = new Set([2,3,4])
// 筛选出set中有,并且set2里面也有的元素
const set3 = new Set([...set].filter(s=>set2.has(s)))
console.log(set3)

字典
与集合类似,字典也是一种存储唯一值的数据结构,但它是以键值对的形式来存储。
ES6 中有字典,名为 Map。
字典的常用操作:键值对的增删改查。

// 增删改查
const m = new Map();
m.set('a', 'aa')
m.set('b', 'bb')
m.delete('b')
// 清空
// m.clear();
// 改直接覆盖set
m.set('a', '啊啊')

// 求两个数组中都存在的元素,要求去重
const arr1 = [1,2, 2,3,4]
const arr2 = [2,3,2,4]
// 字典中的key也是唯一的,所以遍历数组一创建字典就不会有重复的key
const map = new Map();
arr1.forEach(a=>{
    map.set(a, true)
})
const res = []
arr2.forEach(a=>{
    if(map.get(a)) {
        res.push(a)
        map.delete(a)
    }
})
console.log(res)

六、树

树是一种分层数据的抽象模型
前端的树:DOM树、级联选择、树形控件
JS中没有树,只能用Array和Object模拟
树的常用操作:深度/广度优先遍历、二叉树的先中后序遍历。
深度优先遍历:尽可能深的搜索树的分支
广度优先遍历:先访问离根节点最近的节点
下面一图,左侧是深度优先遍历的访问顺序,右侧是广度优先遍历的访问顺序
在这里插入图片描述
深度优先遍历算法口诀 (最为重要)
1、访问根节点
2、对根节点的 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);
    // 对根节点的children依次进行深度优先遍历
    root.children.forEach(dfs);
}

console.log(dfs(tree))

广度优先遍历算法口诀
1、新建一个队列,把根节点入队。
2、把队头出队并访问。
3、把队头的 children 挨个入队。
4、重复第二、三步,直到队列为空。

// 广度优先遍历
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)
        // 队头的children挨个入队
        n.children.forEach(c => q.push(c));
    }
}
bfs(tree)

树在前端中的应用

  • 访问json数据中的所有节点
    使用深度优先遍历实现,深度优先遍历就是先访问根节点,然后对每个根节点进行深度优先遍历
// 访问json中所有节点值
const json = {
    a: {
        b: {
            c: 3
        }
    },
    d: [1, 2]
}

// 深度优先遍历 使用path记录每个节点的路径
const dfs = (n, path) =>{
	// 访问当前节点
    console.log(n, path)
    // 使用Object.keys遍历n的所有子节点
    Object.keys(n).forEach(k=>{
        dfs(n[k], path.concat(k))
    })
}

dfs(json, [])

在这里插入图片描述

七、二叉树

树中每个节点最多只能有两个子节点。
在JS中通常用 Object 来模拟二叉树。

const binaryTree = {
    val: '1',
    left: {
        val: '2',
        left: null,
        right: null
    },
    right: {
        val: '3',
        left: null,
        right: null
    }
}

二叉树的遍历主要有三种:先序遍历、中序遍历、后序遍历
二叉树的先序遍历
根->左->右
在这里插入图片描述

1、访问根节点。
2、对根节点的左子树进行先序遍历。
3、对根节点的右子树进行先序遍历。

const binaryTree = {
    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 preorder = root => {
    if (!root) return;
    console.log(root.val);
    preorder(root.left);
    preorder(root.right);
}
preorder(binaryTree)

二叉树的中序遍历
左->根->右
在这里插入图片描述
1、对根节点的左子树进行中序遍历。
2、访问根节点。
3、对根节点的右子树进行中序遍历。
首先对于整棵树而言,根节点是5,要先对于左子树2进行中序遍历,找2 的左子树,是1,对1中序遍历,1没有左子树,所以1是最先访问的节点
当前节点是1,访问1的根节点,就是2,第二个访问的是2
然后对2这棵树的右子树进行中序遍历,即4,先找4的左子树,即3,访问3,即第三个访问的是3
3访问完,要访问3的根节点,第四个访问的是4
4没有右子树,所以结束访问,2也结束了访问
此时就要访问2节点所在的根节点,即5,第五个访问的是5
在找5的右子树,进行中序遍历,它的右子树6没有左子树,所以先访问根节点6,即第六个访问的是6
访问6之后需要访问6树的右子树,即7,因此第七个访问的是7

二叉树的后序遍历
左->右->根
在这里插入图片描述
1、对根节点的左子树进行后序遍历。
2、对根节点的右子树进行后序遍历。
3、访问根节点。

// 将二叉树定义为一个独立可复用的模块
const binaryTree = require('./binaryTree')
const postorder = (root) => {
    if (!root) return;
    // 1 左子树进行后序遍历
    postorder(root.left);
    // 2 右子树进行后序遍历
    postorder(root.right);
    // 3 访问根节点
    console.log(root.val);
}
postorder(binaryTree)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1369300.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何使用内网穿透实现iStoreOS软路由公网远程访问局域网电脑桌面

文章目录 简介一、配置远程桌面公网地址二、家中使用永久固定地址 访问公司电脑**具体操作方法是&#xff1a;** 简介 软路由是PC的硬件加上路由系统来实现路由器的功能&#xff0c;也可以说是使用软件达成路由功能的路由器。 使用软路由控制局域网内计算机的好处&#xff1a…

文心一言 VS 讯飞星火 VS chatgpt (175)-- 算法导论13.3 4题

四、用go语言&#xff0c;Teach 教授担心 RB-INSERT-FIXUP可能将 T.nil.color 设为 RED&#xff0c;这时&#xff0c;当 z 为根时第1行的测试就不会让循环终止。通过讨论 RB-INSERT-FIXUP永远不会将 T.nil.color 设置为 RED&#xff0c;来说明这位教授的担心是没有必要的。 文…

正则表达式Regex

是什么&#xff1a;一句话&#xff0c;正则表达式是对字符串执行模式匹配的技术。 从一段字符串中提取出所有英文单词、数字、字母和数字。 如果采用传统方法&#xff1a;将字符串的所有字符分割成单个&#xff0c;根据ASCII码判断&#xff0c;在一定范围内就是字母&#xff…

时序预测 | Matlab基于灰色隐马尔可夫模型(HMMP-GM11)的时间序列预测

时序预测 | Matlab基于灰色隐马尔可夫模型(HMMP-GM11)的时间序列预测 目录 时序预测 | Matlab基于灰色隐马尔可夫模型(HMMP-GM11)的时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 灰色HMMP-GM11改进模型,通过引入隐马尔可夫模型(HMM)来对原始数据进行状态分…

【2023最新版】20款经济高效的开源网络安全工具推荐

如果企业还没有试用过开源工具来满足日常网络安全需求&#xff0c;就意味着企业可能错过了很多免费的“午餐”。需要了解的是&#xff0c;现在很多开源安全工具都来自活跃的知名社区&#xff0c;技术先进且完全可信&#xff0c;并且有很多专业安全公司和大型运营商都参与其中。…

干货!数字化时代下的企业合规与增长

本文提炼总结自专刊《上市公司如何打好合规与增长的双赢之战》。全书40页&#xff0c;出品方纷享销客市场部。如有需要&#xff0c;请阅读原版电子书&#xff1a;上市公司如何打好合规与增长的双赢之战 目 录 一、潮起&#xff1a;一场复杂的博弈 1. 数字经济大潮对企业提…

适合邮件推广的行业有哪些?

邮件推广因其价格便宜覆盖范围广等优势备受各个行业的企业和单位欢迎&#xff0c;能有效的吸引潜在客户、增加销售额、提高品牌知名度。U-Mail邮件群发平台(根据已有客户给大家总结一下适合进行邮件推广的行业 一、电子电商行业 随着电子商务的普及&#xff0c;越来越多的消费…

xss-labs(6-9)

level6:欢迎来到level6 老规矩还是先看看输入框的闭合情况 尝试事件函数绕过 test" onclick="alert(欢迎来钓鱼) 既然事件函数被转义了,那就使用我们第二关用过的绕过方法插入标签看看 test"><script>alert(欢迎来钓鱼)</script>// <

什么是博若莱新酒节?

在红酒圈儿里混&#xff0c;一定不能不知道博若莱新酒节&#xff0c;这是法国举世闻名的以酒为主题的重要节日之一。现已成为世界范围内庆祝当年葡萄收获和酿制的节日&#xff0c;被称为一年一度的酒迷盛会。 云仓酒庄的品牌雷盛红酒LEESON分享博若莱位于法国勃艮第南部&#x…

78.乐理基础-非常见拍号如何打拍子

内容来源于&#xff1a;三分钟音乐社 上一个内容&#xff1a;76.乐理基础-打拍子-二连音、四连音-CSDN博客 非常见的拍子就是下图中除了红圈里的五个拍号的拍号 最大优先级的规则&#xff1a; 规则一&#xff1a;所有的拍号绝对&#xff0c;永远都可以以一拍为单位&#xff0…

GEC6818 智能语音家居系统——原神主题的平板

GEC6818 智能语音家居系统——原神主题的平板 文章目录 GEC6818 智能语音家居系统——原神主题的平板一、 滑动解锁密码解锁二、 在桌面有两种方式可以进行选择2.1 普通点击模式2.1.1 电子相册2.1.2 监控2.1.3 画板2.1.4 视频播放2.1.5 五子棋小游戏2.1.6 烟雾传感器GY39RFID 2…

基于Java SSM框架实现游戏论坛平台系统项目【项目源码+论文说明】

基于java的SSM框架实现游戏论坛平台系统演示 摘要 本论文主要论述了如何使用java语言开发一个游戏论坛平台的设计&#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架构、ssm 框架和 java 开发的 Web 框架&#xff0c;基于Werkzeug WSGI工具箱和…

【EAI 005】EmbodiedGPT:通过具身思维链进行视觉语言预训练的具身智能大模型

论文描述&#xff1a;EmbodiedGPT: Vision-Language Pre-Training via Embodied Chain of Thought 论文作者&#xff1a;Yao Mu, Qinglong Zhang, Mengkang Hu, Wenhai Wang, Mingyu Ding, Jun Jin, Bin Wang, Jifeng Dai, Yu Qiao, Ping Luo 作者单位&#xff1a;The Universi…

使用 CSS : 伪元素:after、过渡动画transition实现过渡效果(鼠标悬浮或点击 标签时,底部边框从左到右过渡)

首先&#xff0c;给 <span> 标签添加一个父元素&#xff0c;定义属性类名&#xff1a;nav-wrapper &#xff0c;父级设置相对定位。然后&#xff0c;使用 ::after 伪元素来创建一个与底部边框相同宽度的元素&#xff0c;初始时宽度为 0&#xff0c;通过过渡动画transitio…

fastadmin列表头部审核通过,审核驳回按钮,输入原因post传递

index.html <a class="btn btn-success btn-change btn-start btn-disabled" data-params="action=start" data-url

UE5 将类修改目录

有个需求&#xff0c;需要修改ue里面类的位置&#xff0c;默认在Public类下面&#xff0c;我想创建一个二级目录&#xff0c;将所有的类分好位置&#xff0c;方便查看。 上图为创建一个类所在的默认位置。 接下来&#xff0c;将其移动到一个新的目录中。 首先在资源管理器中找…

TS 36.213 V12.0.0-PUSCH相关过程(3)-UE sounding过程

​本文的内容主要涉及TS 36.213&#xff0c;版本是C00&#xff0c;也就是V12.0.0。

快速入门Visual Studio 2022开发.Net Framework研发环境指南

IDE工具 Visual Studio 2022 Vs2022企业版 - VisualStudioSetup.exe Visual Studio Code VSCodeUserSetup-x64-1.66.2.exeVSCodeUserSetup-x64-1.67.0-insider.exe IDE环境 编程字体YaHei.Consolas YaHei.Consolas.1.12.ttf IDE插件 Visual Studio Code常用插件 Chinese…

基于Github官方教程的快速入门学习

GitHub 是一个用于版本控制和协作的代码托管平台。 它允许您和其他人随时随地协同处理项目。 创建仓库 在任何页面的右上角&#xff0c;使用 下拉菜单选择“新建存储库”。 之后会进入创建仓库的界面&#xff0c;需要我们进行如下操作&#xff1a; 写仓库的名字写对于本仓库…