1. 前言
- 本文参加了由 公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
- 这是源码共读的第32期,链接:yocto-queue 队列 链表
2. 基础知识学习
2.1 队列学习
队列是一种先进先出线性数据结构,类似于现实中的排队场景。
特性
- 入队:元素添加到队尾
- 出队:元素从队首移除
用数组实现一个队列
kotlin
class MyQueue{
queue = [];
constructor() {
}
// 入队
enqueue(val) {
this.queue.push(val);
}
// 出队
dequeue() {
return this.queue.shift();
}
// 返回队首值
peek() {
return this.queue[0];
}
// 是否是一个空对队列
isEmpty() {
return this.queue.length === 0;
}
// 队列长度
size () {
return this.queue.length
}
}
const queue = new MyQueue();
queue.enqueue(1)
queue.enqueue(2)
queue.enqueue(3,)
queue.enqueue(4)
queue.dequeue()
queue.dequeue() // [3, 4]
console.log('fffffffffffffffffff',queue.peek()) // 3
2.2 链表学习
链表是一种动态性的线性数据结构,通过指针(引用)将零散的内存快串联起来,每个节点包含"数据域"和"指针域"。与数组相比,链表适合频繁的插入和删除操作
对比项 | 链表 | 数组 |
---|---|---|
内存分配 | 动态分配,无浪费 | 连续分配的内存,需预分配 |
访问速度 | O(n) 需要遍历 | O(1) 通过索引 |
插入/删除 | O(1) | O(n)需要搬移元素 |
适用场景 | 支持快速插入、删除,但随机访问效率低。 | 频繁访问、数据量固定 |
单链表实现如下
kotlin
// 链表节点类
class Node {
constructor(val) {
this.val = val;
this.next = null;
}
}
const isNull = v => v === null || v === undefined || v === ''
// 链表类
class LinkNode {
constructor() {
this.head = null; // 头节点
}
// 尾部添加节点
appendNode(node) {
if (!this.head) {
this.head = node;
return
}
const tail = this.getLastNode()
tail.next = node
}
// 头部添加节点
prependNode(node) {
if (!this.head) {
this.head = node;
}
node.next = this.head
this.head = node;
}
// 根据索引位置添加节点
addNodeByIndex(index, node) {
if (isNull(index)) return
if (index < 0 || index > this.getSize()) return
if (index === 0) {
this.head = this.head.next
return
}
let preNode = this.getNodeByIndex(index - 1)
let t = preNode.next
preNode.next = node
node.next = t
}
// 根据索引位置删除节点
delNodeByIndex(index) {
if (isNull(index)) return
if (index < 0 || index > this.getSize()) return
if (index === 0) {
const r = this.head
this.head = this.head.next
return r
}
let preNode = this.getNodeByIndex(index - 1)
const r = preNode.next
preNode.next = preNode.next.next
return r
}
// 根据索引位置查询节点
getNodeByIndex(index) {
if (isNull(index)) return
if (index < 0 || index > this.getSize()) return
let cur = this.head;
while (index > 0) {
cur = cur.next
index--
}
return cur
}
// 获取链表长度
getSize() {
let count = 0;
let cur = this.head;
while (cur) {
count++
cur = cur.next;
}
return count
}
// 获取最后一个节点
getLastNode() {
let cur = this.head
while (cur.next) {
cur = cur.next
}
return cur
}
// 反转链表
reverse() {
let cur = this.head
let pre = null
while(cur) {
let next = cur.next
cur.next = pre
pre = cur
cur = next
}
this.head = pre
return this.head
}
}
// 构建一个节点数组 【1,2,3,4,5 】
const nodeList = [1,2,3,4,5].map(x => new Node(x))
// 初始化列表
const linkNode = new LinkNode()
// 得到链表 1 => 2 => 3 => 4 => 5
nodeList.forEach(x => linkNode.appendNode(x))
// 向链表头部添加一个节点0
const node0 = new Node(0)
// 得到链表 0 => 1 => 2 => 3 => 4 => 5
linkNode.prependNode(node0)
// 删除索引为3的节点 得到链表 0 => 1 => 2 => 4 => 5
linkNode.delNodeByIndex(3)
// 向索引为2的位置添加一个节点8, 得到链表 0 => 1 => 8 =>2 => 4 => 5
const node8 = new Node(8)
linkNode.addNodeByIndex(2, node8)
// 获取链表最后一个节点 5
const tail = linkNode.getLastNode()
console.log(tail)
// 链表反转, 得到链表 5 => 4 => 2 => 8 => 1 => 0
const head = linkNode.reverse()
console.log(head)
2.3 类的私有属性和Symbol.iterator学习
2.3.1
- ES2022正式为
class
添加了私有属性,方法是在属性名之前使用#
表示 - 私有属性只能在类的内部使用,如果在类的外部使用就会报错
arduino
class TestCount{
#count = 0
addCount() {
this.#count++
}
getCount() {
return this.#count
}
}
const c = new TestCount()
c.addCount()
// 浏览器会抛错错误 Uncaught SyntaxError: Private field '#count' must be declared in an enclosing class
c.#count
2.3.2
ES6规定,只有一个数据结构具有Symbol.iterator属性,就认为是可遍历的,就可以用for...of循环遍历这个数据结构,任意一个对象,我们给他添加Symbol.iterator属性,这个属性是一个函数,返回一个迭代器,我们就可以遍历啦~
但是啥子是迭代器呢 ?
它是一个对象,里面有个next方法,调用迭代器的next方法,返回一个对象,这个对象有2个属性
- value: 当前迭代的值
- done: 是否迭代完成
让我们照葫芦画瓢写一个
javascript
const obj = {
[Symbol.iterator]() {
// 看我看我,我是一个迭代器对象,我有一个next属性,他是一个函数,next函数的返回值是一个对象{value: any, done: true | false}
const iteratorObje = {
value: 1,
next: function () {
// 此处返回一个对象,该对象的value属性包含迭代值,done属性表示是否完成迭代。
// 这里我们让他可以输出1-10的数字
if(this.value <= 10) {
return {
value: this.value++,
done: false
};
}
return {
value: 1,
done: true
};
}
};
return iteratorObje
}
};
for (let num of obj) {
console.log(num) // 输出 1 2 3 4 5 6 7 8 9 10
}
为啥Array、Map、String可以直接使用for of循环呢? 用的时候也没有给这些数据结构加Symbol.iterator属性哒?
因为他们原生就有Symbol.iterator属性,直接在浏览器控制台打印任意一个数组,我们就可以在Array.prototype里面找到Symbol.iterator属性
3. 源码解析
kotlin
class Node {
value;
next;
constructor(value) {
this.value = value;
}
}
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;
}
peek() {
if (!this.#head) {
return;
}
// 返回队首节点值
return this.#head.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;
}
}
// 连续出队队列中的所有元素, 循环结束后,队列将为空
* drain() {
while (this.#head) {
yield this.dequeue();
}
}
}
const queue = new Queue()
queue.enqueue(1)
queue.enqueue(2)
queue.enqueue(3)
queue.enqueue(4)
queue.enqueue(5)
queue.enqueue(6)
for(let q of queue) {
console.log(q) // 1 2 3 4 5 6
}
queue.dequeue()
console.log(queue.size) // 5
// 测试drain
for(const value of queue.drain()) {
console.log('dequeue value', value) // 2 3 4 5 6
}
console.log(queue.size) // 0
4. 总结
平时多用数组来实现队列,链表实现队列也是如此有趣~
如有问题,欢迎指正,thanks