JS算法系统学习第四章:队列结构

上一章:# JS算法系统学习第三章:栈结构

1. 队列是是什么?

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。

队尾:进行插入操作的端

队头:进行删除操作的端

空队列:队列中没有元素时

队列的数据元素又称为队列元素。

入队:在队列中插入一个队列元素

出队:从队列中删除一个队列元素

因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO - first in first out)线性表。

2. 队列结构的封装

第一种:与栈结构的封装类似

js 复制代码
class Queue {
   // constructor(){
  //   this.items = [];
  // }
  // items = []; // items 可以放在 constructor中,也可以直接提到外面来
  // _items = []; // 给items加下划线_,表示是私有的。君子协定,大家公认的,但其实外部依旧可以访问修改的。
  #items = []; // es13 出现 js 默认支持的私有属性,加井号 # 表示私有属性。

  // 出队
  dequeue(){
    // 用起来方便,但JS处理起来是 性能不高,效率低下 的(因为JS需要将队头弹出,然后逐步将剩余元素往前挪)。对于大数据就更...
    this.#items.shift(); 
    // 不符合 "好的数据结构是为了提高效率的" 的原则
    // 优化看后续代码
  }

  // 入队
  enqueue(data){
    this.#items.push(data);
  }

  // 返回队头
  front(){
    return this.#items.at(0);
  }

  isEmpty(){
    return this.#items.length === 0;
  }

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

  clear(){
    this.#items = []
  }

  toString(){
    return this.#items.join(" ")
  }
}

let queue = new Queue()

出队尝试一:使用 delete 方法 (此方法并不可行,因为数组会变得丑陋)

出队尝试二:使用对象结构(可行,结构好看,空间占用小,性能更好)

js 复制代码
class Queue {
  #items = {}
  #lowCount = 0 // 记录队头索引值
  #count = 0 // 插入数量  (size = lowCount - count) 思路与数据库索引其实是一致的

  // 出队
  dequeue(){
    // 假如先进行删除,要保证没有造成影响
    if(this.isEmpty()){
      // return undefined
      return
    }
    let res = this.#items[this.#lowCount]
    delete this.#items[this.#lowCount]
    this.#lowCount++
    return res
  }

  // 入队
  enqueue(data){
    this.#items[this.#count] = data
    this.#count++
  }

  // 返回队头
  front(){
    return this.#items[this.#lowCount]
  }

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

  size(){
    return this.#count - this.#lowCount
  }

  clear(){
    this.#items = {}
    this.#count = 0
    this.#lowCount = 0
  }

  toString(){
    let str = ""
    for(let i=this.#lowCount;i<this.#count;i++){
      // 注意这里拼接时,最后会多一个空格,要处理一下
      str+=`${this.#items[i]} `
    }
    return str
  }
}

let queue = new Queue()

3. 队列应用之击鼓传花

(经典面试题哦~)

设计一个函数完成击鼓传花游戏。既定的人,设定的敲鼓次数。一次敲鼓,传一个人(第一次敲鼓:user1-> user2),鼓数敲完,传到的人出局失败,剩余一个人为胜利者。

js 复制代码
function game(list,num) {
  let queue = new Queue()
  for(let i=0;i<list.length;i++) {
    queue.enqueue(list[i])
  }

  while(queue.size()>1) {
    for(let i=0;i<num;i++){
      queue.enqueue(queue.dequeue())
    }

    console.log(queue.dequeue(),"淘汰了")
  }

  return queue.dequeue()
}


game(["user1","user2","user3","user4","user5"],7)

4. 双端队列

例如排队的时候,前面的人可以出列也可能有人插队到队头,队尾有人入队,也可能有人等太久了直接出队。

再比如进行 ctrl+z 撤销的时候的逻辑:设定了保存100步,在第101步时,第一步出队。在100步时使用了 ctrl+z 撤销了一步,第100步出队了~

js 复制代码
// double-ended queue
class DeQueue {
  #items = {}
  #lowCount = 0 // 记录队头索引值
  #count = 0 // 插入数量  (size = lowCount - count) 思路与数据库索引其实是一致的

  // 出队(默认:可以从队头进行删除)
  removeFront(){
    // 假如先进行删除,要保证没有造成影响
    if(this.isEmpty()){
      // return undefined
      return
    }
    let res = this.#items[this.#lowCount]
    delete this.#items[this.#lowCount]
    this.#lowCount++
    return res
  }

  // 入队(默认:可以从队尾进行增加)
  addBack(data){
    this.#items[this.#count] = data
    this.#count++
  }

  // 双端队列新增:可以从队头增加
  addFront(data){
    // 要考虑3个场景
    // 1. 如果为空,复用 addBack
    if(this.isEmpty()){
      this.addBack(data)
    } else {
      // 2. lowCount > 0, 就直接将 lowCount-1, 再将新元素放到 lowCount-1 的位置上
      if(this.#lowCount > 0){
        this.#lowCount--;
        this.#items[this.#lowCount] = data
      } else {
        // 3. lowCount === 0
        // 可以引出链表的好处,在任意位置插入删除元素。
        // 现在只能硬着头皮干,使用与JS数组unshift一样的机制(性能不太好)
        // 从队头插入一个元素,之前的每一位向后挪一步
        for(let i=this.#count;i>0;i--){
          this.#items[i] = this.#items[i-1]
        }
        this.#items[0] = data
        this.#count++
      }
      
    }
    
  }

  // 双端队列新增:可以从队尾删除
  removeBack(){
    if(this.isEmpty()){
      return
    }
    this.#count--
    const res = this.#items[this.#count]
    delete this.#items[this.#count]
    return res
  }

  // 返回队头元素
  peekFront(){
    return this.#items[this.#lowCount]
  }

  // 双端队列新增:返回队尾元素
  peekBack(){
    // 可加可不加,不影响
    // if(this.isEmpty()){
    //   return
    // }
    return this.#items[this.#count-1]
  }

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

  size(){
    return this.#count - this.#lowCount
  }

  clear(){
    this.#items = {}
    this.#count = 0
    this.#lowCount = 0
  }

  toString(){
    let str = ""
    for(let i=this.#lowCount;i<this.#count;i++){
      // 注意这里拼接时,最后会多一个空格,要处理一下
      str+=`${this.#items[i]} `
    }
    return str
  }
}

let dequeue = new DeQueue()

addFront场景3:当 lowCount === 0 时的测试

addFront场景2:当 lowCount > 0, 就直接将 lowCount-1, 再将新元素放到 lowCount-1 的位置上 的测试

addFront场景1:当 dequeue 为空时的测试

removeBack 测试

peekBack、peekFront 测试

5. 双端队列的应用

  1. 回文检查(仅笔试应用...,在业务场景中用的不多)

例如(大小写不计,空格不计):dad、Dad、Da d、假似真时真似假、

js 复制代码
function test(str){
  const lowstr = str.toLocaleLowerCase().split(" ").join("")

  let dequeue = new DeQueue()

  for(let i=0;i<lowstr.length;i++){
    dequeue.addBack(lowstr[i])
  }

  let isEqual = true
  while(dequeue.size()>1){
    if(dequeue.removeFront() !== dequeue.removeBack()){
      isEqual = false
      break;
    }
  }

  return isEqual
}

test("D   a       d")
相关推荐
90后小陈老师23 分钟前
WebXR教学 07 项目5 贪吃蛇小游戏
前端·数码相机
一口一个橘子25 分钟前
[ctfshow web入门] web118
前端·web安全·网络安全
GanGuaGua41 分钟前
Vue3:脚手架
前端·javascript·css·vue.js·vue
鸡吃丸子2 小时前
常见的实时通信技术(轮询、sse、websocket、webhooks)
前端·websocket·状态模式
胡斌附体2 小时前
vue添加loading后修复页面渲染问题
前端·javascript·vue.js·渲染·v-if·异步加载
酷爱码3 小时前
css中的 vertical-align与line-height作用详解
前端·css
沐土Arvin3 小时前
深入理解 requestIdleCallback:浏览器空闲时段的性能优化利器
开发语言·前端·javascript·设计模式·html
专注VB编程开发20年3 小时前
VB.NET关于接口实现与简化设计的分析,封装其他类
java·前端·数据库
小妖6663 小时前
css 中 content: “\e6d0“ 怎么变成图标的?
前端·css
L耀早睡4 小时前
mapreduce打包运行
大数据·前端·spark·mapreduce