队列数据结构

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

生活中队列的例子:排队,打印队列(文档)。

创建 Queue 类

Queue 类和 Stack 类非常相似,只是添加元素和移除元素的原则不同。

  • 向队列添加元素
  • 从队列移除元素
  • 查看队列头元素
  • 检查队列是否为空
  • 获取队列的长度
  • 清空队列
  • 创建 toString 方法
  1. /**
  2. * 创建队列
  3. */
  4. class Queue {
  5. constructor() {
  6. this.count = 0;
  7. this.lowestCount = 0;
  8. this.items = {};
  9. }
  10. // 向队列尾部添加一个(或多个)元素
  11. enqueue(element) {
  12. this.items[this.count] = element;
  13. this.count++;
  14. }
  15. // 移除队列的第一个元素,并返回被移除的元素
  16. dequeue() {
  17. if (this.isEmpty()) {
  18. return undefined;
  19. }
  20. const result = this.items[this.lowestCount];
  21. delete this.items[this.lowestCount];
  22. this.lowestCount++;
  23. return result;
  24. }
  25. // 返回队列中的第一个元素,不对队列做任何修改
  26. peek() {
  27. if (this.isEmpty()) {
  28. return undefined;
  29. }
  30. return this.items[this.lowestCount];
  31. }
  32. // 如果队列中没有元素就返回 true,否则返回 false
  33. isEmpty() {
  34. return this.count - this.lowestCount === 0;
  35. // return this.size();
  36. }
  37. // 返回队列的元素个数
  38. size() {
  39. return this.count - this.lowestCount;
  40. }
  41. // 清空队列
  42. clear() {
  43. this.items = {};
  44. this.count = 0;
  45. this.lowestCount = 0;
  46. }
  47. // toString 方法
  48. toString() {
  49. if (this.isEmpty()) {
  50. return '';
  51. }
  52. let objString = `${this.items[this.lowestCount]}`;
  53. for (let i = this.lowestCount + 1; i < this.count; i++) {
  54. objString = `${objString}, ${this.items[i]}`;
  55. }
  56. return objString;
  57. }
  58. }

使用 Queue 类

  1. /**
  2. * 使用 Queue 类
  3. */
  4. const queue = new Queue();
  5. console.log(queue.isEmpty()); // true
  6. queue.enqueue('John');
  7. queue.enqueue('Jack');
  8. console.log(queue.toString()); // John, Jack
  9. console.log(queue.size()); // 2
  10. console.log(queue.isEmpty()); // false
  11. queue.dequeue();
  12. console.log(queue.peek()); // Jack

双端队列数据结构

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

在计算机科学中,双端队列的一个常见应用是存储一些列的撤销操作。每当用户在软件中进行了一个操作,改操作会被存储在一个双端队列中。每当用户点击撤销按钮时,该操作会被从双端队列中弹出,表示它被从后面移除了。在进行了预定义的一定数量的操作后,最先进行的操作会被双端队列的前端移除。

创建 Deque 类

  1. /**
  2. * 创建双端队列(double-ended queue)
  3. */
  4. class Deque {
  5. constructor() {
  6. this.count = 0;
  7. this.lowestCount = 0;
  8. this.items = {};
  9. }
  10. // 在双端队列最前端添加新的元素
  11. addFront(element) {
  12. if (this.isEmpty()) {
  13. this.addBack(element);
  14. } else if (this.lowestCount > 0) {
  15. this.lowestCount--;
  16. this.items[this.lowestCount] = element;
  17. } else {
  18. for (let i = this.count; i > 0; i--) {
  19. this.items[i] = this.items[i - 1];
  20. }
  21. this.count++;
  22. this.lowestCount = 0;
  23. this.items[0] = element;
  24. }
  25. }
  26. // 在双端队列后端添加新的元素
  27. addBack(element) {
  28. this.items[this.count] = element;
  29. this.count++;
  30. }
  31. // 从双端队列前端移除第一个元素,并返回被移除的元素
  32. removeFront() {
  33. if (this.isEmpty()) {
  34. return undefined;
  35. }
  36. const result = this.items[this.lowestCount];
  37. delete this.items[this.lowestCount];
  38. this.lowestCount++;
  39. return result;
  40. }
  41. // 从双端队列后端移除第一个元素,并返回被移除的元素
  42. removeBack() {
  43. if (this.isEmpty()) {
  44. return undefined;
  45. }
  46. this.count--;
  47. const result = this.items[this.count];
  48. delete this.items[this.count];
  49. return result;
  50. }
  51. // 返回双端队列前端的第一个元素,不对队列做任何修改
  52. peekFront() {
  53. if (this.isEmpty()) {
  54. return undefined;
  55. }
  56. return this.items[this.lowestCount];
  57. }
  58. // 返回双端队列后端的第一个元素,不对队列做任何修改
  59. peekBack() {
  60. if (this.isEmpty()) {
  61. return undefined;
  62. }
  63. return this.items[this.count - 1];
  64. }
  65. // 如果队列中没有元素就返回 true,否则返回 false
  66. isEmpty() {
  67. return this.count - this.lowestCount === 0;
  68. // return this.size();
  69. }
  70. // 返回队列的元素个数
  71. size() {
  72. return this.count - this.lowestCount;
  73. }
  74. // 清空队列
  75. clear() {
  76. this.items = {};
  77. this.count = 0;
  78. this.lowestCount = 0;
  79. }
  80. // toString 方法
  81. toString() {
  82. if (this.isEmpty()) {
  83. return '';
  84. }
  85. let objString = `${this.items[this.lowestCount]}`;
  86. for (let i = this.lowestCount + 1; i < this.count; i++) {
  87. objString = `${objString}, ${this.items[i]}`;
  88. }
  89. return objString;
  90. }
  91. }

使用 Deque 类

  1. /**
  2. * 使用 Deque 类
  3. */
  4. const deque = new Deque();
  5. console.log(deque.isEmpty()); // true
  6. deque.addBack('John');
  7. deque.addBack('Jack');
  8. deque.addFront('Camila');
  9. console.log(deque.toString()); // Camila, John, Jack
  10. console.log(deque.size()); // 3
  11. console.log(deque.isEmpty()); // false
  12. deque.removeFront();
  13. console.log(deque.toString()); // John, Jack
  14. console.log(deque.peekFront()); // John
  15. deque.removeBack();
  16. console.log(deque.peekBack()); // John
  17. deque.clear();

使用队列和双端队列来解决问题

击鼓传花游戏

击鼓传花游戏:

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

使用队列

  1. function hotPotato(elementList, num) {
  2. const queue = new Queue();
  3. const elimitatedList = [];
  4. for (let i = 0; i < elementList.length; i++) {
  5. queue.enqueue(elementList[i]);
  6. }
  7. while (queue.size() > 1) {
  8. for (let i = 0; i < num; i++) {
  9. queue.enqueue(queue.dequeue());
  10. }
  11. elimitatedList.push(queue.dequeue());
  12. }
  13. return {
  14. eliminated: elimitatedList,
  15. winner: queue.dequeue()
  16. };
  17. }
  18. const names = ['John', 'Jack', 'Camila', 'Ingrid', 'Carl'];
  19. const result = hotPotato(names, 7);
  20. result.eliminated.forEach(name => {
  21. console.log(`${name}在击鼓传花游戏中被淘汰`);
  22. })
  23. console.log(`胜利者:${result.winner}`);
  24. /*
  25. Camila在击鼓传花游戏中被淘汰
  26. Jack在击鼓传花游戏中被淘汰
  27. Carl在击鼓传花游戏中被淘汰
  28. Ingrid在击鼓传花游戏中被淘汰
  29. 胜利者:John
  30. */

回文检查器

回文检查器:

  • 回文是正反都能读通的单词、词组、数或一系列字符的序列,例如:madam 或 racecar。
  • 最简单的检查方式:将字符串反向排列并检查它和原字符串是否相同。

使用双端队列

  1. function palindromeChecker(aString) {
  2. if (aString === undefined || aString === null || (aString !== null && aString.length === 0)) {
  3. return false;
  4. }
  5. const deque = new Deque();
  6. const lowerString = aString.toLocaleLowerCase().split(' ').join('');
  7. let isEqual = true;
  8. let firstChar;
  9. let lastChar;
  10. for (let i = 0; i < lowerString.length; i++) {
  11. deque.addBack(lowerString.charAt(i));
  12. }
  13. while (deque.size() > 1 && isEqual) {
  14. firstChar = deque.removeFront();
  15. lastChar = deque.removeBack();
  16. if (firstChar !== lastChar) {
  17. isEqual = false;
  18. }
  19. }
  20. return isEqual;
  21. }
  22. console.log('a', palindromeChecker('a')); // a true
  23. console.log('level', palindromeChecker('level')); // level true
  24. console.log('JavaScript', palindromeChecker('JavaScript')); // JavaScript false

JavaScript 任务队列

当我们在浏览器中打开新的标签时,就会创建一个任务队列。这是因为每个标签都是单线程处理所有的任务,称为事件循环。

浏览器要负责多个任务,如渲染 HTML、执行 JavaScript 代码、处理用户交互(用户输入、鼠标点击等)、执行和处理异步请求。

更多地了解事件循环