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")
相关推荐
Mars狐狸2 分钟前
AI项目改用服务端组件实现对话?包体积减小50%!
前端·react.js
H5开发新纪元11 分钟前
Vite 项目打包分析完整指南:从配置到优化
前端·vue.js
嘻嘻嘻嘻嘻嘻ys12 分钟前
《Vue 3.3响应式革新与TypeScript高效开发实战指南》
前端·后端
恋猫de小郭27 分钟前
腾讯 Kuikly 正式开源,了解一下这个基于 Kotlin 的全平台框架
android·前端·ios
2301_7994049129 分钟前
如何修改npm的全局安装路径?
前端·npm·node.js
(❁´◡双辞`❁)*✲゚*35 分钟前
node入门和npm
前端·npm·node.js
韩明君39 分钟前
前端学习笔记(四)自定义组件控制自己的css
前端·笔记·学习
tianchang1 小时前
TS入门教程
前端·typescript
吃瓜群众i1 小时前
初识javascript
前端
前端练习生1 小时前
vue2如何二次封装表单控件如input, select等
前端·javascript·vue.js