[路飞]前端算法——数据结构篇(二、队列): 三种队列实现

176 阅读3分钟

「这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战

前言

前端算法系列是我对算法学习的一个记录, 主要从常见算法数据结构算法思维常用技巧几个方面剖析学习算法知识, 通过LeetCode平台实现刻意练习, 通过掘金和B站的输出来实践费曼学习法, 我会在后续不断更新优质内容并同步更新到掘金、B站和Github, 以记录学习算法的完整过程, 欢迎大家多多交流点赞收藏, 让我们共同进步, daydayup👊

目录地址:目录篇

相关代码地址: Github

相关视频地址: 哔哩哔哩-百日算法系列

一、循环队列

LeetCode: 622. 设计循环队列

循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。

循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。

image.png

/**
 * @param {number} k
 */
 var MyCircularQueue = function(k) {
    this.k = k
    this.cnt = 0
    this.head = 0
    this.tail = 0
    this._v = new Array(k)
};

/** 
 * @param {number} value
 * @return {boolean}
 */
MyCircularQueue.prototype.enQueue = function(value) {
    // 入队
    if (this.isFull()) return false
    this._v[this.tail] = value
    this.tail = (this.tail + 1) % this.k
    this.cnt++
    return true
};

/**
 * @return {boolean}
 */
MyCircularQueue.prototype.deQueue = function() {
    // 出队
    if (this.isEmpty()) return false
    this.head = (this.head + 1) % this.k
    this.cnt--
    return true
};

/**
 * @return {number}
 */
MyCircularQueue.prototype.Front = function() {
    // 队首
    if (this.isEmpty()) return -1
    return this._v[this.head]
};

/**
 * @return {number}
 */
MyCircularQueue.prototype.Rear = function() {
    // 队尾
    if (this.isEmpty()) return -1
    return this._v[(this.tail - 1 + this.k) % this.k]
};

/**
 * @return {boolean}
 */
MyCircularQueue.prototype.isEmpty = function() {
    // 判空
    return !this.cnt
};

/**
 * @return {boolean}
 */
MyCircularQueue.prototype.isFull = function() {
    // 判满
    return this.cnt === this.k
};

/**
 * Your MyCircularQueue object will be instantiated and called as such:
 * var obj = new MyCircularQueue(k)
 * var param_1 = obj.enQueue(value)
 * var param_2 = obj.deQueue()
 * var param_3 = obj.Front()
 * var param_4 = obj.Rear()
 * var param_5 = obj.isEmpty()
 * var param_6 = obj.isFull()
 */

二、循环双端队列

LeetCode: 641. 设计循环双端队列

双端队列指即可以从头部出队、也可以从头部入队, 即可以 尾部出队, 也可以 尾部入队

/**
 * @param {number} k
 */
 var MyCircularDeque = function(k) {
    this.k = k
    this.cnt = 0
    this.head = 0
    this.tail = 0
    this._v = new Array(k)
};

/** 
 * @param {number} value
 * @return {boolean}
 */
MyCircularDeque.prototype.insertFront = function(value) {
    // 队首入队
    if (this.isFull()) return false
    this.head = (this.head - 1 + this.k) % this.k
    this._v[this.head] = value
    this.cnt++
    return true
};

/** 
 * @param {number} value
 * @return {boolean}
 */
MyCircularDeque.prototype.insertLast = function(value) {
    // 队尾入队
    if (this.isFull()) return false
    this._v[this.tail] = value
    this.tail = (this.tail + 1) % this.k
    this.cnt++
    return true
};

/**
 * @return {boolean}
 */
MyCircularDeque.prototype.deleteFront = function() {
    // 队首出队
    if (this.isEmpty()) return false
    this.head = (this.head + 1) % this.k
    this.cnt--
    return true
};

/**
 * @return {boolean}
 */
MyCircularDeque.prototype.deleteLast = function() {
    // 队尾出队
    if (this.isEmpty()) return false
    this.tail = (this.tail - 1 + this.k) % this.k
    this.cnt--
    return true
};

/**
 * @return {number}
 */
MyCircularDeque.prototype.getFront = function() {
    // 队首
    if (this.isEmpty()) return -1
    return this._v[this.head]
};

/**
 * @return {number}
 */
MyCircularDeque.prototype.getRear = function() {
    // 队尾
    if (this.isEmpty()) return -1
    return this._v[(this.tail - 1 + this.k) % this.k]
};

/**
 * @return {boolean}
 */
MyCircularDeque.prototype.isEmpty = function() {
    // 判空
    return !this.cnt
};

/**
 * @return {boolean}
 */
MyCircularDeque.prototype.isFull = function() {
    // 判满
    return this.cnt === this.k
};

/**
 * Your MyCircularDeque object will be instantiated and called as such:
 * var obj = new MyCircularDeque(k)
 * var param_1 = obj.insertFront(value)
 * var param_2 = obj.insertLast(value)
 * var param_3 = obj.deleteFront()
 * var param_4 = obj.deleteLast()
 * var param_5 = obj.getFront()
 * var param_6 = obj.getRear()
 * var param_7 = obj.isEmpty()
 * var param_8 = obj.isFull()
 */

三、前中后队列

LeetCode: 1670. 设计前中后队列

// 一、使用链表构造一个双向队列
// 二、维护一前一后两个队列、当插入中间时插入到前面队列的尾端
// 三、如果两个队列的差值过大、进行修复

// 链表
var Node = function(val, next, pre) {
    this.val = val || 0
    this.next = next || null
    this.pre = pre || null
}

Node.prototype.insertPre = function(val) {
    let pre = this.pre
    pre.next = new Node(val, this, pre)
    this.pre = pre.next
    return true
}

Node.prototype.insertNext = function(val) {
    let next = this.next
    this.next = new Node(val, next, this)
    next.pre = this.next
    return true
}

Node.prototype.delPre = function() {
    if (!this.pre) return
    let pre = this.pre
    let lastPre = pre.pre
    if (lastPre) {
        lastPre.next = this
        this.pre = lastPre
    } else {
        this.pre = null
    }
    return true
}

Node.prototype.delNext = function() {
    if (!this.next) return
    let next = this.next
    let lastNext = next.next
    if (lastNext) {
        this.next = lastNext
        lastNext.pre = this
    } else {
        this.next = null
    }
    return true
}

// 双向队列
var Queue = function() {
    this.cnt = 0
    this.head = new Node(0)
    this.tail = new Node(0)
    this.head.next = this.tail
    this.tail.pre = this.head
}

Queue.prototype.pushBack = function(val) {
    this.tail.insertPre(val)
    this.cnt++
}

Queue.prototype.pushFront = function(val) {
    this.head.insertNext(val)
    this.cnt++
}

Queue.prototype.popBack = function() {
    if (this.isEmpty()) return
    let val = this.tail.pre.val
    this.tail.delPre()
    this.cnt--
    return val
}

Queue.prototype.popFront = function() {
    if (this.isEmpty()) return
    let val = this.head.next.val
    this.head.delNext()
    this.cnt--
    return val
}

Queue.prototype.back = function() {
    return this.tail.pre.val
}

Queue.prototype.front = function() {
    return this.head.next.val 
}

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

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

// 前中后队列
var FrontMiddleBackQueue = function() {
    this.last = new Queue()
    this.next = new Queue()
};

/** 
 * @desc 平衡前后两个队列的数量
 * @return {void}
 */
FrontMiddleBackQueue.prototype.optimization = function() {
    if(this.last.size() < this.next.size()) {
        this.last.pushBack(this.next.front())
        this.next.popFront()
    } else if(this.last.size() === this.next.size() + 2) {
        this.next.pushFront(this.last.back())
        this.last.popBack()
    }
};

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

/** 
 * @param {number} val
 * @return {void}
 */
FrontMiddleBackQueue.prototype.pushMiddle = function(val) {
    if (this.last.size() > this.next.size()) {  
        this.next.pushFront(this.last.back())
        this.last.popBack()
    }
    this.last.pushBack(val)
    this.optimization()
};

/** 
 * @param {number} val
 * @return {void}
 */
FrontMiddleBackQueue.prototype.pushBack = function(val) {
    this.next.pushBack(val)
    this.optimization()
};

/**
 * @return {number}
 */
FrontMiddleBackQueue.prototype.popFront = function() {
    if (!this.last.size()) return -1
    let val = this.last.popFront()
    this.optimization()
    return val
};

/**
 * @return {number}
 */
FrontMiddleBackQueue.prototype.popMiddle = function() {
    if (!this.last.size()) return -1
    let val = this.last.popBack()
    this.optimization()
    return val
};

/**
 * @return {number}
 */
FrontMiddleBackQueue.prototype.popBack = function() {
    if (this.next.size()) {
        let val = this.next.popBack()
        this.optimization()
        return val
    } else if (this.last.size()) {
        let val = this.last.popBack()
        this.optimization()
        return val
    } else {
        return -1
    }
};

/**
 * Your FrontMiddleBackQueue object will be instantiated and called as such:
 * var obj = new FrontMiddleBackQueue()
 * obj.pushFront(val)
 * obj.pushMiddle(val)
 * obj.pushBack(val)
 * var param_4 = obj.popFront()
 * var param_5 = obj.popMiddle()
 * var param_6 = obj.popBack()
 */