用JS表示队列类,且练习用queue实现击鼓传花

413 阅读5分钟

夯下数据结构和算法基础,JS 里没有栈、队列、链表巴拉巴拉明显的结构,只能用类去伪造,不然做算法题真的费劲。

这次造普通队列类吧,这边底层使用数组,当然有空的话,你也可以试试对象。

基本概念

  • 队列是一种遵循先进先出(FIFO,First In First Out)原则的有序集合。
  • 新添加的或待删除的元素都保存在队列的一端,称为“队尾”,另一端就叫“队头”。
  • 队列是一种操作受限制的线性表,只允许在一端插入数据,在另一端删除数据。
  • 队列的插入操作叫做入队,删除操作叫做出队。

应用场景

队列的应用场景:银行排队、打印机打印任务、操作系统中的进程调度、浏览器的前进后退、任务队列、事件队列、Promise队列等等。

基本方法

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

类实现

class Queue {
  queue: any[];
  constructor() {
    this.queue = [];
  }
  enqueue(value: any) {
    this.queue.enqueue(value);
  }
  dequeue() {
    return this.queue.shift();
  }
  // 这里注意是队前面的第一个
  front() {
    return this.queue[0];
  }
  size() {
    return this.queue.length;
  }
  isEmpty() {
    return this.size() === 0;
  }
  clear() {
    this.queue = [];
  }
}

可以头上顶个注释,就容易调用方法了

/**
 * Queue
 * 队列是一种遵循先进先出(FIFO,First In First Out)原则的有序集合。
 * 新添加的或待删除的元素都保存在队列的一端,称为“队尾”,另一端就叫“队头”。
 * 队列是一种操作受限制的线性表,只允许在一端插入数据,在另一端删除数据。
 * 队列的插入操作叫做入队,删除操作叫做出队。
 * @description 队列
 * @class Queue
 * @property queue 队列内部存储的数组
 * @method enqueue(element(s)) 向队列尾部添加一个(或多个)新的项。
 * @method dequeue() 移除队列的第一(即排在队列最前面的)项,并返回被移除的元素。
 * @method front() 返回队列中第一个元素——最先被添加,也将是最先被移除的元素。队列不做任何变动(不移除元素,只返回元素信息——与 Stack 类的 peek 方法非常类似)。
 * @method size() 返回队列包含的元素个数,与数组的 length 属性类似。
 * @method isEmpty() 如果队列中不包含任何元素,返回 true,否则返回 false。
 * @method clear() 移除队列里的所有元素。
 * @example
 * const q = new Queue()
 * q.enqueue(1)
 * q.enqueue(2)
 * q.enqueue(3)
 * q.dequeue() // 1
 * q.dequeue() // 2
 * q.dequeue() // 3
 * q.dequeue() // undefined
 * q.enqueue(1)
 * q.enqueue(2)
 * q.front() // 1
 * q.size() // 2
 * q.isEmpty() // false
 * q.clear()
 * q.isEmpty() // true
 */

练习:实现击鼓传花的游戏

击鼓传花的规则是:有 n 个人围成一圈,从第一个人开始报数,数到 m 的人出局,然后从下一个人重新开始报数,数到 m 的人再出局,直到剩下最后一个人为止。

利用循环队列 可以实现击鼓传花的游戏。

循环队列是一种特殊的队列,当队列中的元素个数达到队列的最大容量时,再往队列中添加元素,会覆盖掉队列中最早添加的元素。

游戏实现的主要逻辑:

  1. 参赛者形成参赛者队列
  2. 每一次传花的时候,传完花的出队,然后重新从队尾进队(循环队列的威力,也是最核心的地方)
  3. 每一轮传花完毕之后,拿花的人也就是队头的第一个人,出队,进去淘汰者列表(回不去嘞),参赛者队列减少
  4. 直到参赛者队列只剩一个人的时候,比赛结束,那人获胜
function hotPotato(nameList: any[], num: number) {
  // 传花队列,这个队列始终是参赛者,但是参赛者会越来越少
  const queue = new Queue()
  // 淘汰列表
  const eliminatedList: any[] = []
  // 将参赛者一个个放进队列
  for (let i = 0; i < nameList.length; i++) {
    queue.enqueue(nameList[i])
  }
  // 队列里只剩一个人的时候,比赛终止,也就是循环终止条件
  while (queue.size() !== 1) {
    // 开始传花,传num次
    for (let i = 0; i < num; i++) {
      // 传完的人,出队,然后重新排到队尾(循环)
      queue.enqueue(queue.dequeue())
    }
    // 一轮之后,拿花的话也就是队首人出队,进去淘汰者列表
    eliminatedList.enqueue(queue.dequeue())
  }
  return {
    eliminated: eliminatedList,
    // 队里唯一的人
    winner: queue.front()
  }

}

加上注释的话

/**
 * 击鼓传花的规则是:有 n 个人围成一圈,从第一个人开始报数,数到 m 的人出局,然后从下一个人重新开始报数,数到 m 的人再出局,直到剩下最后一个人为止。
 * 可以利用循环队列实现击鼓传花的游戏。
 * 循环队列是一种特殊的队列,当队列中的元素个数达到队列的最大容量时,再往队列中添加元素,
 * 会覆盖掉队列中最早添加的元素。
 * @description 击鼓传花
 * @param {Array} nameList 参与游戏的人
 * @param {Number} num 数到几的人出局
 * @returns {Object} 最后剩下的人和出局的人
 * @example
 * const nameList = ['a', 'b', 'c', 'd', 'e', 'f']
 * const num = 3
 * drumAndPass(nameList, num) // { eliminated: [ 'c', 'f', 'd', 'b', 'e' ], winner: 'a' }
 * @example
 * const nameList = ['a', 'b', 'c', 'd', 'e', 'f']
 * const num = 2
 * drumAndPass(nameList, num) // { eliminated: [ 'b', 'e', 'a', 'f', 'd' ], winner: 'c' }
 * @example
 * const nameList = ['a', 'b', 'c', 'd', 'e', 'f']
 * const num = 7
 * drumAndPass(nameList, num) // { eliminated: [ 'f', 'e', 'd', 'c', 'b' ], winner: 'a' }
 * @example
 * const nameList = ['a', 'b', 'c', 'd', 'e', 'f']
 * const num = 1
 * drumAndPass(nameList, num) // { eliminated: [ 'a', 'b', 'c', 'd', 'e' ], winner: 'f' }
 */

引用

  • 《JavaScript 数据结构与算法》(希望我能看完)