本文已参与「新人创作礼」活动,一起开启掘金创作之路。
本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
【若川视野 x 源码共读】第32期 | yocto-queue 队列 链表点击了解本期详情一起参与。
本章涉及
- 数组和链表的异同
- 数组和链表的异同
- 实现一个队列
- Symbol.iterator的理解
- yocto-queue的适用场景
分析源码
先看用法,这个库是实现了数据结构中的队列,队列具有先进先出(FIFO)的属性。
通常,我们在js中用数组来模拟队列的操作
const queue = []
queue.push()
queue.shift()
既然原生的数组就可以模拟出队列的操作,这个库的意义在哪里,有一个值得关注的点
这个库实现了将时间复杂度降到O(1)
- 使用链表来模拟队列的操作
Symbol.iterator实现自定义迭代器- 数组和链表的异同
- 数组因为是连续地址存储,所以可以直接通过索引来访问
- 链表不一定是连续的地址,但是便于插入删除操作
/*
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; // 指针指向下一个元素
}
}
}
测试用例
-
项目依赖
-
"ava": "^3.15.0", // 测试库 "tsd": "^0.17.0", // 检查TypeScript类型 "xo": "^0.44.0" // 格式化代码规范
覆盖实现的功能,我们主要挑迭代的测试来看
test('iterable', t => {
const queue = new Queue();
queue.enqueue('🦄');
queue.enqueue('🌈');
t.deepEqual([...queue], ['🦄', '🌈']);
});
使用解构的方式构建新的数组,查看是否与测试用例相等
总结
-
链表和数组的区别
- 链表的顺序是随机访问,所以通过指针next指向下一个元素访问
- 数组的顺序是连续的,所以可以通过索引下标直接访问
- 链表增删比较简单高效,通过指针的指向即可修改,时间复杂度为
O(n) - 数组增删需要移动多个元素,取决于元素插入的位置,时间复杂度为
O(1)
-
Symbol.iterator实现for...of....迭代 -
适用场景
- 数据量较大的时候,并进行增删操作时,链表的优势比较明显