数据结构-03:队列
队列的抽象类
1 | template <class elemType> |
队列的规则是先进先出:只能从队尾入队,从队头出队。可以把它想成排队买票,先进入队伍的人先被服务。后续顺序队列和链式队列的实现差异,主要在于如何高效维护“队头”和“队尾”这两个位置。
顺序存储实现
队列的顺序存储实现看似可以直接用数组,但如果每次出队都把后面的元素整体前移,代价会退化为 。因此更合理的做法是让队头位置也移动起来,只维护队头、队尾两个下标。循环队列进一步把数组尾部和头部逻辑上接起来,让已经出队释放出的空间可以重新使用。
顺序队列的难点不在入队或出队本身,而在数组空间被反复使用后,逻辑顺序和物理下标不再一致。后面的循环队列正是为了解决这个问题。
实现分类
- 使用数组存储队列中的元素。
- 节点个数用 MaxSize 维护。
- 下标范围为 0 到 MaxSize-1。
- 分三种组织方式:
- 队头位置固定:出队会引起大量的数据移动。
- 队头位置不固定:操作为 ,但需要记录队头和队尾的位置,同时会浪费数组空间。
- 循环队列:队头和队尾位置可以循环使用,避免了空间浪费,同时避免大量的数据移动。
循环队列
- front 指向队头前方的位置。
- rear 指向队尾位置。
循环队列的核心是把数组下标看成一个环。普通数组走到末尾就不能继续使用前面释放出来的位置,而循环队列用 % maxSize 让下标回绕,从而复用已经出队的空间。front 不直接指向队头元素,而是指向队头前一格;这样空队列可以统一表示为 front == rear。
操作实现
循环队列的操作要围绕两个判断展开:什么时候为空,什么时候为满。由于 front == rear 既可能表示空,也可能表示满,所以常见做法是牺牲一个数组单元,或者额外维护元素个数。本笔记采用牺牲一个单元的方法,使得空队列和满队列能用两个不同条件区分。
只要记住 front 是队头前一格、rear 是队尾元素位置,入队就是先移动 rear 再写入,出队就是先移动 front 再读出。
改进前:队头可以存储元素
1 | rear = (rear + 1) % maxSize; elem[rear] = x;// 入队 |
- 最后一个元素出队时,front = (front + 1) % maxSize,front 和 rear 相等,表示队列为空。
- 只剩最后一个空位置,执行入队操作,rear = (rear + 1) % maxSize,front 和 rear 相等,表示队列满。
循环队列 front 除了初始状态时,其余时间不一定指向 0 号下标。Front 和 rear“你逃我追”
改进后:队头不存储元素
- “牺牲”一个单元,规定 front 指向的单元不能存储队列元素,只起到标志作用,表示后面一个是队头元素。
- 当
rear“绕一卷”赶上front时,队列就满了。因此队列满的条件是(rear + 1) % maxSize == front。 - 队列空的条件是
rear == front,即队头追上了队尾。
这里故意空出一个单元,是为了区分“空”和“满”。如果不牺牲这个单元,front == rear 既可能表示没有元素,也可能表示数组刚好被填满,代码就必须额外维护元素个数。牺牲一个单元后,判断条件更简单:rear == front 是空,rear 的下一格是 front 则是满。
类定义
1 | template <class elemType> |
类实现
1 | //构造函数 |
这段实现最值得关注的是 doubleSpace()。扩容时不能简单地从旧数组下标 0 开始复制,因为队列元素可能已经在环上“断开”成两段。代码从 front 的后一格开始,按队列逻辑顺序搬移 maxSize - 1 个可能存在的元素,把它们重新整理到新数组的 1..maxSize-1 区间。整理完成后,front 被重置为 0,rear 指向最后一个旧元素的位置,队列又恢复成连续形态。
队列的链表实现
对链式队列的操作基本等同于对链表的操作,只是需要维护队头和队尾指针,限制了修改链表的位置。此处不再实现。
链式队列通常维护两个指针:front 指向队头结点,rear 指向队尾结点。入队时只改队尾附近的指针,出队时只改队头附近的指针。与循环队列相比,链式队列没有固定容量限制,但每个结点需要额外指针空间,且动态分配结点会有开销。
顺序实现与链接实现的比较
- 时间:两者都能在常量的时间 内完成基本操作,但顺序队列由于采用回绕,使入队和出队的处理比较麻烦。
- 空间:链接队列中每一个节点多一个指针字段,空间利用率较高。顺序队列由于长度预指定,数组中可能有大量未使用的空间。
实际选择时,可以按容量是否可预估来判断。若队列最大规模比较稳定,循环队列数组连续、缓存友好,通常很高效;若队列规模变化大或者不希望处理扩容,链式队列更灵活。两者的抽象接口相同,差别主要体现在空间管理和边界条件处理上。






