js别再用数组实现队列了,来用链表实现高性能的队列吧

本文旨在通过分析yocto-queue库源码来探究队列的更高性能的实现方式

库的地址:github.com/sindresorhu…

通常,我们在js中用数组来模拟队列的操作,那为什么还要用这个库呢?

const queue = []
queue.push()
queue.shift()

这个库使用链表来模拟队列的操作,同时使用 Symbol.iterator实现自定义迭代器.大大降低了队列的时间复杂度.

数组实现与链表实现的区别

数组:

  • 进行查找操作,对于使用下标进行访问,比如get(index),其时间消耗仅为O(1)。
  • 但查找特定元素get(e),需要花费O(n)的时间,因为需要遍历数组。
  • 新增add,最坏的情况下,在下标0处进行新增add操作,则需要将原有元素全部后移一位,这便意味着开销为O(n)。
  • 对于删除来说,删除下标为0的元素,则需要将数组元素向前移动一个位置,开销也为0(n)。
  • 仅在数组末尾进行插入和删除操作,那么开销仅为O(1)。

链表

  • 当我们需要查找target元素或者根据下标来找元素,查找的开销O(n)。
  • remove方法仅需要修改一个next引用就可以实现,使用一句代码即可A1.next = A2.next;开销仅为O(1),因为不涉及元素的移动。
  • 添加新的节点也仅仅需要修改next指针指向新的节点,开销也仅仅为O(1)。

由此可见,使用链表作为队列的实现方式具有很大的性能优势.

源码分析

请看注释:

class Node {
    // 创建节点类,写上节点属性
	value;
	next;

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

export default class Queue {
        // 创建head和tail头尾指针
	#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;
		}
	}
}

迭代器与生成器

重点看一下最后一个函数:

* [Symbol.iterator]() {
		let current = this.#head;

		while (current) {
			yield current.value;
			current = current.next;
		}
	}

此处做了一个自定义的迭代器,相当于对此class所new出来的实例对象上添加了一个[Symbol.iterator]属性,属性的值为一个生成器函数.

而生成器函数的作用就是返回一个iterator对象,这样就可以通过iterator.next().value方法来遍历指针所指向的值.

如果不用面向对象的写法就是这样的

    function Queue(){
      // 这里相当于 类 里面的constructor函数
    }
    Queue.prototype.xxx=function(){
      // 这里放 类 里面的属性和方法
    }
    var queue = new Queue();
    queue[Symbol.iterator] = function* () {
      let current = this.#head;

      while (current) {
        yield current.value;
        current = current.next;
      }
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值