当前位置:   article > 正文

《双向队列》

双向队列

介绍

双向队列(deque,全名double-ended queue)是一种具有队列和栈性质的数据结构。

对于队列,我们只能在头部删除或在尾部添加元素,

而「双向队列 Deque」更加灵活,在其两端都能执行元素添加或删除操作。

常用操作

方法名

描述

时间复杂度

pushFirst()

将 元素添加至队首

O(1)

pushLast()

将元素添加至队尾

O(1)

pollFirst()

删除队首元素

O(1)

pollLast()

删除队尾元素

O(1)

peekFirst()

访问队首元素

O(1)

peekLast()

访问队尾元素

O(1)

size()

获取队列的长度

O(1)

isEmpty()

判断队列是否为空

O(1)

  1. /*初始化双向队列*/
  2. Deque<Integer> deque = new LinkedList<>();
  3. /*元素入队*/
  4. deque.offerLast(2);//添加至队尾
  5. deque.offerLast(5);
  6. deque.offerLast(4);
  7. deque.offerFirst(3); // 添加至队首
  8. deque.offerFirst(1);
  9. /* 访问元素 */
  10. int peekFirst = deque.peekFirst(); // 队首元素
  11. int peekLast = deque.peekLast(); // 队尾元素
  12. /*元素出队*/
  13. int popFirst = deque.pollFirst(); // 队首元素出队
  14. int popLast = deque.pollLast();// 队尾元素出队
  15. /*获取双向队列的长度 */
  16. int size = deque.size();
  17. /*判断双向队列是否为空*/
  18. boolean isEmpty = deque.isEmpty();

实现

与队列类似,双向队列同样可以使用链表数组来实现

1.基于双向链表的实现

由于可以方便地删除链表头结点(即出队),以及在链表尾结点后添加新结点(即入队),因此我们使用普通单向链表来实现队列,

而双向队列的头部和尾部都可以执行入队与出队操作

换言之,双向队列的操作是“首尾对称”的,也需要实现另一个对称方向的操作

因此,双向队列需要使用「双向链表」来实现

双向链表的头结点和尾结点分别看作双向队列的队首和队尾,并且实现在两端都能添加与删除结点

  1. /*双向链表节点*/
  2. class ListNode {
  3. int val; // 节点值
  4. ListNode next; // 后继节点引用(指针)
  5. ListNode prev; // 前驱节点引用(指针)
  6. ListNode(int val) {
  7. this.val = val;
  8. prev = next = null;
  9. }
  10. }
  11. /*基于双向链表实现的双向队列*/
  12. class LinkedListDeque {
  13. private ListNode front, rear; // 头节点front ,尾节点rear
  14. private int queSize = 0; // 双向队列的长度
  15. public LinkedListDeque() {
  16. front = rear = null;
  17. }
  18. /*获取双向队列的长度*/
  19. public int size() {
  20. return queSize;
  21. }
  22. /*判断双向队列是否为空*/
  23. public boolean isEmpty() {
  24. return size() == 0;
  25. }
  26. /*入队操作*/
  27. private void push(int num, boolean isFront) {
  28. ListNode node = new ListNode(num);
  29. // 若链表为空,则令front,rear都指向node
  30. if (isEmpty()) {
  31. front = rear = node;
  32. } else if (isFront) { // 队首入队操作
  33. // 将node添加至链表头部
  34. front.prev = node;
  35. node.next = front;
  36. front = node; // 更新头节点
  37. } else { // 队尾入队操作
  38. // 将node添加至链表尾部
  39. rear.next = node;
  40. node.prev = rear;
  41. rear = node; // 更新尾节点
  42. }
  43. queSize++; // 更新队列长度
  44. }
  45. /*队首入队*/
  46. public void pushFirst(int num) {
  47. push(num, true);
  48. }
  49. /*队尾入队*/
  50. public void pushLast(int num) {
  51. push(num, false);
  52. }
  53. /*出队操作*/
  54. private Integer pop(boolean isFront) {
  55. // 若队列为空,直接返回null
  56. if (isEmpty()) {
  57. return null;
  58. }
  59. int val;
  60. if (isFront) { // 队首出队操作
  61. val = front.val; // 暂存头节点值
  62. // 删除头节点
  63. ListNode fNext = front.next;
  64. if (fNext != null) {
  65. fNext.prev = null;
  66. front.next = null;
  67. }
  68. front = fNext; // 更新头节点
  69. } else { // 队尾出队操作
  70. val = rear.val; // 暂存尾节点值
  71. // 删除尾节点
  72. ListNode rPrev = rear.prev;
  73. if (rPrev != null) {
  74. rPrev.next = null;
  75. rear.prev = null;
  76. }
  77. rear = rPrev; // 更新尾节点
  78. }
  79. queSize--; // 更新队列长度
  80. return val;
  81. }
  82. /*队首出队*/
  83. public Integer popFirst() {
  84. return pop(true);
  85. }
  86. /*队尾出队*/
  87. public Integer popLast() {
  88. return pop(false);
  89. }
  90. /*访问队首元素*/
  91. public Integer peekFirst() {
  92. return isEmpty() ? null : front.val;
  93. }
  94. /*访问队尾元素*/
  95. public Integer peekLast() {
  96. return isEmpty() ? null : rear.val;
  97. }
  98. /*返回数组用于打印*/
  99. public int[] toArray() {
  100. ListNode node = front;
  101. int[] res = new int[size()];
  102. for (int i = 0; i < res.length; i++) {
  103. res[i] = node.val;
  104. node = node.next;
  105. }
  106. return res;
  107. }
  108. }

2.基于环形数组的实现

与基于数组实现队列类似,我们也可以使用环形数组来实现双向队列

在实现队列的基础上,增加实现“队首入队”和“队尾出队”方法即可

  1. /*基于环形数组实现的双向队列*/
  2. class CircularDeque {
  3. private int capacity; // 队列容量
  4. private int[] arr; // 存储数据的数组
  5. private int front; // 头指针,指向队头元素的位置
  6. private int rear; // 尾指针,指向队尾元素的下一个位置
  7. public CircularDeque(int k) {
  8. capacity = k + 1;
  9. arr = new int[capacity];
  10. front = 0;
  11. rear = 0;
  12. }
  13. /* 获取双向队列的长度 */
  14. public int size() {
  15. return (rear - front + capacity) % capacity;
  16. }
  17. /* 判断双向队列是否为空 */
  18. public boolean isEmpty() {
  19. return front == rear;
  20. }
  21. /* 判断双向队列是否已满 */
  22. public boolean isFull() {
  23. return (rear + 1) % capacity == front;
  24. }
  25. /* 从队头插入元素 */
  26. public boolean insertFront(int value) {
  27. if (isFull()) { // 队列已满
  28. return false;
  29. }
  30. front = (front - 1 + capacity) % capacity; // 计算新的头指针位置
  31. arr[front] = value;
  32. return true;
  33. }
  34. /* 从队尾插入元素 */
  35. public boolean insertLast(int value) {
  36. if (isFull()) { // 队列已满
  37. return false;
  38. }
  39. arr[rear] = value;
  40. rear = (rear + 1) % capacity; // 计算新的尾指针位置
  41. return true;
  42. }
  43. /* 从队头删除元素 */
  44. public boolean deleteFront() {
  45. if (isEmpty()) { // 队列为空
  46. return false;
  47. }
  48. front = (front + 1) % capacity; // 计算新的头指针位置
  49. return true;
  50. }
  51. /* 从队尾删除元素 */
  52. public boolean deleteLast() {
  53. if (isEmpty()) { // 队列为空
  54. return false;
  55. }
  56. rear = (rear - 1 + capacity) % capacity; // 计算新的尾指针位置
  57. return true;
  58. }
  59. /* 获取队头元素 */
  60. public int getFront() {
  61. if (isEmpty()) { // 队列为空
  62. return -1;
  63. }
  64. return arr[front];
  65. }
  66. /* 获取队尾元素 */
  67. public int getRear() {
  68. if (isEmpty()) { // 队列为空
  69. return -1;
  70. }
  71. return arr[(rear - 1 + capacity) % capacity];
  72. }
  73. /* 入队操作 */
  74. private void enqueue(int num, boolean isFront) {
  75. if (isFull()) { // 队列已满,无法入队
  76. return;
  77. }
  78. if (isFront) { // 头部入队
  79. front = (front - 1 + capacity) % capacity; // 新的头指针位置
  80. arr[front] = num;
  81. } else { // 尾部入队
  82. arr[rear] = num;
  83. rear = (rear + 1) % capacity; // 新的尾指针位置
  84. }
  85. }
  86. /* 头部入队 */
  87. public void enqueueFront(int num) {
  88. enqueue(num, true);
  89. }
  90. /* 尾部入队 */
  91. public void enqueueRear(int num) {
  92. enqueue(num, false);
  93. }
  94. /* 出队操作 */
  95. private Integer dequeue(boolean isFront) {
  96. if (isEmpty()) { // 队列为空,无法出队
  97. return null;
  98. }
  99. int res;
  100. if (isFront) { // 头部出队
  101. res = arr[front];
  102. front = (front + 1) % capacity; // 新的头指针位置
  103. } else { // 尾部出队
  104. rear = (rear - 1 + capacity) % capacity; // 新的尾指针位置
  105. res = arr[rear];
  106. }
  107. return res;
  108. }
  109. /* 头部出队 */
  110. public Integer dequeueFront() {
  111. return dequeue(true);
  112. }
  113. /* 尾部出队 */
  114. public Integer dequeueRear() {
  115. return dequeue(false);
  116. }
  117. /* 打印输出双向队列中的元素 */
  118. public void printDeque() {
  119. if (isEmpty()) { // 队列为空
  120. System.out.println("[]");//输出空列表[]
  121. return;
  122. }
  123. StringBuilder sb = new StringBuilder("[");
  124. for (int i = front; i != rear; i = (i + 1) % capacity) {
  125. sb.append(arr[i]);
  126. if ((i + 1) % capacity != rear) {
  127. sb.append(", ");
  128. }
  129. }
  130. sb.append("]");
  131. System.out.println(sb.toString());
  132. }
  133. }
  134. //点提:修改头指针或尾指针时需要取模->保证它们的范围在[0, capacity)之间

环形数组实现双向队列,需要注意头指针和尾指针的计算方法,以及队列判空和判满的条件。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/891760
推荐阅读
相关标签
  

闽ICP备14008679号