我们为啥学队列?用js设计前中后队列 - (链表+数组两种实现方式)

322 阅读3分钟

我们为什么学算法,学了队列用在哪?js的数组就能实现队列的所有功能了,我还要实现这个干嘛?

这就是我为什么写这题,我觉得这题就很能解释上面的问题。

  • 学了队列用在哪?

队列是一个思想,用户还是很广泛的。我最近工作中在做链路监控,就使用了队列的特性。在一个业务流程中,前端总会遇到报错,就需要上报错误。为了不影响主线程的执行,在关键节点我将收集用户信息,添加到队列中,在线程空闲时上报。这就用到了队列的思想,什么时候取队头信息,什么时候要将关键信息插入队头优先上报,队列阻塞了怎么办等等。。这里就不展开将了。

  • 为什么要实现前中后队列?

这其实是因为我们在js中一般就使用数组去模拟队列,如果遇到往中间插入数组怎么办?一般是slice一半的数组插入再合并。取数据就是算中间位置再拆分数组再合并。这道题我本来也是这么写的。后来想想,其实我们何不把一个有中间属性的队列变成两个只有前后属性的队列呢?往中间插入或者取出数据其实就是对两个队列的首位操作插入/取出操作。一个大的数据结构的问题其实是很多很基础的小数据结构组合而成。这就是我写这道题想表达的初衷。

好了,现在直接看题目吧~ LeetCode第1670题

1670. 设计前中后队列

image.png

image.png

上面我们说到,使用两个具有首尾操作功能的队列去模拟一个新的队列,该队列的中部操作就是queue1,queue2的首尾操作组成。

首先先实现一个具有首尾操作功能的队列。下面附有链表和数组两种方式实现的队列。

  • 链表实现queue
var ListNode = function (val, prev, next) {
    this.val = val;
    this.prev = prev || null;
    this.next = next || null;
}

var Queue = function () {
    this.size = 0;
    this.head = this.tail = null;
}

Queue.prototype.getSize = function () {
    return this.size;
}

Queue.prototype.isEmpty = function () {
    return !this.getSize()
}

Queue.prototype.push_back = function (val) {
    const node = new ListNode(val);
    if (this.isEmpty()) {
        this.head = this.tail = node;
    } else {
        this.tail.next = node;
        node.prev = this.tail;
        this.tail = node;
    }
    this.size++;
}

Queue.prototype.push_front = function (val) {
    const node = new ListNode(val);
    if (this.isEmpty()) {
        this.head = this.tail = node;
    } else {
        node.next = this.head;
        this.head.prev = node;
        this.head = node;
    }
    this.size++;
}

Queue.prototype.pop_back = function () {
    if (this.isEmpty()) return -1;
    const ret = this.tail.val;
    this.tail = this.tail.prev;
    if (this.tail) {
        this.tail.next = null;
    } else {
        this.head = null;
    }
    this.size--;
    return ret;
}

Queue.prototype.pop_front = function () {
    if (this.isEmpty()) return -1;
    const ret = this.head.val;
    if (this.getSize() === 1) {
        this.head = this.tail = null;
    } else {
        this.head = this.head.next;
        this.head.prev = null;
    }
    this.size--;
    return ret;
}

  • 数组实现queue
var Queue = function () {
    this.queue = new Array();
    this.size = 0;
}

Queue.prototype.push_front = function (val) {
    this.queue.unshift(val);
    this.size++;
}

Queue.prototype.push_back = function (val) {
    this.queue.push(val);
    this.size++;
}

Queue.prototype.pop_front = function () {
    if (this.isEmpty()) return -1;
    const ret = this.queue.shift();
    this.size--;
    return ret;
}

Queue.prototype.pop_back = function () {
    if (this.isEmpty()) return -1;
    const ret = this.queue.pop();
    this.size--;
    return ret;
}

Queue.prototype.getSize = function () {
    return this.size;
}

Queue.prototype.isEmpty = function () {
    return !this.getSize()
}

然后就是合成前中后队列的过程了,该过程只要注意middle插入和取出的时候,前后队列的长度问题。我们每次操作后要保持两队列平衡,使queue1长度永远比queue2长度大1或者相等。即队列为空情况下优先插入queue1,队首取出数据后要将queue2的首位数据放到queue1的尾部。若queue1 - queue2 === 1时,再popBack,需要在操作后将queue1的队尾数据移动到queue2的队首。

FrontMiddleBackQueue.prototype.balance = function () {
    if (this.q1.getSize() - this.q2.getSize() === 2) {
        this.q2.push_front(this.q1.pop_back())
    } else if (this.q2.getSize() - this.q1.getSize() === 1) {
        this.q1.push_back(this.q2.pop_front())
    }
}

完整代码如下:

var FrontMiddleBackQueue = function () {
    this.q1 = new Queue();
    this.q2 = new Queue();
};

/** 
 * @param {number} val
 * @return {void}
 */
FrontMiddleBackQueue.prototype.pushFront = function (val) {
    this.q1.push_front(val)
    this.balance()
};

/** 
 * @param {number} val
 * @return {void}
 */
FrontMiddleBackQueue.prototype.pushMiddle = function (val) {
    if (this.q1.getSize() > this.q2.getSize()) {
        this.q2.push_front(this.q1.pop_back())
    }
    this.q1.push_back(val)
};

/** 
 * @param {number} val
 * @return {void}
 */
FrontMiddleBackQueue.prototype.pushBack = function (val) {
    if (this.q1.isEmpty()) {
        this.q1.push_back(val);
        return
    }
    this.q2.push_back(val);
    this.balance()
};

/**
 * @return {number}
 */
FrontMiddleBackQueue.prototype.popFront = function () {
    const ret = this.q1.pop_front();
    this.balance()
    return ret;
};

/**
 * @return {number}
 */
FrontMiddleBackQueue.prototype.popMiddle = function () {
    const ret = this.q1.pop_back()
    if (this.q1.getSize() !== this.q2.getSize()) {
        this.balance()
    }
    return ret;
};

/**
 * @return {number}
 */
FrontMiddleBackQueue.prototype.popBack = function () {
    if (this.q2.isEmpty()) {
        return this.q1.pop_back();
    }
    const ret = this.q2.pop_back();
    this.balance();
    return ret;
};

FrontMiddleBackQueue.prototype.balance = function () {
    if (this.q1.getSize() - this.q2.getSize() === 2) {
        this.q2.push_front(this.q1.pop_back())
    } else if (this.q2.getSize() - this.q1.getSize() === 1) {
        this.q1.push_back(this.q2.pop_front())
    }
}