队列 - 双端队列实现

之前实现的单端队列, 只能从队列的尾部进, 头部出.

但现在我们来实现一种从两端都可进行出队入队的结构, 即双端队列 deque.

在计算机中, 双端队列最常用的一个场景是存储一系列的撤销操作. 当然用户点击了某个操作, 则此操作会被存在一个双端队列中, 类似栈里.

当用户点击撤销操作时, 该操作会被从双端队列弹出. 一顿猛如虎的操作后, 最先进行的操作会先进行出队. 由于双端队列 同时遵循先进先出和后进先出 原则, 可知它是一种 队列+栈 结合的数据结构.

以数组来模拟队列直观演示一下,

当单端时是这样的, 假设左边是队首, 右边是队尾, 是单向流动的.

// 初始队列
var queue = [ ]

// 元素 1, 2, 3 依次从队尾入队
[1, 2, 3]

// 队首元素出队
[2, 3]

当双端队列时候, 队尾和队首都可进行新增和删除元素, 还是以数组来模拟, 左边是队首, 右边是对尾.

// 初始队列
var deque = []

// 元素1, 2 从队首或者队尾入对
[1, 2]

// 元素3 从队首入队
[3, 1, 2]

// 元素4 从队尾入队
[3, 1, 2, 4]

// 队首元素出队一次
[1, 2, 4]

// 队尾元素出队一次
[1, 2]

创建队列

同之前一样, 底层用 js对象来实现, 或者直接在原来的基础改造即可.

class Deque {
  constructor() {
    this.count = 0 
    this.frontIndex = 0  
    this.obj = {}
  }
  // 队列长度
  size() {
    return this.count - this.frontIndex
  }
  // 队列是否为空
  isEmpty() {
    this.count - this.frontIndex == 0
  }
  // 清空队列
  clear() {
    this.obj = {}
    this.frontIndex = 0
    this.count = 0
  }
  // 查看全队列
  toString() {
    if (this.isEmpty()) return this.obj

    let objString = `${this.obj[this.frontIndex]}`
    for (let i = this.frontIndex + 1; i < this.count; i++) {
      objString = `${objString}, ${this.obj[i]}`
    }
    return objString
  }
}

双端队列除了以上通用方法外, 还运行在两端添加和移除元素, 因此还有又如下方法:

  • addFront () 在双端队列前端添加元素
  • addBack () 在双端队列后端添加元素, 类似单端的 enqueue
  • removeFront () 从双端队列前端移除第一个元素, 类似单端的 dequeue
  • removeBack () 从双端队列后端移除第一个元素, 类似单端的 pop
  • peekFront () 从双端队列前端返回第一个元素, 类似单端的 peek
  • peekBack () 从双端队列后端返回第一个元素

队尾添加元素

class Deque {
  constructor() {
    this.count = 0 
    this.frontIndex = 0  
    this.obj = {}
  }
  // 队尾添加元素
  addBack(item) {
    this.obj[this.count] = item
    this.count++
  }
}

队尾删除元素

class Deque {
  constructor() {
    this.count = 0 
    this.frontIndex = 0  
    this.obj = {}
  }
  // 队尾删除元素
  removeBack() {
    if (this.isEmpty()) return undefined

    this.count -= 1
    let backItem = this.obj[this.count]
    
    delete this.obj[this.count]
    return backItem
  }
}

队首添加元素

class Deque {
  constructor() {
    this.count = 0 
    this.frontIndex = 0  
    this.obj = {}
  }
  // 队首添加元素
  addFront(item) {
    // 当为空队列时, 则从队尾加
    if (this.isEmpty()) {
      this.addBack(item)
    } else if (this.frontIndex > 0) {
      // 当队首元素已有过出队时, 此时 frontIndex > 0, 此时进行 减1替换即可
      this.frontIndex -= 1
      this.obj[this.frontIndex] = item
    } else {
      // 当队首元素未有过出队时, 此时 frontIndex = 0, 后面元素依次后挪
      for (let i = this.count; i > 0; i--) {
        this.obj[i] = this.obj[i-1]
      }
      // 移完后将队首元素的值和索引都更新, 队列长度加1
      this.obj[0] = item 
      this.frontIndex = 0
      this.count += 1
    }
  }
}

队首删除元素

class Deque {
  constructor() {
    this.count = 0 
    this.frontIndex = 0  
    this.obj = {}
  }
  // 队尾删除元素
  removeBack() {
    if (this.isEmpty()) return undefined

    this.count -= 1
    let backItem = this.obj[this.count]
    
    delete this.obj[this.count]
    return backItem
  }
}

于是, 这样一个双端都可以进行出队, 入队的结构就做好啦!

测试验证

然后整体来测试一波.

class Deque {
  constructor() {
    this.count = 0 
    this.frontIndex = 0  
    this.obj = {}
  }
  // 队列长度
  size() {
    return this.count - this.frontIndex
  }
  // 队列是否为空
  isEmpty() {
    if (this.count - this.frontIndex == 0) return true;
  }
  // 清空队列
  clear() {
    this.obj = {}
    this.frontIndex = 0
    this.count = 0
  }
  // 查看全队列
  toString() {
    if (this.isEmpty()) return this.obj;

    let objString = `${this.obj[this.frontIndex]}`
    for (let i = this.frontIndex + 1; i < this.count; i++) {
      objString = `${objString}, ${this.obj[i]}`
    }
    return objString
  }
  // 队尾添加元素
  addBack(item) {
    this.obj[this.count] = item
    this.count++
  }
  // 队尾删除元素
  removeBack() {
    if (this.isEmpty()) return undefined

    this.count -= 1
    let backItem = this.obj[this.count]
    delete this.obj[this.count]
    return backItem
  }
  // 队首添加元素
  addFront(item) {
    // 当为空队列时, 则从队尾加
    if (this.isEmpty()) {
      this.addBack(item)
    } else if (this.frontIndex > 0) {
      // 当队首元素已有过出队时, 此时 frontIndex > 0, 此时进行 减1替换即可
      this.frontIndex -= 1
      this.obj[this.frontIndex] = item
    } else {
      // 当队首元素未有过出队时, 此时 frontIndex = 0, 后面元素依次后挪
      for (let i = this.count; i > 0; i--) {
        this.obj[i] = this.obj[i-1]
      }
      // 移完后将队首元素的值和索引都更新, 队列长度加1
      this.obj[0] = item 
      this.frontIndex = 0
      this.count += 1
    }
  }
  // 队首删除元素
  removeFront() {
    if (this.isEmpty()) return undefined
    
    let frontItem = this.obj[this.frontIndex]
    delete this.obj[this.frontIndex]

    this.frontIndex += 1
    return frontItem
  }
}


// test 

// 初始队列
let deque = new Deque()
console.log('新建空队列此时为: ', deque.toString());

let item0 = deque.removeBack()
console.log('此时队列为: ', deque.toString());


console.log('元素1, 2 分别从队首, 对尾入队');
deque.addFront(1)
deque.addBack(2)
console.log('此时队列为: ', deque.toString());

console.log('元素3 从队首入队');
deque.addFront(3)
console.log('此时队列为: ', deque.toString());

console.log('元素4 从队尾入队');
deque.addBack(4)
console.log('此时队列为: ', deque.toString());


let item1 = deque.removeFront()
console.log('队首元素出队一次, 删除的元素是: ', item1);
console.log('此时队列为: ', deque.toString());

let item2 = deque.removeBack()
console.log('队尾元素出队一次, 删除的元素是: ', item2);
console.log('此时队列为: ', deque.toString());

PS F:\algorithms> node .\deque.js

新建空队列此时为:  {}
此时队列为:  {}

元素1, 2 分别从队首, 对尾入队
此时队列为:  1, 2

元素3 从队首入队
此时队列为:  3, 1, 2

元素4 从队尾入队
此时队列为:  3, 1, 2, 4

队首元素出队一次, 删除的元素是:  3
此时队列为:  1, 2, 4

队尾元素出队一次, 删除的元素是:  4
此时队列为:  1, 2

只要逻辑理解了, 然后实现就是慢慢思考即可, 还是比较有趣的.

posted @ 2024-04-11 21:18  致于数据科学家的小陈  阅读(103)  评论(0编辑  收藏  举报