阅读 198

学习JavaScript数据结构与算法之队列(2)

队列是遵循先进先出(FIFO,也称为先来先服务)原则的一组有序的项。队列在尾部添加新元素,并从顶部移除元素。最新添加的元素必须排在队列的末尾。

1.1 模拟一个队列

 enqueue(element(s)):向队列尾部添加一个(或多个)新的项。
 dequeue():移除队列的第一项(即排在队列最前面的项)并返回被移除的元素。
 peek():返回队列中第一个元素——最先被添加,也将是最先被移除的元素。队列不做任何变动(不除元素,只返回元素信息——与 Stack 类的 peek 方法非常类似)。该方法在其他语言中也可以叫作 front 方法。
 isEmpty():如果队列中不包含任何元素,返回 true,否则返回 false。
 size():返回队列包含的元素个数,与数组的 length 属性类似

class Queue { 
  constructor() { 
    this.count = 0
    this.lowestCount = 0
    this.items = {}
  } 

  // 向队列尾部添加一个(或多个)新的项
  enqueue(element){
    this.items[this.count] = element
    this.count++
  }

  // 移除队列的第一项(即排在队列最前面的项)并返回被移除的元素
  dequeue() {
    if (this.isEmpty()) { 
      return undefined
    } 
    const result = this.items[this.lowestCount]
    delete this.items[this.lowestCount]
    this.lowestCount++
    return result
  }

  // 返回队列中第一个元素, 队列不做任何变动
  peek() {
    if (this.isEmpty()) { 
      return undefined
    } 
    return this.items[this.lowestCount]
  }

  // 如果队列中不包含任何元素,返回 true,否则返回 false
  isEmpty() {
    return this.count - this.lowestCount === 0
  }

  // 返回队列包含的元素个数,与数组的 length 属性类似
  size() {
    return this.count - this.lowestCount
  }

  clear() { 
    this.items = {}
    this.count = 0
    this.lowestCount = 0
  }

  toString() { 
    if (this.isEmpty()) { 
      return ''
    } 
    let objString = `${this.items[this.lowestCount]}`
    for (let i = this.lowestCount + 1; i < this.count; i++) { 
      objString = `${objString},${this.items[i]}`
    } 
    return objString
  }
}
复制代码

2. 双端队列

双端队列(deque,或称 double-ended queue)是一种允许我们同时从前端和后端添加和移除元素的特殊队列。

双端队列在现实生活中的例子有电影院、餐厅中排队的队伍等。举个例子,一个刚买了票的人如果只是还需要再问一些简单的信息,就可以直接回到队伍的头部。另外,在队伍末尾的人如果赶时间,他可以直接离开队伍。

双端队列的一个常见应用是存储一系列的撤销操作。每当用户在软件中进行了一个操作,该操作会被存在一个双端队列中(就像在一个栈里)。当用户点击撤销按钮时,该操作会被从双端队列中弹出,表示它被从后面移除了。在进行了预先定义的一定数量的操作后,最先进行的操作会被从双端队列的前端移除。由于双端队列同时遵守了先进先出和后进先出原则,可以说它是把队列和栈相结合的一种数据结构

class Deque { 
  constructor() { 
    this.count = 0
    this.lowestCount = 0
    this.items = {}
  }

  // 该方法在双端队列前端添加新的元素
  addFront(element) {
    if (this.isEmpty()) { 
      this.addBack(element)
    } else if (this.lowestCount > 0) {
      this.lowestCount-- 
      this.items[this.lowestCount] = element 
    } else { 
      for (let i = this.count; i > 0; i--) {
        this.items[i] = this.items[i - 1]
      } 
      this.count++ 
      this.lowestCount = 0 
      this.items[0] = element
    }
  }

  // 该方法在双端队列后端添加新的元素
  addBack(element) {}

  // 该方法会从双端队列前端移除第一个元素
  removeFront() {}

  // 该方法会从双端队列后端移除第一个元素
  removeBack() {}

  // 该方法返回双端队列前端的第一个元素
  peekFront() {}

  // 该方法返回双端队列后端的第一个元素
  peekBack() {}

  // 如果队列中不包含任何元素,返回 true,否则返回 false
  isEmpty() {
    return this.count - this.lowestCount === 0
  }

  // 返回队列包含的元素个数,与数组的 length 属性类似
  size() {
    return this.count - this.lowestCount
  }

  clear() { 
    this.items = {}
    this.count = 0
    this.lowestCount = 0
  }

  toString() { 
    if (this.isEmpty()) { 
      return ''
    } 
    let objString = `${this.items[this.lowestCount]}`
    for (let i = this.lowestCount + 1; i < this.count; i++) { 
      objString = `${objString},${this.items[i]}`
    } 
    return objString
  }
}
复制代码

3. 常见队列问题

3.1 最近的请求次数

var RecentCounter = function() {
    this.q = []
};

RecentCounter.prototype.ping = function(t) {
  this.q.push(t)
  while(this.q[0] < t - 3000) {
    this.q.shift()
  }
  return this.q.length
};
复制代码

3.2 循环队列——击鼓传花游戏

在这个游戏中,孩子们围成一个圆圈,把花尽快地传递给旁边的人。某一时刻传花停止,这个时候花在谁手里,谁就退出圆圈、结束游戏。重复这个过程,直到只剩一个孩子(胜者)

function hotPotato(elementsList, num) {
    const queue = []
    const elimitatedList = []
    for(let i = 0; i < elementsList.length; i++) {
        queue.unshift(elementsList[i])
    }

    while (queue.length > 1) {
        for (let i = 0; i < num; i++) {
            queue.unshift(queue.pop())
        }
        elimitatedList.push(queue.pop())
    }

    return {
        eliminated: elimitatedList, 
        winner: queue.pop()
    }
}

const names = ['John', 'Jack', 'Camila', 'Ingrid', 'Carl']
const result = hotPotato(names, 7)

result.eliminated.forEach(name => { 
    console.log(`${name}在击鼓传花游戏中被淘汰。`)
}); 
console.log(`胜利者: ${result.winner}`)

复制代码