数据结构之队列与链表

114 阅读3分钟

我们知道要存储多个元素,数组是最常用的数据结构,但是数组会有一个缺点,数组大小是固定的,要从数组的起点或中间插入或移除项的成本是很高的,因为需要移动元素。这个时候我们可以考虑到链表,链表是存储有序的元素集合并且元素在内存中并不是连续放置的。每个元素由一个存储元素本身的节点和一个指向下一个元素的引用组成。本节我们一起学习yocto-queue

GitHub地址:github.com/sindresorhu…

下图展示了一个链表的结构

epub_26211966_96.jpg 相对于数组,链表的一个好处在于添加或移除元素的时候不需要移动其他元素

yocto-queue

官网对已yocto-queue的介绍是如果你在大型数组上做很多事情,你应该使用这个包而不是Array#push()数组Array#shift(),因为Array#shift()它具有线性时间复杂度 O(n)Queue#dequeue()具有恒定时间复杂度 O(1) 。这对大型阵列产生了巨大的影响。

接下来我们来看看yocto-queue内部是如何实现的

class Node {
	value;//当前元素的值
	next;//指向下一个元素的指针

	constructor(value) {
		this.value = value;
	}
}

在源码里有一个Node类,Node类表示我们想要添加到链表中的项

export default class Queue {
	#head;//指向第一个元素的引用
	#tail;//指向最后一个元素的引用
	#size;//当前元素的个数

	constructor() {
		this.clear();
	}

	enqueue(value) {
                //实例化一个Node
		const node = new Node(value);
                //如果没有#head,那么#head和#tail都是node
                //如果有#head,那么#tail为最后一个节点,即node
		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;
	}
        //生成器函数,可以用for...of遍历
	* [Symbol.iterator]() {
		let current = this.#head;
		while (current) {
			yield current.value;
			current = current.next;
		}
	}
}

因为涉及到数组的push操作和unshift操作所以这里用到队列数据结构,队列是遵循先进先出原则,在尾部添加新元素,在顶部移除元素,其中enqueue方法表示往队列尾部添加一个新元素,dequeue方法表示移除队列的第一项,clear方法表示清空队列,size方法返回当前队列元素个数,Symbol.iterator一个生成器函数,只要在内部有这个函数,那么就能用for...of遍历

总结

  1. 对队列与链表的使用
  2. Symbol.iterator的使用场景