【源码共读】yocto-queue 一个微型队列数据结构

news2024/11/18 1:34:26

yocto-queue是一个微型队列的数据结构,根据作者的介绍,如果在你一个数据量很大的数组上,大量的操作Array.pushArray.shift,那么你可以考虑使用yocto-queue来替代Array

因为Array.shift的时间复杂度是O(n),而Queue.dequeue的时间复杂度是O(1),这对于大量的数据来说,性能上的提升是非常明显的。

时间复杂度和空间复杂度

学习算法和数据结构的时候,我们经常会听到时间复杂度和空间复杂度,这两个概念是什么呢?

时间复杂度

时间复杂度是指一个算法执行所耗费的时间,它是一个函数,这个函数的变量是问题规模的函数;

通常会使用大O符号来表示时间复杂度,比如O(n)O(n^2)O(logn)等等,这就是大 O 表示法(Big O notation)。

O代表的是算法的渐进时间复杂度(asymptotic time complexity),也就是说,随着问题规模的增大,算法的执行时间的增长率和O中的函数相同,称作渐进时间复杂度。

O(1)表示算法的执行时间与问题规模无关,也就是说,不管问题规模有多大,算法执行所耗费的时间都是一样的,这种算法称为时间复杂度为常数阶的算法。

O(n)表示算法的执行时间与问题规模成正比,也就是说,随着问题规模的增大,算法执行所耗费的时间也随之增大,这种算法称为时间复杂度为线性阶的算法。

O(n^2)表示算法的执行时间与问题规模成平方比,也就是说,随着问题规模的增大,算法执行所耗费的时间呈二次方增长,这种算法称为时间复杂度为平方阶的算法。

通过上面的介绍,我们可以将O比喻成函数,O(1)就是一个常数函数,O(n)就是一个线性函数,O(n^2)就是一个平方函数,O(logn)就是一个对数函数。

说了这么多概念性的问题,不如直接来看看代码;

例如,我们有一个数组,我们要遍历这个数组,然后将数组中的每个元素都乘以2,那么我们可以这么写:

function multiplyBy2(arr) {const result = [];for (let i = 0; i < arr.length; i++) {result.push(arr[i] * 2);}return result;
} 

这段代码的时间复杂度是O(n),因为我们遍历了数组,所以时间复杂度就是O(n)O代表这个函数,n代表问题规模,也就是数组的长度,组合起来就是O(n)

再直观一点,我们可以这么理解,当数组的长度为n时,我们需要执行n次循环,所以时间复杂度就是O(n),用代码表示就是:

function O(n) {for (let i = 0; i < n; i++) {// do something}
}

O(1); // O(1)
O(2); // O(2)
O(3); // O(3)
O(n); // O(n) 

空间复杂度

空间复杂度是指一个算法执行所耗费的内存空间,它是一个函数,这个函数的变量是问题规模的函数;

和时间复杂度一样,空间复杂度也有O(1)O(n)O(n^2)O(logn)等等,它们的含义和时间复杂度一样,只不过它们是表示算法执行所耗费的内存空间的增长率。

当然空间复杂度计算的不是内存消耗,而是变量的个数,例如冒泡排序的空间复杂度是O(1),因为它只需要一个变量temp

function bubbleSort(arr) {for (let i = 0; i < arr.length; i++) {for (let j = 0; j < arr.length - i - 1; j++) {if (arr[j] > arr[j + 1]) {const temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}}return arr;
} 

而快速排序的空间复杂度是O(logn),因为它需要一个变量pivot,而且它是递归的,所以空间复杂度是O(logn)

function quickSort(arr) {if (arr.length <= 1) {return arr;}const pivotIndex = Math.floor(arr.length / 2);const pivot = arr.splice(pivotIndex, 1)[0];const left = [];const right = [];for (let i = 0; i < arr.length; i++) {if (arr[i] < pivot) {left.push(arr[i]);} else {right.push(arr[i]);}}return quickSort(left).concat([pivot], quickSort(right));
} 

源码分析

上面了解了时间复杂度和空间复杂度的概念,那么我们开始正式分析yocto-queue的源码;

  • yocto-queue
  • yocto-queue github1s

代码不多,可以直接通过github1s在线阅读,然后使用浏览器控制台来查看效果;

代码分析

先来看一下yocto-queue的使用方式:

  • 安装
npm install yocto-queue 
  • 使用
import Queue from 'yocto-queue';

const queue = new Queue();

queue.enqueue('🦄');
queue.enqueue('🌈');

console.log(queue.size);
//=> 2

console.log(...queue);
//=> '🦄 🌈'

console.log(queue.dequeue());
//=> '🦄'

console.log(queue.dequeue());
//=> '🌈' 

然后再来看一下yocto-queue的代码:

/*
How it works:
`this.#head` is an instance of `Node` which keeps track of its current value and nests another instance of `Node` that keeps the value that comes after it. When a value is provided to `.enqueue()`, the code needs to iterate through `this.#head`, going deeper and deeper to find the last value. However, iterating through every single item is slow. This problem is solved by saving a reference to the last value as `this.#tail` so that it can reference it to add a new value.
*/

class Node {value;next;constructor(value) {this.value = value;}
}

export default class Queue {#head;#tail;#size;constructor() {this.clear();}enqueue(value) {const node = new Node(value);if (this.#head) {this.#tail.next = node;this.#tail = node;} else {this.#head = node;this.#tail = node;}this.#size++;}dequeue() {const current = this.#head;if (!current) {return;}this.#head = this.#head.next;this.#size--;return current.value;}clear() {this.#head = undefined;this.#tail = undefined;this.#size = 0;}get size() {return this.#size;}* [Symbol.iterator]() {let current = this.#head;while (current) {yield current.value;current = current.next;}}
} 

可以直接直接粘贴到浏览器控制台中运行

构造函数

首先来看一下Queue的构造函数:

class Queue {#head;#tail;#size;constructor() {this.clear();}
} 

一个Queue类,它有三个私有属性:#head#tail#size

class中,如果属性名前面加上#,表示这个属性是私有属性,外部是无法访问的;

  • #head:指向队列的头部;
  • #tail:指向队列的尾部;
  • #size:队列的长度;

然后在构造函数中调用了this.clear()方法,这个方法的作用是清空队列,代码如下:

class Queue {clear() {this.#head = undefined;this.#tail = undefined;this.#size = 0;}
} 

enqueue

接下来看一下enqueue方法:

class Queue {enqueue(value) {const node = new Node(value);if (this.#head) {this.#tail.next = node;this.#tail = node;} else {this.#head = node;this.#tail = node;}this.#size++;}
} 

enqueue方法的作用是向队列中添加一个元素;

首先创建一个Node实例,然后判断this.#head是否存在,如果存在,说明队列中已经有元素了,那么就把新创建的Node实例添加到队列的尾部;

如果this.#head不存在,说明队列中还没有元素,那么就把新创建的Node实例添加到队列的头部;

最后,队列的长度加一;

这里使用到了一个Node类,它的作用是用来保存队列中的元素的,代码如下:

class Node {value;next;constructor(value) {this.value = value;}
} 
  • value:指向的是队列中的元素;
  • next:指向下一个Node实例;

dequeue

接下来看一下dequeue方法:

class Queue {dequeue() {const current = this.#head;if (!current) {return;}this.#head = this.#head.next;this.#size--;return current.value;}
} 

dequeue方法的作用是从队列中移除一个元素;

首先获取队列的头部元素,然后判断current是否存在,如果不存在,说明队列中没有元素,那么就直接返回;

如果current存在,说明队列中有元素,那么就把队列的头部元素移除,然后把队列的长度减一;

最后,返回移除的元素;

图例

一个队列结构只会有两个操作:入队和出队,下面通过一个图例来说明一下;

入队时,会把新的元素添加到队列的尾部;

出队时,会把队列的头部元素移除;

graph TD
#head --> Node0
Node0 -->|value| Current0
Node0 -->|next| Node1

Node1 -->|value| Current1
Node1 -->|next| Node2

Node2 -->|value| Current2
Node2 -->|next| Node3

Node3 -->|value| Current3
Node3 -->|next| Node_n

Node_n -->|value| Current_n

#tail --> Node_n 

上面的图例中,Node0表示队列的头部元素,Node_n表示队列的尾部元素;

Current0表示队列中的第一个元素,Current_n表示队列中的最后一个元素;

在队列中,只会关心头部和尾部的元素,头部的元素用于弹出队列,尾部的元素用于添加元素;

在上面的图例中,如果我们执行dequeue方法,那么就会把Node0移除,然后把Node1设置为队列的头部元素;

graph TD
dequeue -->|出队列 #head| #head

Node0 -->|value| Current0
Current0 -->|返回结果| End
Node0 -->|next| Node1

#head -->|修改指向为 Node1| Node1


Node1 -->|value| Current1
Node1 -->|next| Node2

Node2 -->|value| Current2
Node2 -->|next| Node3

Node3 -->|value| Current3
Node3 -->|next| Node_n

Node_n -->|value| Current_n

#tail --> Node_n 

上面的dequeue方法执行完毕后,队列的头部元素就变成了Node1

Node0因为没有任何引用指向它,所以会被垃圾回收机制回收;

如果我们执行enqueue方法,那么就会把新的元素添加到队列的尾部:

graph TD
#head --> Node0
Node0 -->|value| Current0
Node0 -->|next| Node1

Node1 -->|value| Current1
Node1 -->|next| Node2

Node2 -->|value| Current2
Node2 -->|next| Node3

Node3 -->|value| Current3
Node3 -->|next| Node_n

Node_n -->|next 指向| newNode
newNode -->|value| newData

#tail -->|修改指向| newNode 

上面的enqueue方法执行完毕后,队列的尾部元素就变成了newNode

迭代器

yocto-queue到最后还提供了一个迭代器,用于遍历队列中的元素;

class Queue {// ...*[Symbol.iterator]() {let current = this.#head;while (current) {yield current.value;current = current.next;}}
} 

上面的代码中,使用了一个generator函数,它会返回一个迭代器;

迭代器中,会从队列的头部元素开始遍历,然后把每个元素的值返回出去;

迭代器的使用

迭代器是es6中的一个新特性,它可以用于遍历数据结构中的元素,使用起来也非常简单,只需要在数据结构上调用Symbol.iterator方法即可;

const obj = {[Symbol.iterator]() {let i = 0;return {next() {return {value: i++,done: i > 10};}};}
};

for (const item of obj) {console.log(item);
} 

上面的代码中,我们定义了一个对象,它的Symbol.iterator方法返回了一个迭代器;

一个迭代器是一个对象,它有一个next方法,每次调用next方法,都会返回一个对象,这个对象中包含了当前元素的值和一个done属性,表示是否已经遍历完毕;

可以使用generator函数来简化上面的代码:

const obj = {*[Symbol.iterator]() {let i = 0;while (i < 10) {yield i++;}}
};

for (const item of obj) {console.log(item);
} 

上面的代码中,我们使用了generator函数来简化迭代器的定义;

参考:

  • Symbol.iterator
  • Generator 函数

总结

yocto-queue是一个非常简单的队列实现,它的核心是使用了链表来存储数据;

链表的优点是插入和删除元素的时间复杂度是O(1),但是查找元素的时间复杂度是O(n),不过对于队列来说,查找元素的操作是不需要的;

对比于数组,每次插入和删除元素都需要移动元素,所以时间复杂度是O(n),但是查找元素的时间复杂度是O(1)

所以正如文章开头,作者提到的,对于一个大数组来实现队列,效率是非常低的,而应该使用链表来实现(因为这个库的核心实现就是链表,所以肯定优先推荐用这个库=);

通过阅读yocto-queue的源码,我们学习到了很多:

  • 时间复杂度、空间复杂度的概念
  • 链表的实现
  • 如何使用es6Symbol.iterator来实现可迭代对象;
  • 如何使用es6generator函数来实现迭代器;
  • 数组、队列、链表的区别;

最后

最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

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

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

相关文章

长除法计算平方根的方法总结与代码实现(C++, Python)

tags: DSA Math C Python 写在前面 之前总结了计算平方根的方法, 但是并没有给出手算方法的解释, 这次专门写一下手算方法. 据说这个方法是中国的数学家创造的, 我也没深入考证过, 总之就是非常经典了, 因为这个长除法算法(英文:Long Division Algorithm)可以计算任意精度的…

《ESP32》Adafruit_GFX、u8g2驱动ssd1306

本示例将使用ESP32驱动ssd1306&#xff0c;将为你介绍SSD1306&#xff0c;接线方式以及如何使用Adafruit_GFX、u8g2两种模式进行开发SSD1306。 本人踩坑了半天&#xff0c;发现Adafruit_SSD1306并不能驱动器LVGL&#xff0c;其并没有实现关键的setAddrWindow和pushColors函数用…

【网络编程】第四章 网络套接字(守护进程+TCP英译汉+TCP通协议讯流程+TCP和UDP对比)

&#x1f3c6;个人主页&#xff1a;企鹅不叫的博客 ​ &#x1f308;专栏 C语言初阶和进阶C项目Leetcode刷题初阶数据结构与算法C初阶和进阶《深入理解计算机操作系统》《高质量C/C编程》Linux ⭐️ 博主码云gitee链接&#xff1a;代码仓库地址 ⚡若有帮助可以【关注点赞收藏】…

[Java]注解

文章目录⚽ 注解的概述⚽ 常见注解的使用示例&#x1f3d0; 文档相关的注解&#x1f3d0; 在编译时进行格式检查(JDK内置的三个基本注解)&#x1f3d0; 跟踪代码依赖性&#xff0c;实现替代配置文件功能⚽ 自定义注解⚽ JDK中的元注解&#x1f3d0; Retention&#x1f3d0; Tar…

【PowerDesign】制作数据流图

目录 文章目录 前言 一、下载软件 二、使用步骤 1.打开面板 2.绘制顶层0层/数据流程图 创建加工 画出数据流向 对格式进行设置 结果展示 2. 1层数据流程图 生成子加工 框图格式设置 结果展示 注意事项 3. 2层数据流程图 总结 前言 本文以图书管理系统举例&…

java开发的环保网站垃圾分类系统源码

本项目是基于springboot开发的小区垃圾分类的监管系统。为了更好的督促小区业主更好的进行垃圾分类和垃圾投放&#xff0c;本系统设计了一套积分奖罚机制&#xff0c;如果业主此次投放垃圾符合分类要求则加10积分&#xff0c;不符合则扣除200积分&#xff0c;积分不够需要进行扫…

东北大学c++实验最后一次

【问题描述】 建立两个磁盘文件f1.txt和f2.txt&#xff0c;编写程序实现以下工作&#xff1a; &#xff08;1&#xff09;从文件in.txt输入20个整数&#xff0c;分别存放在两个磁盘文件f1.txt和f2.txt中&#xff08;每个文件中放10个整数&#xff09;&#xff1b; &#xff…

【java】java集合详解

目录一.集合类型二.集合的不同三.List解析1.ArrayList2.LinkedList3.Vector四.Set解析1.HashSet2.TreeSet3.LinkedHashSet五.Map解析1.HashMap2.TreeMap3.HashTable4.ConcurrentHashMap一.集合类型 集合类型和关系(我画的比较简略&#xff0c;其中有很多继承实现关系都没有画),…

Web入门开发【三】- 准备工作

欢迎来到霍大侠的小院&#xff0c;我们来学习Web入门开发的系列课程。 首先我们来了解下这个课程能学到什么&#xff1f; 1、你将可以掌握Web网站的开发全过程。 2、了解基础的HTML&#xff0c;CSS&#xff0c;JavaScript语言。 3、开发自己的第一个网站。 4、认识很多对编…

刷爆力扣之字符串转换整数(atoi)

刷爆力扣之字符串转换整数(atoi) HELLO&#xff0c;各位看官大大好&#xff0c;我是阿呆 &#x1f648;&#x1f648;&#x1f648; 今天阿呆继续记录下力扣刷题过程&#xff0c;收录在专栏算法中 &#x1f61c;&#x1f61c;&#x1f61c; 该专栏按照不同类别标签进行刷题&a…

1、移动端基础

目录1、常见浏览器PC端移动端2、手机屏幕3、移动端调试方法4、视口4.1 布局视口 layout viewport4.2 视觉视口visual viewport4.3 理想视口 idea viewport **meta视口标签5、二倍图1、物理像素和物理像素比6、多倍图7 背景缩放background-size移动端背景图展示8、移动端主流方案…

IDEA创建kotlin项目

今天新建了一个kotlin项目&#xff0c;竟然不能导入jar包&#xff0c;原因是新建项目的时候&#xff0c;选择了kotlin作为Gradle的开发语音&#xff0c;kotlin语音里面&#xff0c;下面这行配置识别不了&#xff1a; implementation fileTree(dir: libs, include: [*.jar])所以…

【蓝桥杯】第10届Scratch国赛第6题程序2 -- 捉迷藏

[导读]&#xff1a;蓝桥杯大赛是工业和信息化部人才交流中心举办的全国性专业信息技术赛事。蓝桥杯大赛首席专家倪光南院士说&#xff1a;“蓝桥杯以考促学&#xff0c;塑造了领跑全国的人才培养选拨模式&#xff0c;并获得了行业的深度认可。” 春雷课堂计划推出Scratch蓝桥杯…

青龙面板搭建+QQ机器人

搭建青龙面板首先有个服务器 我这里看到华为云有活动就入手了一个 1.系统选择 centos7.9 华为云购买地址&#xff1a;https://activity.huaweicloud.com/1212_promotion/index.html 2. 服务器上安装宝塔 yum install -y wget && wget -O install.sh http://downl…

340页11万字智慧政务大数据资源平台数据治理方案

一.1.1 数据治理子系统 建设大数据治理子平台&#xff0c;提供数据标准管理、元数据管理、数据质量管理能力&#xff0c;实现对数据的规范治理与管理&#xff1b;提供数据工厂能力&#xff0c;实现对归集的数据进行清洗、加工&#xff0c;支撑业务的数据应用需求。具体&#xf…

ES6 箭头函数 Arrow Function

前言 1. ES6 前定义函数 2. ES6 箭头函数语法 3. ES6 箭头函数返回值 4. 箭头函数中的 this 到底是谁 ? 前言 ES6 新增了一种新的函数: 箭头函数 Arrow Function 箭头函数相当于匿名函数&#xff0c;简化了函数定义&#xff0c;将原函数的 function 关键字和函数名都删掉&am…

学习.NET MAUI Blazor(三)、创建.NET MAUI Blazor应用并使用AntDesignBlazor

大致了解了Blazor和MAUI之后&#xff0c;尝试创建一个.NET MAUI Blazor应用。 需要注意的是&#xff1a; 虽然都叫MAUI&#xff0c;但.NET MAUI与.NET MAUI Blazor 并不相同&#xff0c;MAUI还是以xaml为主&#xff0c;而MAUI Blazor则是以razor为主。 这个系列还是以MAUI Bla…

23. 【gRPC系列学习】gRPC安全认证-JWT认证

JWT 即 JSON Web Token,是用 JSON 形式安全传输信息的方法。本节介绍JWT与gRPC结合,关于JWT交互流程的介绍参考文末的链接。 1. 使用JWT客户端与服务端交互 1)客户端使用用户名、密码发送给服务端 2)服务端返回JWT数据,返回数据由三部分组成 Header:TOKEN 的类型,就是JW…

截至2022年12月共计451个信息安全国家标准 汇总

写在前面 早年刚参加信息安全工作更多的学点皮毛技术&#xff0c;到处找安全工具&#xff0c;跟踪poc&#xff0c;拿到一个就全网扫一遍&#xff0c;从来没有想过&#xff0c;系统化的安全工作应该怎样搞?我做的工作在安全体系中处于哪个阶段? 后来有机会做企业安全建设&…

二本跨专业自学编程及程序员就业之路——20W社招进银行

自学编程的道路 先做个自我介绍&#xff0c;我是一名普通二本院校的学生。在广州上学&#xff0c;21年毕业&#xff0c;非科班出身。上大学之前&#xff0c;很少接触电脑&#xff0c;连QQ都是别人送我的&#xff0c;当时还开心了好一阵子。 大学的时候&#xff0c;开始接触的第…