队列和双端队列

本篇博客的所有代码都整理到自己的github库中,以便大家查看及学习。

本篇博客主要是从以下几点展开

  1. 队列数据结构
  2. 双端数列数据结构
  3. 向队列和双端队列增加元素
  4. 从队列和双端队列中删除元素
  5. 经典算法

1. 队列数据结构

队列遵循先进先出(FIFO)原则的一组有序的项。队列在尾部添加元素,在头部移除元素,最先添加的元素一定是放在队列的末尾。

队列

队列的例子在生活中很常见,在排队买票,排队上车等等

2. 创建队列

接下来我们通过创建自己的类表示队列,然后声明以下方法

  • enqueue(elements): 向尾部追加元素(追加新的项)
  • dequeue(): 移除队列中的第一项,并返回移除的元素
  • peek() : 返回队列中的第一项(并不会对该项有任何操作)
  • isEmpty() : 判断队列是否为空
  • size() : 返回队列中包含的元素的个数
  • clear() : 清空队列
  • toString() : 转为字符串的方法
class Queue{
    constructor(){
        this.items = {} // 根据上篇文章《栈》的理解,这次我们也是用数组的方式来放队列中的各元素
        this.count = 0 // 队列中各项元素的个数
        this.lowestCount = 0 // 用来帮助我们追踪第一个元素的变量
    }
    enqueue(elem){
        this.items[this.count] = elem //将队列中的最后一个位置设置为追加进来的元素
        this.count++
    }
    dequeue(){
        if (this.isEmpty()) {
            return ''
        }
        const result = this.items[this.lowestCount]
        delete this.items[this.lowestCount]
        this.lowestCount++
        return result
    }
    peek(){
        if (this.isEmpty()) {
            return ''
        }
        return this.items[this.lowestCount] 
    }
    isEmpty(){
        return this.size() === 0
    }
    size(){
        return this.count - this.lowestCount
    }
    clear(){
        this.items = {}
        this.count = 0
        this.lowestCount = 0
    }
    toString(){
        if (this.isEmpty()) {
            return ''
        }
        let result = this.items[this.lowestCount]
        
        for (const k in this.items) {
            if (this.items[k] && this.items[k] !== this.items[this.lowestCount]) {
                result = `${result},${this.items[k]}` 
            }
        }
        return result
    }

}

var queue = new Queue()
queue.enqueue(1)
queue.enqueue(2)
queue.enqueue(3)
queue.enqueue(4)
console.log(queue) // Queue { items: [ 1, 2, 3, 4 ], count: 4, lowestCount: 0 }
console.log(queue.size()) // 4
console.log(queue.isEmpty()) // false
console.log(queue.dequeue()) // 1
console.log(queue) // Queue { items: [ <1 empty item>, 2, 3, 4 ], count: 4, lowestCount: 1 }
console.log(queue.size())
console.log(queue.peek()) // 2
console.log(queue) // Queue { items: [ <1 empty item>, 2, 3, 4 ], count: 4, lowestCount: 1 }
console.log(queue.toString()) // 2,3,4

3. 双端队列数据结构

双端队列(deque,或称 double-end queue) 是一种允许我们同时从前端和后端添加和移除元素的特殊队列。
最常见的生活中的例子就是大家排队买票,当第一个人买完票后,可能会再次返回问售票员一些问题,这个时候他就不用去排队,而排在队尾的人可能就因为赶时间,提前离开队伍。

双端队列最常见的一个应用就是存储一系列撤销操作

4. 创建 Deque 类

和之前一样,我们也是在构造函数中声明一些双端队列内部的属性及方法,isEmpty,clear,size,toString
接下来定义一些对外的方法

  • addFront(element) : 向双端队列前面插入新的元素
  • addBack(element) : 向双端队列后面追加新的元素
  • removeFront() : 移除双端队列前面的元素,并返回移除的元素
  • removeBack() : 移除双端队列后面的元素,并返回移除的元素
  • peekFront() : 返回双端队列中的第一项元素
  • peekBack() : 返回双端队列中的最后一项元素
class Deque{
    constructor(){
        this.items = {}
        this.count = 0
        this.lowestCount = 0
    }
    isEmpty(){
        return this.count === 0
    }
    clear(){
        this.items = {}
        this.count = 0
        this.lowestCount = 0
    }
    size(){
        return this.count - this.lowestCount
    }
    addFront(elem){
        
        if (this.isEmpty()) {
            this.addBack(elem)
        } else if (this.lowestCount > 0) {
            this.lowestCount --
            this.items[this.lowestCount] = elem
        } else {
            for (let i = this.count; i > 0; i--) {
              this.items[i] = this.items[i - 1]
            }
            this.count ++
            this.lowestCount = 0  
            this.items[0] = elem
        }
    }
    addBack(elem){
        this.items[this.count] = elem
        this.count++
    }
    removeFront(){
        const result = this.items[this.lowestCount]
        this.items[this.lowestCount] = undefined
        this.lowestCount ++
        return result
    }
    removeBack(){
        const result = this.items[this.count]
        this.count --
        return result
    }
    peekFront(){
        return this.items[this.lowestCount]
    }
    peekBack(){
        return this.items[this.count]
    }
    toString(){
        if (this.isEmpty()) {
            return ''
        }
        let result = this.items[this.lowestCount]
        for (const k in this.items) {
            if (this.items[k] !== result && this.items[k]) {
                result = `${result},${this.items[k]}`
            }
        }
        return result
    }
}

var deque = new Deque()
deque.addBack(1)
deque.addFront(0)
deque.addBack('Alice')
deque.addFront('JD')
console.log(deque) //Deque {items: { '0': 'JD', '1': 0, '2': 1, '3': 'Alice' },count: 4,lowestCount: 0 }
console.log(deque.removeFront()) // JD
console.log(deque) //Deque {items: { '0': undefined, '1': 0, '2': 1, '3': 'Alice' },count: 4, lowestCount: 1}

console.log(deque.size()) // 3
console.log(deque.toString()) // 0,1,Alice

5. 经典算法

1. 击鼓传花

在思考这个题目的时候我们先了解一下循环队列。而循环队列最常见的例子就是击鼓传花。击鼓传花就是一群孩子们围成圈,将花传递给旁边的人,当在某一时刻,传花停止时,花在谁的手里谁就会被淘汰。重复这个过程,直到剩下最后一个孩子

function hotPotato(elementList,num){
    const queue = new Queue()
    const eliminatedList = [] // 淘汰的元素

    for (let i = 0; i < elementList.length; i++) {
        queue.enqueue(elementList[i])
    }

    while (queue.size() > 1) {
        for (let i = 0; i < num; i++) {
            queue.enqueue(queue.dequeue())
        }
        eliminatedList.push(queue.dequeue())
    }
    return {
        eliminated:eliminatedList,
        winner:queue.dequeue()
    }
}

var names = ['John','Jack','Calmia','Alice','Carl']
const result = hotPotato(names,7)
// console.log(result) // { eliminated: [ 'Calmia', 'Jack', 'Carl', 'Alice' ], winner: 'John' }
result.eliminated.forEach(name => {
    console.log(`${name}在游戏中被淘汰`)
})
console.log(`胜利者是:${result.winner}`)
// Calmia在游戏中被淘汰
// Jack在游戏中被淘汰
// Carl在游戏中被淘汰
// Alice在游戏中被淘汰
// 胜利者是:John

上述的代码不好理解,这里画一幅图,便于理解;图中只表示了第一次循环的状态

xuxieban.png

2. 回文检查器

最简单的方式就是将字符串反向排列后,检查它和原字符串是否一样,若相同,则为一个回文字符串

function palindromeChecker(string) {
    if (!string) { // 判空
        return false 
    }
    let deque = new Deque()
    const str = string.toLowerCase() // 转为了小写
    const tempStr = str.split(' ').join() // 去除字符串中的空格
    let isEqual = true 
    let firstChar; let lastChar 

    // 将字符串中的第一个字符串放到最后
    for (let i = 0; i < tempStr.length; i++) { 
        deque.addBack(tempStr.charAt(i))
    }
    // 比较每次双端队列移除的项是否相等,相等则继续循环,直到size为1,不想等则直接退出循环
    while (deque.size() > 1 && isEqual) {
        firstChar = deque.removeFront()
        lastChar = deque.removeBack()
        if (firstChar !== lastChar) {
            isEqual = false     
        }
    }
    return isEqual
}
posted @ 2022-03-01 11:02  前端加油站  阅读(93)  评论(0编辑  收藏  举报