队列和双端队列(JS)

108 阅读2分钟

一、队列

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

创建队列

class Queue {
  constructor() {
    this.count = 0  //控制队列的大小
    this.lowestCount = 0  //前端元素位置
    this.items = {}  //存储队列元素
  }
}
声明以下队列可用的方法
  • enqueue(element(s)):向队列尾部添加一个(或多个)新的项。
  • dequeue():移除队列的第一项(即排在队列最前面的项)并返回被移除的元素。
  • peek():返回队列中第一个元素——最先被添加,也将是最先被移除的元素。队列不做任何变动(不移除元素,只返回元素信息——与 Stack 类的 peek 方法非常类似)。该方法在其他语言中也可以叫作 front 方法。
  • isEmpty():如果队列中不包含任何元素,返回 true,否则返回 false。
  • size():返回队列包含的元素个数,与数组的 length 属性类似。

1.向队列添加元素(enqueue)

enqueue(element) {
  this.items[this.count] = element
  this.count++
}

2.从队列移除元素(dequeue)

dequeue() {
  if (this.isEmpty()) {  //判断队列是否为空
    return undefined
  }
  const result = this.items[this.lowestCount]  //记录队列的第一个元素
  delete this.items[this.lowestCount]  //删除队列的第一个元素
  this.lowestCount++  //将lowestCount移到下一个元素
  return result
}

3.查看队列头元素(peek)

peek() {
  if (this.isEmpty()) {
    return undefined
  }
  return this.items[this.lowestCount]
}

4.检查队列是否为空(isEmpty)

isEmpty() {
  return this.count - this.lowestCount === 0
}

5.获取队列的长度(size)

size(){
  return this.count - this.lowestCount
}

6.清空队列(clear)

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

7.创建 toString 方法

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
}

二、双端队列

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

现实生活的例子:一个刚买了票的人如果只是还需要再问一些简单的信息,就可以直接回到队伍的头部。另外,在队伍末尾的人如果赶时间,他可以直接离开队伍。

由于双端队列同时遵守了先进先出和后进先出原则,可以说它是把队列和栈相结合的一种数据结构。

创建双端队列类

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

双端队列是一种特殊的队列,所以isEmpty、clear、size 和 toString方法一样。

增加以下方法:

  • addFront(element):该方法在双端队列前端添加新的元素。
  • addBack(element):该方法在双端队列后端添加新的元素(实现方法和 Queue 类中的enqueue 方法相同)。
  • removeFront():该方法会从双端队列前端移除第一个元素(实现方法和 Queue 类中的dequeue 方法相同)。
  • removeBack():该方法会从双端队列后端移除第一个元素(实现方法和 Stack 类中的pop 方法一样)。
  • peekFront():该方法返回双端队列前端的第一个元素(实现方法和 Queue 类中的 peek方法一样)。
  • peekBack():该方法返回双端队列后端的第一个元素(实现方法和 Stack 类中的 peek方法一样)。

1.双端队列后端添加新的元素(addBack)

addBack(element) {
  this.items[this.count] = element
  this.count++
}

2.双端队列前端添加新的元素(addFront)

addFront(element) {
  // 情况一:双端队列为空,直接使用addBack(),向队列后端插入元素
  if (this.isEmpty()) {
    this.addBack(element)
  } else if (this.lowestCount > 0) {
    // 情况二:有元素从队列前端删除,此时队列的前端元素位置向后移动了
    this.lowestCount--
    this.items[this.lowestCount] = element
  } else {
    // 情况三:队列的前端元素位置为0,此时需要将队列的前端元素位置向后移动
    for (let i = this.count; i > 0; i--) {
      this.items[i] = this.items[i - 1]
    }
    this.count++
    this.lowestCount = 0
    this.items[0] = element
  }
}

3.双端队列前端移除第一个元素(removeFront)

removeFront() {
  if (this.isEmpty()) {
    return undefined
  }
  const result = this.items[this.lowestCount]
  delete this.items[this.lowestCount]
  this.lowestCount++
  return result
}

4.双端队列后端移除元素(removeBack)

removeBack() {
  if (this.isEmpty()) {
    return undefined
  }
  this.count--
  const result = this.items[this.count]
  delete this.items[this.count]
  return result
}

5.返回双端队列前端的第一个元素(peekFront)

peekFront(){
  if (this.isEmpty()) {
    return undefined
  }
  return this.items[this.lowestCount]
}

6.返回双端队列后端的元素(peekBack)

peekBack() {
  if (this.isEmpty()) {
    return undefined
  }
  return this.items[this.count - 1]
}

三、循环队列解决击鼓传花游戏

// elementsList:名单,num:淘汰位置
function hotPotato(elementsList, num) {
  const queue = new Queue()
  const elimitatedList = [] //被淘汰名单
  for (let i = 0; i < elementsList.length; i++) {
    queue.enqueue(elementsList[i])  //将名单加入队列
  }
  while (queue.size > 1) {
    for (let i = 0; i < num; i++) {
      queue.enqueue(queue.dequeue())  //将队列开头移除加到队尾
    }
    elimitatedList.push(queue.dequeue())  //加入淘汰名单
  }
  return {
    eliminated: elementsList,
    winner: queue.dequeue()
  }
}

//测试
const names = ['John', 'Jack', 'Camila', 'Ingrid', 'Carl'];
const result = hotPotato(names, 7);
result.eliminated.forEach(name => {
  console.log(`${name}在击鼓传花游戏中被淘汰。`);
});
console.log(`胜利者: ${result.winner}`);

下图模拟了这个输出过程 image.png

四、双端队列检查是否为回文

回文概念:回文是正反都能读通的单词、词组、数或一系列字符的序列,例如 madam。

function palindromeChecker(aString) {
  //不合法字符串参数返回false
  if (aString === undefined || aString === null || (aString !== null && aString.length === 0)) {
    return false;
  }
  const deque = new Deque()
  const lowerString = aString.toLocaleLowerCase().split(' ').join('')  //将字母转化为小写并移除所有的空格
  let isEqual = true  // 是否为回文
  let firstChar, lastChar  // 定义两个变量,用于存储首尾字符

  // 将字符串加入双端队列
  for (let i = 0; i < lowerString.length; i++) {
    deque.addBack(lowerString.charAt(i));
  }

  while (!deque.isEmpty() && isEqual) {
    firstChar = deque.removeFront()  // 取出首字符
    lastChar = deque.removeBack()  // 取出尾字符
    if (firstChar !== lastChar) {
      isEqual = false  // 首尾字符不相等则不是回文
    }
  }
  return isEqual
}