从yocto-queue.js中67行代码认识队列和链表结构

227 阅读4分钟

本文参加了由公众号@若川视野 发起的每周源码共读活动,  点击了解详情一起参与。

这是源码共读的第32期,| 链接:yocto-queue 队列链表

源码地址

yocto-queue

为什么使用链表结构

当你的数组在处理大型数组的时候,shift或者是splice的时候,数组它会将被移除的元素后面的所有元素依次向前推进一位,这是非常消耗性能的,而链表是不存在这个问题

  1. 数组的创建需要申请一段连续的内存空间,并且通常它的大小固定
  2. 数组开头或者中间位置 插入数据成本很高,因为需要对大量元素进行位移
  3. 链表中的元素在内存不必是连续的空间, 内存灵活管理
  4. 链表每个元素由一个存储元素本身的节点 和 指向下一个元素的引用 组成
  5. 链表在插入和删除数据时,时间复杂度可以达到O(1) 效率更高

image.png

也就是说,如果你移除链表的某一个元素时,只需要将它的上一个元素重新指向这个元素的下一个元素即可。在表现上就不需要像数组那样需要将后面的元素挨个一一向前赋值一次

什么是队列

一种受限的线性结构,通常简称为Queue

  1. 它只允许在数据序列的前端进行删除, 而在尾部进行插入操作
  2. 遵循先进先出原则,即先进来的元素在移除元素时先出去

image.png

源码阅读

class Node {
	value;
	next;

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

	constructor() {
		this.clear();
	}
	//队列压入一个元素到头部
	enqueue(value) {
		const node = new Node(value);

		//非第一次添加值的时候,因为#head中最后一个对象的next和 #tail是同一个对象
		//这里修改this.#tail.next想当于同时修改最后一个对象的next
		//最后再改变#tail即可,此时最后一个对象的next 和# tail指向又是同一个对象
		if (this.#head) {
			this.#tail.next = node;
			this.#tail = node;
		} else {
			//第一次增加值的时候 #head 和#tail 都指向了node
			//所以#head和#tail之间是具有一个引用关系
			this.#head = node;
			this.#tail = node;
		}

		this.#size++;
	}
        //队列移除一个元素
	dequeue() {
		const current = this.#head;
		if (!current) {
			return;
		}
		//遵循先进先出的原则,#head直接等于#head.next,原先的对象失去引用关系会被垃圾回收
		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;
		}
	}
}

Node

用于生成链表节点的类,具有next属性和value属性

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

自己重写一遍

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._head.next = node
			this._tail = node; //然后再修改_tail, 此时tail就等同于 this._head.next
			//下次进来修改this._tail.next就等于修改head中最后一个节点的next属性
			//然后赋值_tail 为node , 那this._tail 就等同于this._head.next
		} else {
			this._head = node;
			this._tail = node;
		}
		this._size++; //增加队列长度
	}

	//压出一个头部元素
	dequeue() {
		let value = this._head?.value
		if (!value) return; //头部不存在元素直接结束
		
		this._head = this._head.next;
		//直接让_head.next变成_head,此时原来_head失去引用关系会被回收

		this._size--; //队列长度减一

		//返回被移除元素的value
		return value;
	}

	clear() {
		this._head = undefined;
		this._tail = undefined;
		this._size = 0;
	}
	// es6 生成器语法糖 本质还是 迭代器函数的实现
	// *[Symbol.iterator]() {
	// 	let current = this._head;
	// 	//只要next中有元素则会一直遍历下去
	// 	while (current) {
	// 		yield current.value;
	// 		current = current.next;
	// 	}
	// }
	//自己实现迭代器
	[Symbol.iterator]() {
                let res = this._head;
                return {
                        next: () => {
                                if (res) {
                                        const value = res.value;
                                        res = res.next;
                                        return { done: false, value };
                                } else {
                                        return { done: true, value: undefined };
                                }
                        },
                        //迭代器被停止的监听器
                        return: () => {
                                console.log("迭代器提前终止");
                                return { done: true, value: undefined };
                        },
                };
        }

	get size() {
		return this._size;
	}
}

关于迭代器

在另一篇文章有介绍过关于迭代器以及迭代协议

编写单元测试

import test from "ava";
import Queue from "./use.js"; //重写的js文件

test(".enqueue()", (t) => {
	const queue = new Queue();
	queue.enqueue("🦄");
	t.is(queue.dequeue(), "🦄");
	queue.enqueue("🌈");
	queue.enqueue("❤️");
	t.deepEqual([...queue], ["🌈", "❤️"]);
	t.is(queue.dequeue(), "🌈");
	t.is(queue.dequeue(), "❤️");
});

test(".dequeue()", (t) => {
	const queue = new Queue();
	t.is(queue.dequeue(), undefined);
	t.is(queue.dequeue(), undefined);
	queue.enqueue("🦄");
	t.is(queue.dequeue(), "🦄");
	t.is(queue.dequeue(), undefined);
});

test(".clear()", (t) => {
	const queue = new Queue();
	queue.clear();
	queue.enqueue(1);
	queue.clear();
	t.is(queue.size, 0);
	queue.enqueue(1);
	queue.enqueue(2);
	queue.enqueue(3);
	queue.clear();
	t.is(queue.size, 0);
});

test(".size", (t) => {
	const queue = new Queue();
	t.is(queue.size, 0);
	queue.clear();
	t.is(queue.size, 0);
	queue.enqueue("🦄");
	t.is(queue.size, 1);
	queue.enqueue("🦄");
	t.is(queue.size, 2);
	queue.dequeue();
	t.is(queue.size, 1);
	queue.dequeue();
	t.is(queue.size, 0);
	queue.dequeue();
	t.is(queue.size, 0);
});

test("iterable", (t) => {
	const queue = new Queue();
	queue.enqueue("🦄");
	queue.enqueue("🌈");
	t.deepEqual([...queue], ["🦄", "🌈"]);
});

总结

  1. 认识队列以及链表结构
  2. 自己实现Queue类,迭代器,加深对象的引用赋值使用
  3. 使用ava编写单元测试