js实现队列结构(3种实现方式)

470 阅读1分钟

队列

队列是遵循先进先出(FIFO,也称为先来先服务)原则的一组有序的项。队列在尾部添加新元素,并从顶部移除元素。最新添加的元素必须排在队列的末尾。

1. 基于数组实现队列

class Queue {
    constructor() {
        this.items = [];
    }
   
    enqueue(element) {
       this.items.push(element);
    }

    dequeue() {
        // shift在数组头部删除元素,后面的元素都会向前移动一位,性能较差
        return this.items.shift();
    }

    peek() {
        return this.items[0];
    }

    isEmpty() {
        return this.size() === 0;
    }

    size() {
        return this.items.length;
    }

    toString() {
        if(this.isEmpty()) return '';
        let objString = `${this.items[0]}`;
        for (let i = 1; i < this.items.length; i++) {
            objString = `${objString} , ${this.items[i]}`;
        }
        return objString;
    }
}

2. 基于对象实现队列

class Queue {
    constructor() {
        this.items = {}; // 保存元素
        this.count = 0;  // 记录元素的位置
        this.minCount = 0; // 记录元素的起始位置
    }
    // 入队
    enqueue(val) {
      if (val == null) return;  
      this.items[this.count++] = val;
    }
    // 出队
    dequeue() {
       if (this.isEmpty()) return undefined;
       const val = this.items[this.minCount];
       delete this.items[this.minCount];
       this.minCount++; // 起始位置向后移动一位
       return val; 
    }

    peek() {
        if (this.isEmpty()) return undefined;
        return this.items[this.minCount]
    }

    size() {
        return this.count - this.minCount;
    }

    isEmpty() {
        return this.size() === 0;
    }

    clear() {
        this.items = {};
        this.minCount = 0;
        this.count = 0;
    }

    toString() {
        if (this.isEmpty()) return '';
        let str = `${this.items[this.minCount]}`;
        for (let i = this.minCount + 1; i < this.count; i++) {
           str = `${str}, ${this.items[i]}`;
        }
        return str;
    }
}

3. 基于链表实现队列

   // 链表节点类 
   class ListNode {
        constructor(val) {
            this.val = val;
            this.next = null; // 指向下一个节点,默认为空
        }
   }

class Queue {
    constructor() {
        this.head = null; // 指向队列头节点
        this.tail = null;  // 指向队列尾节点
        this.len = 0;  // 记录队列大小,避免遍历链表获取,否则时间复杂度过高
    }

    enqueue(val) {
      const node = new ListNode(val);  
      // 队列为空  
      if (this.head == null) {
        // 此时头节点和尾节点指向同一个节点
        this.head = node;
        this.tail = node;
      } else {
        // 在队尾添加节点
        this.tail.next = node;
        // 将tail指向新加的节点,也就是最后一个节点
        this.tail = this.tail.next;
      } 
      // 队列大小增加1
      this.len++;
    }

    dequeue() {
        // 队列为空
        if (this.isEmpty()) return null;
        // 取头节点,队列要从头部删除元素
        const node = this.head;
        // 头节点的值
        const val = node.val;
        // 头指针指向下一个节点,就删除了上一个节点
        this.head = this.head.next;
        // 队列大小减一
        this.len--;

        return val;
    }

    peek() {
        if(this.isEmpty()) return null;
        return this.head.val;
    }

    size() {
        return this.len;
    }

    isEmpty() {
        return this.size() === 0;
    }

    toString() {
        if(this.isEmpty()) return '';
        let str = `${this.head.val}`;
        let node = this.head.next;
        while(node) {
           str = `${str}, ${node.val}`;
           node = node.next;
        }
        return str;
    }
}

        // 测试代码
        const queue = new Queue();
        queue.enqueue(10)
        queue.enqueue(20)
        queue.enqueue(30)
        console.info('size1:',queue.size());
        console.info('peek1:',queue.peek());
        console.info('toString1:',queue.toString());
        queue.dequeue()
        console.info('size2:',queue.size());
        console.info('peek2:',queue.peek());
        console.info('toString2:',queue.toString());
        queue.dequeue()
        console.info('size3:',queue.size());
        console.info('peek3:',queue.peek());
        console.info('toString3:',queue.toString());
        queue.dequeue()
        console.info('size4:',queue.size());
        console.info('peek4:',queue.peek());
        console.info('toString4:',queue.toString());

结果:

image.png

三种实现方式的性能测试:

   <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>队列三种实现方式性能测试</title>
    </head>
    <body>
        <script src="./queue_with_array.js"></script>
        <script src="./queue_with_object.js"></script>
        <script src="./queue_with_listnode.js"></script>
        <script>
            const q1 = new Queue_arr();
            console.time('queue with array')
            for (let i = 0; i < 10 * 10000; i++) {
               q1.enqueue(i)
            }
            for (let i = 0; i < 10 * 10000; i++) {
               q1.dequeue()
            }
            console.timeEnd('queue with array')

            const q2 = new Queue_obj();
            console.time('queue with object')
            for (let i = 0; i < 10 * 10000; i++) {
               q2.enqueue(i)
            }
            for (let i = 0; i < 10 * 10000; i++) {
               q2.dequeue()
            }
            console.timeEnd('queue with object')

            const q3 = new Queue_list();
            console.time('queue with list')
            for (let i = 0; i < 10 * 10000; i++) {
               q3.enqueue(i)
            }
            for (let i = 0; i < 10 * 10000; i++) {
               q3.dequeue()
            }
            console.timeEnd('queue with list')
        </script>
    </body>
    </html>

测试结果:

image.png

由此可见,基于数组的实现性能最差,原因是shift()时间复杂度为O(n),故而影响了性能。但是实际工作中如果只是想使用队列的逻辑功能(先进先出),还是推荐使用数组的。