重学JavaScript数据结构之 “队列”

968 阅读2分钟

1.什么是队列

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

2.队列的方法

在创建一个队列时,首先需要一个用于存储队列中元素的数据结构,可以是数组,像在上一章栈的讲解中实现的那样,但为了写出一个在获取元素时更加高效的数据结构,这里我们将用对象来实现。下面我们创建一个类来表示队列:

class myQueue {
    constructor() {
        this.count = 0;
        this.lowestCount = 0;
        this.queue = {};
    }
}

这里我们定义了count字段来表示队列的大小,而lowestCount字段则用来表示队列头部的索引,用于获取队列里面队头的值,下面是一些队列里面可用的方法:

enqueue(element(s?)):向队列的头部添加一个(或多个)新的项。
dequeue(): 移除队列中的第一个元素,并返回被移除的元素。
peek():返回队列中的第一个元素,队列不做任何变动。
isEmpty():队列是否为空,返回布尔值。
size(): 返回队列包含的元素个数,与数组中的length类似。
clear():清空队列。

现在我们可以这样表示队列:

class myQueue {
    constructor() {
        this.count = 0;
        this.lowestCount = 0;
        this.queue = {};
    }
    enqueue(...elements) { }
    dequeue() { }
    peek() { }
    isEmpty() { }
    size() { }
    clear() {}
}

3.队列的实现

enqueue

enqueue(...elements) {
    if (let i = 0; i < elements.length; i++) {
        this.queue[this.count] = elements[i];
        this.count++;
    }
    return this;
}

dequeue

dequeue() {
    if (this.isEmpty()) {
        return undefined;
    }
    const result = this.queue[this.lowestCount];
    delete this.queue[this.lowestCount];
    this.lowestCount++;
    return result;
}

isEmpty

isEmpty() {
    return this.count === this.lowestCount;
}

size

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

clear

clear() {
    this.queue = {};
    return this;
}

完整代码如下:

class myQueue {
    constructor() {
        this.count = 0;
        this.lowestCount = 0;
        this.queue = {};
    }
    enqueue(...elements) {
        if (let i = 0; i < elements.length; i++) {
            this.queue[this.count] = elements[i];
            this.count++;
        }
        return this;
    }
    dequeue() {
        if (this.isEmpty()) {
            return undefined;
        }
        const result = this.queue[this.lowestCount];
        delete this.queue[this.lowestCount];
        this.lowestCount++;
        return result;
    }
    isEmpty() {
        return this.count === this.lowestCount;
    }
    size() {
        return this.count - this.lowestCount;
    }
    clear() {
        this.queue = {};
        return this;
    }
}

4.队列进阶 => 双端队列

双端队列是一种允许我们同时从前端和后端添加和移除元素的特殊队列。在计算机科学中,双端队列一个常规用途就是用于存储一系列的撤销操作。

1.双端队列的实现

这里直接上代码:

class myDeque {
    constructor() {
        this.count = 0;
        this.lowestCount = 0;
        this.queue = {};
    }
    addFront(element) { 
        if (this.isEmpty()) {
            this.queue[this.count] = element;
        } else if (this.lowestCount > 0) {
            this.lowestCount--;
            this.queue[this.lowestCount] = element;
        } else {
            // this.lowestCount === 0
            for (let i = this.count; i > this.lowestCount ; i--) {
                this.queue[i] = this.queue[i - 1];
            }
            this.queue[this.lowestCount] = element;
            this.count++;
        }
        return this;
    }
    addBack(element) {
        this.queue[this.count] = element;
        this.count++;
        return this;
    }
    removeFront() {
        if (this.isEmpty()) {
            return undefined;
        }
        const resullt = this.queue[this.lowestCount]
        delete this.queue[this.lowestCount]
        this.lowestCount++;
        return resullt;
    }
    removeBack() {
        if (this.isEmpty()) {
            return undefined;
        }
        const resullt = this.queue[this.count - 1]
        delete this.queue[this.count - 1]
        this.count--;
        return resullt;
    }
    peekFront() {
        return this.queue[this.lowestCount]
    }
    peekBack() {
        return this.queue[this.count]
    }
    isEmpty() {
        return this.count === this.lowestCount;
    }
    size() {
        return this.count - this.lowestCount;
    }
    clear() {
        this.queue = {};
        return this;
    }
}

5.队列的应用

下面我们将用队列来实现一个击鼓传花游戏,在这个游戏中,所有人围成一个圈,指定由一个人开始,依次将花传给下一个人,一定时间过后停止,花在谁手上谁出局,剩下的人继续游戏,直到剩下最后一个人为止。

function whoIsLucky(pList, num) {
    const queue = new myQueue();
    for (let i = 0; i < pList.length; i++) {
        queue.enqueue(pList[i]);
    }
    while (queue.size() > 1) {
        for (let i = 0; i < num; i++) {
            // 出队的同时入队,循环队列
            queue.enqueue(queue.dequeue());
        }
        queue.dequeue();
    }
    return {
        winner: queue.peek()
    }
}

接下来是双端队列的应用,我们知道一道经典的算法题,叫做判断一个字符串是否为回文字符串,即一个字符串的正序排列和倒序排列的结果一致,下面我们将用双端队列的思想来解决这个问题:

function charChecker(str) {
    if (Object.prototype.toString.call(str).slice(8, -1) !== 'String') {
        return false;
    }
    const deque = new myDeque();
    let isEquel = str.length > 0;
    for (let i = 0; i < str.length; i++) {
        deque.addBack(str[i]);
    }
    while (deque.size() > 1 && isEquel) {
        isEquel = deque.removeBack() === deque.removeFront();
    }
    return isEquel;
}