数据结构之队列

学习github地址

队列和双端队列

我们前面学习了栈数据结构, 队列数据结构和栈很相似, 区别是队列属于先进先出的数据结构, 双端队列是一种将栈的原则和队列的原则混合在一起的数据结构

队列数据结构

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

  1. 如何创建一个队列? 以及声明一些队列可用的方法
    • enqueue(element):向队列尾部添加一个(或多个)新的项
    • dequeue():移除队列的第一项, 并返回被移除的元素
    • peek():返回队列的第一个元素-最先被添加, 也将是最先被移除的元素. 队列不做变动. 该方法在其他语言也可以叫front方法
    • isEmpty():如果队列中不包含任何元素, 返回 true, 否则返回 false
    • size():返回队列包含的元素个数
class Queue {
  constructor() {
    this.count = 0;
    this.lowestCount = 0;
    this.items = {}; // 使用对象来存储我们的元素
  }

  // enqueue 方法和 Stack 类中 push 方法的实现方式相同
  enqueue(element) {
    this.items[this.count] = element;
    this.count++;
  }

  /**
   * items: {0: 1, 1: 8}
   * 键为0 获取队列头部的值, 删除它, 再返回它的值, 删除后, 变为 items: {1: 8}
   * 再次执行 dequeue 方法, lowestCount 0 -> 1
   */
  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];
  }

  /**
   * 假设 count 2, lowestCount 为 0, 表示队列有两个元素
   * 移除一个元素, lowestCount -> 1  count 还是 2, 队列只有一个元素了
   *
   */
  // 计算队列有多少元素, 计算 count 和 lowestCount 之间的差值
  isEmpty() {
    return this.count - this.lowestCount === 0;
  }

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

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

  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;
  }
}

module.exports = Queue;

双端队列数据结构

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

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

  1. 和之前一样, 声明一个 Deque 类及其构造函数, 包括相同的内部属性和以下方法:isEmpty、clear、size 和 toString。 由于双端队列允许在两端添加和移除元素,还会有下面几个方法?
    • addFront(element):该方法在双端队列前端添加新的元素
    • addBack(element):该方法在双端队列后端添加新的元素(实现方法和 Queue 类中的 enqueue 方法相同)
    • removeFront():该方法会从双端队列前端移除第一个元素(实现方法和 Queue 类中的 dequeue 方法相同)
    • removeBack():该方法会从双端队列后端移除第一个元素(实现方法和 Stack 类中的 pop 方法一样)
    • peekFront():该方法返回双端队列前端的第一个元素(实现方法和 Queue 类中的 peek 方法一样)
    • peekBack():该方法返回双端队列后端的第一个元素(实现方法和 Stack 类中的 peek 方法一样)
class Deque {
  constructor() {
    this.count = 0;
    this.lowestCount = 0;
    this.items = {};
  }

  addFront(element) {
    // 第一种情况: 如果双端队列是空的, 执行 addBack 方法
    if (this.isEmpty()) {
      this.addBack(element);
      /**
       * 第二种情况:
       * 一个元素已经被从双端队列的前端移除, 也就是说 lowestCount 属性会大于等于 1,
       * 这种情况下,我们只需要将 lowestCount 属性减 1 并将新元素的值放在这个键的位置上即可
       * 实例: items = {1: 8, 2: 9} count = 0; lowestCount = 1
       * 想将元素7添加到双端队列的前端: -> lowestCount = 0;  items[0] = 7
       */
    } else if (this.lowestCount > 0) {
      this.lowestCount--;
      this.items[this.lowestCount] = element;
      /**
       * 第三种情况: 最后一种场景是 lowestCount 为 0 的情况
       * 可以设置一个负值的键,同时更新用于计算双端队列长度的逻辑,使其也能包含负键值
       *
       * 我们把本场景看作使用数组。要在第一位添加一个新元素,我们需要将所有元素后移一位(行{1})来空出第一个位置。由于我们不想丢失任何已
       * 有的值,需要从最后一位开始迭代所有的值,并为元素赋上索引值减 1 位置的值。在所有的元素都完成移动后,第一位将是空闲状态,这样就可以用需要添加的新元素来覆盖它了{4}
       */
    } else {
      // {1} {0: 1, 1: 3}
      for (let i = this.count; i > 0; i--) {
        this.items[i] = this.items[i - 1];
      }
      this.count++;
      this.lowestCount = 0;
      this.items[0] = element; // {4}
    }
  }

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

  removeFront() {
    if (this.isEmpty()) return undefined;
    const result = this.items[this.lowestCount]; // 如果队列不为空, 暂存队列头部的值
    delete this.items[this.lowestCount]; // 删除队列删除元素
    this.lowestCount++;
    return result;
  }

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

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

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

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

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

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

  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;
  }
}

module.exports = Deque;

使用双端队列解决问题

使用队列来模拟击鼓传花游戏,并使用双端队列来检查一个短语是否为回文

  1. 循环队列 --- 击鼓传花问题
const Queue = require("./queue");

function hotPotato(elementList, num) {
  const queue = new Queue();
  const elimitatedList = [];

  // 会得到一份名单, 把里面的名字加入队列
  for (let i = 0; i < elementList.length; i++) {
    queue.enqueue(elementList[i]);
  }

  // 给定一个数字, 然后迭代队列. 从队列开头移除一项, 再将其添加到队列末尾
  while (queue.size() > 1) {
    for (let i = 0; i < num; i++) {
      queue.enqueue(queue.dequeue());
    }
    // 模拟击鼓传花(如果你把花传给了旁边的人,你被淘汰的威胁就立刻解除了)。一旦达到给定的传递次数,拿着花的那个人就被淘汰了(从队列中移除
    elimitatedList.push(queue.dequeue());
  }

  // 最后只剩下一个人的时候,这个人就是胜者
  return {
    eliminated: elimitatedList,
    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}`);
击鼓传花
  1. 回文检查器

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

下面的算法使用了一个双端队列来解决问题

const Deque = require("./Deque");

const palindromeChecker = (aString) => {
  // 检查传入的字符串参数是否合法
  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++) {
    // 对字符串中的所有字符执行 enqueue 操作
    deque.addBack(lowerString.charAt(i));
  }

  // 如果所有元素都在双端队列中(如果只有一个字符的话,那它肯定是回文)并且首尾字符相同的话
  while (deque.size() > 1 && isEqual) {
    firstChar = deque.removeFront(); // 从前端移除一个元素
    lastChar = deque.removeBack(); // 再从后端移除一个元素
    // 要使字符串为回文,移除的两个字符必须相同。如果字符不同的话,这个字符串就不是一个回文
    if (firstChar !== lastChar) {
      isEqual = false;
    }
  }

  return isEqual;
};

你可能感兴趣的:(数据结构之队列)