双向队列
在队列中,我们仅能删除头部元素或在尾部添加元素。如下图所示,双向队列(double-ended queue) 提供了更高的灵活性,允许在头部和尾部执行元素的添加或删除操作。
双向队列常用操作
双向队列的常用操作如下表所示,具体的方法名称需要根据所使用的编程语言来确定。
表 双向队列操作效率
方法名
描述
时间复杂度
push_first()
将元素添加至队首
O ( 1 ) O(1) O ( 1 )
push_last()
将元素添加至队尾
O ( 1 ) O(1) O ( 1 )
pop_first()
删除队首元素
O ( 1 ) O(1) O ( 1 )
pop_last()
删除队尾元素
O ( 1 ) O(1) O ( 1 )
peek_first()
访问队首元素
O ( 1 ) O(1) O ( 1 )
peek_last()
访问队尾元素
O ( 1 ) O(1) O ( 1 )
同样地,我们可以直接使用编程语言中已实现的双向队列类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 from collections import dequedeq: deque[int ] = deque() deq.append(2 ) deq.append(5 ) deq.append(4 ) deq.appendleft(3 ) deq.appendleft(1 ) front: int = deq[0 ] rear: int = deq[-1 ] pop_front: int = deq.popleft() pop_rear: int = deq.pop() size: int = len (deq) is_empty: bool = len (deq) == 0
双向队列实现 *
双向队列的实现与队列类似,可以选择链表或数组作为底层数据结构。
基于双向链表的实现
回顾上一节内容,我们使用普通单向链表来实现队列,因为它可以方便地删除头节点(对应出队操作)和在尾节点后添加新节点(对应入队操作)。
对于双向队列而言,头部和尾部都可以执行入队和出队操作。换句话说,双向队列需要实现另一个对称方向的操作。为此,我们采用“双向链表”作为双向队列的底层数据结构。
如下图所示,我们将双向链表的头节点和尾节点视为双向队列的队首和队尾,同时实现在两端添加和删除节点的功能。
LinkedListDeque push_last() push_first() pop_last() pop_first()
实现代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 class ListNode : """双向链表节点""" def __init__ (self, val: int ): """构造方法""" self .val: int = val self .next : ListNode | None = None self .prev: ListNode | None = None class LinkedListDeque : """基于双向链表实现的双向队列""" def __init__ (self ): """构造方法""" self ._front: ListNode | None = None self ._rear: ListNode | None = None self ._size: int = 0 def size (self ) -> int : """获取双向队列的长度""" return self ._size def is_empty (self ) -> bool : """判断双向队列是否为空""" return self ._size == 0 def push (self, num: int , is_front: bool ): """入队操作""" node = ListNode(num) if self .is_empty(): self ._front = self ._rear = node elif is_front: self ._front.prev = node node.next = self ._front self ._front = node else : self ._rear.next = node node.prev = self ._rear self ._rear = node self ._size += 1 def push_first (self, num: int ): """队首入队""" self .push(num, True ) def push_last (self, num: int ): """队尾入队""" self .push(num, False ) def pop (self, is_front: bool ) -> int : """出队操作""" if self .is_empty(): raise IndexError("双向队列为空" ) if is_front: val: int = self ._front.val fnext: ListNode | None = self ._front.next if fnext != None : fnext.prev = None self ._front.next = None self ._front = fnext else : val: int = self ._rear.val rprev: ListNode | None = self ._rear.prev if rprev != None : rprev.next = None self ._rear.prev = None self ._rear = rprev self ._size -= 1 return val def pop_first (self ) -> int : """队首出队""" return self .pop(True ) def pop_last (self ) -> int : """队尾出队""" return self .pop(False ) def peek_first (self ) -> int : """访问队首元素""" if self .is_empty(): raise IndexError("双向队列为空" ) return self ._front.val def peek_last (self ) -> int : """访问队尾元素""" if self .is_empty(): raise IndexError("双向队列为空" ) return self ._rear.val def to_array (self ) -> list [int ]: """返回数组用于打印""" node = self ._front res = [0 ] * self .size() for i in range (self .size()): res[i] = node.val node = node.next return res
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 """Driver Code""" if __name__ == "__main__" : deque = LinkedListDeque() deque.push_last(3 ) deque.push_last(2 ) deque.push_last(5 ) print ("双向队列 deque =" , deque.to_array()) peek_first: int = deque.peek_first() print ("队首元素 peek_first =" , peek_first) peek_last: int = deque.peek_last() print ("队尾元素 peek_last =" , peek_last) deque.push_last(4 ) print ("元素 4 队尾入队后 deque =" , deque.to_array()) deque.push_first(1 ) print ("元素 1 队首入队后 deque =" , deque.to_array()) pop_last: int = deque.pop_last() print ("队尾出队元素 =" , pop_last, ",队尾出队后 deque =" , deque.to_array()) pop_first: int = deque.pop_first() print ("队首出队元素 =" , pop_first, ",队首出队后 deque =" , deque.to_array()) size: int = deque.size() print ("双向队列长度 size =" , size) is_empty: bool = deque.is_empty() print ("双向队列是否为空 =" , is_empty)
基于数组的实现
如下图所示,与基于数组实现队列类似,我们也可以使用环形数组来实现双向队列。
基于数组实现双向队列的入队出队操作
ArrayDeque push_last() push_first() pop_last() pop_first()
在队列的实现基础上,仅需增加“队首入队”和“队尾出队”的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 class ArrayDeque : """基于环形数组实现的双向队列""" def __init__ (self, capacity: int ): """构造方法""" self ._nums: list [int ] = [0 ] * capacity self ._front: int = 0 self ._size: int = 0 def capacity (self ) -> int : """获取双向队列的容量""" return len (self ._nums) def size (self ) -> int : """获取双向队列的长度""" return self ._size def is_empty (self ) -> bool : """判断双向队列是否为空""" return self ._size == 0 def index (self, i: int ) -> int : """计算环形数组索引""" return (i + self .capacity()) % self .capacity() def push_first (self, num: int ): """队首入队""" if self ._size == self .capacity(): print ("双向队列已满" ) return self ._front = self .index(self ._front - 1 ) self ._nums[self ._front] = num self ._size += 1 def push_last (self, num: int ): """队尾入队""" if self ._size == self .capacity(): print ("双向队列已满" ) return rear = self .index(self ._front + self ._size) self ._nums[rear] = num self ._size += 1 def pop_first (self ) -> int : """队首出队""" num = self .peek_first() self ._front = self .index(self ._front + 1 ) self ._size -= 1 return num def pop_last (self ) -> int : """队尾出队""" num = self .peek_last() self ._size -= 1 return num def peek_first (self ) -> int : """访问队首元素""" if self .is_empty(): raise IndexError("双向队列为空" ) return self ._nums[self ._front] def peek_last (self ) -> int : """访问队尾元素""" if self .is_empty(): raise IndexError("双向队列为空" ) last = self .index(self ._front + self ._size - 1 ) return self ._nums[last] def to_array (self ) -> list [int ]: """返回数组用于打印""" res = [] for i in range (self ._size): res.append(self ._nums[self .index(self ._front + i)]) return res
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 """Driver Code""" if __name__ == "__main__" : deque = ArrayDeque(10 ) deque.push_last(3 ) deque.push_last(2 ) deque.push_last(5 ) print ("双向队列 deque =" , deque.to_array()) peek_first: int = deque.peek_first() print ("队首元素 peek_first =" , peek_first) peek_last: int = deque.peek_last() print ("队尾元素 peek_last =" , peek_last) deque.push_last(4 ) print ("元素 4 队尾入队后 deque =" , deque.to_array()) deque.push_first(1 ) print ("元素 1 队首入队后 deque =" , deque.to_array()) pop_last: int = deque.pop_last() print ("队尾出队元素 =" , pop_last, ",队尾出队后 deque =" , deque.to_array()) pop_first: int = deque.pop_first() print ("队首出队元素 =" , pop_first, ",队首出队后 deque =" , deque.to_array()) size: int = deque.size() print ("双向队列长度 size =" , size) is_empty: bool = deque.is_empty() print ("双向队列是否为空 =" , is_empty)
双向队列应用
双向队列兼具栈与队列的逻辑,因此它可以实现这两者的所有应用场景,同时提供更高的自由度 。
我们知道,软件的“撤销”功能通常使用栈来实现:系统将每次更改操作 push
到栈中,然后通过 pop
实现撤销。然而,考虑到系统资源的限制,软件通常会限制撤销的步数(例如仅允许保存 50 50 50 步)。当栈的长度超过 50 50 50 时,软件需要在栈底(队首)执行删除操作。但栈无法实现该功能,此时就需要使用双向队列来替代栈 。请注意,“撤销”的核心逻辑仍然遵循栈的先入后出原则,只是双向队列能够更加灵活地实现一些额外逻辑。