队列(Queue) 是一种抽象的线性数据结构,其核心约束只有一句话 —— 先入先出。
1 队列
队列 (Queue) 是一种遵循先入先出规则的线性数据结构。顾名思义,队列模拟了排队现象,即新来的人不断加入队列尾部,而位于队列头部的人逐个离开。
如 图 1-1 所示,我们将队列头部称为“队首”,尾部称为“队尾”,将把元素加入队尾的操作称为“入队”,删除队首元素的操作称为“出队”。
1.1 队列常用操作
队列的常见操作如下表所示。需要注意的是,不同编程语言的方法名称可能会有所不同。我们在此采用与栈相同的方法命名。
方法名
描述
时间复杂度
push()
元素入队,即将元素添加至队尾
O ( 1 ) O(1) O ( 1 )
pop()
队首元素出队
O ( 1 ) O(1) O ( 1 )
peek()
访问队首元素
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 dequeque: deque[int ] = deque() que.append(1 ) que.append(3 ) que.append(2 ) que.append(5 ) que.append(4 ) front: int = que[0 ] pop: int = que.popleft() size: int = len (que) is_empty: bool = len (que) == 0
2 队列实现
为了实现队列,我们需要一种数据结构,可以在一端添加元素,并在另一端删除元素,链表和数组都符合要求。
2.1 基于链表的实现
如 图 2-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 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 typedef struct { ListNode *front, *rear; int queSize; } LinkedListQueue; LinkedListQueue *newLinkedListQueue () { LinkedListQueue *queue = (LinkedListQueue *)malloc (sizeof (LinkedListQueue)); queue ->front = NULL ; queue ->rear = NULL ; queue ->queSize = 0 ; return queue ; } void delLinkedListQueue (LinkedListQueue *queue ) { while (queue ->front != NULL ) { ListNode *tmp = queue ->front; queue ->front = queue ->front->next; free (tmp); } free (queue ); } int size (LinkedListQueue *queue ) { return queue ->queSize; } bool empty (LinkedListQueue *queue ) { return (size(queue ) == 0 ); } void push (LinkedListQueue *queue , int num) { ListNode *node = newListNode(num); if (queue ->front == NULL ) { queue ->front = node; queue ->rear = node; } else { queue ->rear->next = node; queue ->rear = node; } queue ->queSize++; } int peek (LinkedListQueue *queue ) { assert(size(queue ) && queue ->front); return queue ->front->val; } int pop (LinkedListQueue *queue ) { int num = peek(queue ); ListNode *tmp = queue ->front; queue ->front = queue ->front->next; free (tmp); queue ->queSize--; return num; } void printLinkedListQueue (LinkedListQueue *queue ) { int *arr = malloc (sizeof (int ) * queue ->queSize); int i; ListNode *node; for (i = 0 , node = queue ->front; i < queue ->queSize; i++) { arr[i] = node->val; node = node->next; } printArray(arr, queue ->queSize); free (arr); }
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 class LinkedListQueue : """基于链表实现的队列""" 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 ): """入队""" node = ListNode(num) if self ._front is None : self ._front = node self ._rear = node else : self ._rear.next = node self ._rear = node self ._size += 1 def pop (self ) -> int : """出队""" num = self .peek() self ._front = self ._front.next self ._size -= 1 return num def peek (self ) -> int : """访问队首元素""" if self .is_empty(): raise IndexError("队列为空" ) return self ._front.val def to_list (self ) -> list [int ]: """转化为列表用于打印""" queue = [] temp = self ._front while temp: queue.append(temp.val) temp = temp.next return queue
2.2 基于数组的实现
在数组中删除首元素的时间复杂度为 O ( n ) O(n) O ( n ) ,这会导致出队操作效率较低。然而,我们可以采用一下巧妙的方法来避免这个问题。
我们可以使用一个变量 front 指向队首元素的索引,并维护一个变量 size 用于记录队列长度。定义 rear = front + size ,这个公式计算出的 rear 指向队尾元素之后的下一个位置。
基于此设计,数组中包含元素的有效区间为 [front, rear - 1] ,各种操作的实现方法如 图 3-2 所示。
入队操作: 将输入元素赋值给 rear 索引处,并将 size 增加 1 。
出队操作: 只需将 front 增加 1 ,并将 size 减少 1 。
可以看到,入队和出队操作都只需进行一次操作,时间复杂度均为 O ( 1 ) O(1) O ( 1 ) 。
你可能会发现一个问题:在不断进行入队和出队的过程中, front 和 rear 都在向右移动,当他们到达数组尾部时就无法继续移动了 。为了解决此问题,我们可以将数组视为首尾相接的“环形数组”。
对于环形数组,我们需要让 front 或 rear 在越过数组尾部时,直接回到数组头部继续遍历。这种周期性规律可以通过“取余操作”来实现,代码如下所示:
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 typedef struct { int *nums; int front; int queSize; int queCapacity; } ArrayQueue; ArrayQueue *newArrayQueue (int capacity) { ArrayQueue *queue = (ArrayQueue *)malloc (sizeof (ArrayQueue)); queue ->queCapacity = capacity; queue ->nums = (int *)malloc (sizeof (int ) * queue ->queCapacity); queue ->front = queue ->queSize = 0 ; return queue ; } void delArrayQueue (ArrayQueue *queue ) { free (queue ->nums); free (queue ); } int capacity (ArrayQueue *queue ) { return queue ->queCapacity; } int size (ArrayQueue *queue ) { return queue ->queSize; } bool empty (ArrayQueue *queue ) { return queue ->queSize == 0 ; } int peek (ArrayQueue *queue ) { assert(size(queue ) != 0 ); return queue ->nums[queue ->front]; } void push (ArrayQueue *queue , int num) { if (size(queue ) == capacity(queue )) { printf ("队列已满\r\n" ); return ; } int rear = (queue ->front + queue ->queSize) % queue ->queCapacity; queue ->nums[rear] = num; queue ->queSize++; } int pop (ArrayQueue *queue ) { int num = peek(queue ); queue ->front = (queue ->front + 1 ) % queue ->queCapacity; queue ->queSize--; return num; } int *toArray (ArrayQueue *queue , int *queSize) { *queSize = queue ->queSize; int *res = (int *)calloc (queue ->queSize, sizeof (int )); int j = queue ->front; for (int i = 0 ; i < queue ->queSize; i++) { res[i] = queue ->nums[j % queue ->queCapacity]; j++; } 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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 class ArrayQueue : """基于环形数组实现的队列""" def __init__ (self, size: int ): """构造方法""" self ._nums: list [int ] = [0 ] * size 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 push (self, num: int ): """入队""" if self ._size == self .capacity(): raise IndexError("队列已满" ) rear: int = (self ._front + self ._size) % self .capacity() self ._nums[rear] = num self ._size += 1 def pop (self ) -> int : """出队""" num: int = self .peek() self ._front = (self ._front + 1 ) % self .capacity() self ._size -= 1 return num def peek (self ) -> int : """访问队首元素""" if self .is_empty(): raise IndexError("队列为空" ) return self ._nums[self ._front] def to_list (self ) -> list [int ]: """返回列表用于打印""" res = [0 ] * self .size() j: int = self ._front for i in range (self .size()): res[i] = self ._nums[(j % self .capacity())] j += 1 return res
以上实现的队列仍然具有局限性:其长度不可变。然而,这个问题不难解决,我们可以将数组替换为动态数组,从而引入扩容机制。有兴趣的读者可以尝试自行实现。两种实现的对比结论与栈一致,在此不再赘述。
注意
一般,在嵌入式,或者说在固件、裸机里尽量不使用动态数组。
3 双向队列
在队列中,我们仅能删除头部元素或在尾部添加元素。双向队列 (double-ended queue) 如 图 3-1 所示,提供了更高的灵活性,允许在头部和尾部执行元素的添加或删除操作。
3.1 双向队列常用操作
双向队列的常用操作如下表所示,具体的方法名称需要根据所使用的编程语言来确定。
方法名
描述
时间复杂度
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
4 双向队列实现
双向队列的实现与队列类似,可以选择链表或数组作为底层结构。
4.1 基于双向链表的实现
在 队列 中我们使用普通单向链表来实现队列,因为它可以方便地删除头节点(对应出队操作)和在尾节点后添加新节点(对应入队操作)。
对于双向队列而言,头部和尾部都可以执行入队和出队操作。换句话说,双向队列需要实现另一个对称方向的操作。为此,我们采用“双向链表”作为双向队列的底层数据结构。
如 图 4-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 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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 typedef struct DoublyListNode { int val; struct DoublyListNode *next ; struct DoublyListNode *prev ; } DoublyListNode; DoublyListNode *newDoublyListNode (int num) { DoublyListNode *new = (DoublyListNode *)malloc (sizeof (DoublyListNode)); new->val = num; new->next = NULL ; new->prev = NULL ; return new; } void delDoublyListNode (DoublyListNode *node) { free (node); } typedef struct { DoublyListNode *front, *rear; int queSize; } LinkedListDeque; LinkedListDeque *newLinkedListDeque () { LinkedListDeque *deque = (LinkedListDeque *)malloc (sizeof (LinkedListDeque)); deque ->front = NULL ; deque ->rear = NULL ; deque ->queSize = 0 ; return deque ; } void delLinkedListdeque (LinkedListDeque *deque ) { for (int i = 0 ; i < deque ->queSize && deque ->front != NULL ; i++) { DoublyListNode *tmp = deque ->front; deque ->front = deque ->front->next; free (tmp); } free (deque ); } int size (LinkedListDeque *deque ) { return deque ->queSize; } bool empty (LinkedListDeque *deque ) { return (size(deque ) == 0 ); } void push (LinkedListDeque *deque , int num, bool isFront) { DoublyListNode *node = newDoublyListNode(num); if (empty(deque )) { deque ->front = deque ->rear = node; } else if (isFront) { deque ->front->prev = node; node->next = deque ->front; deque ->front = node; } else { deque ->rear->next = node; node->prev = deque ->rear; deque ->rear = node; } deque ->queSize++; } void pushFirst (LinkedListDeque *deque , int num) { push(deque , num, true ); } void pushLast (LinkedListDeque *deque , int num) { push(deque , num, false ); } int peekFirst (LinkedListDeque *deque ) { assert(size(deque ) && deque ->front); return deque ->front->val; } int peekLast (LinkedListDeque *deque ) { assert(size(deque ) && deque ->rear); return deque ->rear->val; } int pop (LinkedListDeque *deque , bool isFront) { if (empty(deque )) return -1 ; int val; if (isFront) { val = peekFirst(deque ); DoublyListNode *fNext = deque ->front->next; if (fNext) { fNext->prev = NULL ; deque ->front->next = NULL ; } delDoublyListNode(deque ->front); deque ->front = fNext; } else { val = peekLast(deque ); DoublyListNode *rPrev = deque ->rear->prev; if (rPrev) { rPrev->next = NULL ; deque ->rear->prev = NULL ; } delDoublyListNode(deque ->rear); deque ->rear = rPrev; } deque ->queSize--; return val; } int popFirst (LinkedListDeque *deque ) { return pop(deque , true ); } int popLast (LinkedListDeque *deque ) { return pop(deque , false ); } void printLinkedListDeque (LinkedListDeque *deque ) { int *arr = malloc (sizeof (int ) * deque ->queSize); int i; DoublyListNode *node; for (i = 0 , node = deque ->front; i < deque ->queSize; i++) { arr[i] = node->val; node = node->next; } printArray(arr, deque ->queSize); free (arr); }
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 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 is not 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 is not 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
4.2 基于数组的实现
如 图 4-2 所示,与基于数组实现队列类似,我们也可以使用环形数组来实现双向队列。
在队列的实现基础上,仅需增加 “队首入队” 和 “队尾出队” 的方法:
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 109 110 111 112 113 114 115 116 typedef struct { int *nums; int front; int queSize; int queCapacity; } ArrayDeque; ArrayDeque *newArrayDeque (int capacity) { ArrayDeque *deque = (ArrayDeque *)malloc (sizeof (ArrayDeque)); deque ->queCapacity = capacity; deque ->nums = (int *)malloc (sizeof (int ) * deque ->queCapacity); deque ->front = deque ->queSize = 0 ; return deque ; } void delArrayDeque (ArrayDeque *deque ) { free (deque ->nums); free (deque ); } int capacity (ArrayDeque *deque ) { return deque ->queCapacity; } int size (ArrayDeque *deque ) { return deque ->queSize; } bool empty (ArrayDeque *deque ) { return deque ->queSize == 0 ; } int dequeIndex (ArrayDeque *deque , int i) { return ((i + capacity(deque )) % capacity(deque )); } void pushFirst (ArrayDeque *deque , int num) { if (deque ->queSize == capacity(deque )) { printf ("双向队列已满\r\n" ); return ; } deque ->front = dequeIndex(deque , deque ->front - 1 ); deque ->nums[deque ->front] = num; deque ->queSize++; } void pushLast (ArrayDeque *deque , int num) { if (deque ->queSize == capacity(deque )) { printf ("双向队列已满\r\n" ); return ; } int rear = dequeIndex(deque , deque ->front + deque ->queSize); deque ->nums[rear] = num; deque ->queSize++; } int peekFirst (ArrayDeque *deque ) { assert(empty(deque ) == 0 ); return deque ->nums[deque ->front]; } int peekLast (ArrayDeque *deque ) { assert(empty(deque ) == 0 ); int last = dequeIndex(deque , deque ->front + deque ->queSize - 1 ); return deque ->nums[last]; } int popFirst (ArrayDeque *deque ) { int num = peekFirst(deque ); deque ->front = dequeIndex(deque , deque ->front + 1 ); deque ->queSize--; return num; } int popLast (ArrayDeque *deque ) { int num = peekLast(deque ); deque ->queSize--; return num; } int *toArray (ArrayDeque *deque , int *queSize) { *queSize = deque ->queSize; int *res = (int *)calloc (deque ->queSize, sizeof (int )); int j = deque ->front; for (int i = 0 ; i < deque ->queSize; i++) { res[i] = deque ->nums[j % deque ->queCapacity]; j++; } 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 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
5 典型应用
5.1 队列
5.2 双向队列应用
双向队列兼具栈与队列的逻辑,因此它可以实现这两者的所有应用场景,同时提供更高的自由度。
我们知道,软件的“撤销”功能通常使用栈来实现:系统将每次更改操作 push 到栈中,然后通过 pop 实现撤销。然而,考虑到系统资源的限制,软件通常会限制撤销的步数(例如仅允许保存 50 步)。当栈的长度超过 50 时,软件需要在栈底(队首)执行删除操作。但栈无法实现该功能,此时就需要使用双向队列来替代栈。请注意,“撤销”的核心逻辑仍然遵循栈的先入后出原则,只是双向队列能够更加灵活地实现一些额外逻辑。