顺序队列
顺序队列是用一组地址连续的存储单元存放队列中的元素,并附设两个指针:front(队头指针)和rear(队尾指针)。
普通顺序队列的问题
#define MaxSize 100
typedef struct {
int data[MaxSize];
int front; // 队头指针
int rear; // 队尾指针
} SqQueue;
初始化:front = rear = 0
入队:data[rear++] = x
出队:x = data[front++]
循环队列
循环队列将数组的首尾相连,形成一个环。当指针到达数组末尾时,下一个位置回到数组开头。这样可以重复利用数组空间,避免假溢出。
存储结构
#define MaxSize 100
typedef struct {
int data[MaxSize];
int front; // 队头指针,指向队头元素
int rear; // 队尾指针,指向队尾元素的下一个位置
} CirQueue;
初始化
void InitQueue(CirQueue &Q) {
Q.front = Q.rear = 0;
}
判空
bool QueueEmpty(CirQueue Q) {
return Q.front == Q.rear;
}
判满
bool QueueFull(CirQueue Q) {
return (Q.rear + 1) % MaxSize == Q.front;
}
(rear + 1) % MaxSize == front,而不是 rear == front!因为 rear == front 同时也是队空条件。为了区分队空和队满,循环队列牺牲一个存储单元,当队尾指针的下一个位置是队头时,认为队列已满。入队
bool EnQueue(CirQueue &Q, int x) {
if (QueueFull(Q)) return false; // 队满
Q.data[Q.rear] = x;
Q.rear = (Q.rear + 1) % MaxSize; // rear后移,取模实现循环
return true;
}
出队
bool DeQueue(CirQueue &Q, int &x) {
if (QueueEmpty(Q)) return false; // 队空
x = Q.data[Q.front];
Q.front = (Q.front + 1) % MaxSize; // front后移,取模实现循环
return true;
}
求队列长度
int QueueLength(CirQueue Q) {
return (Q.rear - Q.front + MaxSize) % MaxSize;
}
时间复杂度:所有操作均为 。
区分循环队列队空和队满的三种方法:
- 牺牲一个存储单元(本文采用):队满条件
(rear+1)%MaxSize == front - 增设size成员:记录队列中元素的个数,队满时 size == MaxSize
- 增设tag成员:tag=0表示最近一次操作是删除,tag=1表示最近一次操作是插入。当 front==rear 且 tag==0 时队空,当 front==rear 且 tag==1 时队满
习题
习题 1
循环队列用数组A[0..m-1]存放元素,已知其队头指针front和队尾指针rear(rear指向队尾元素的下一个位置),则当前队列中的元素个数是( )
A. (rear - front + m) % m
B. rear - front + 1
C. rear - front
D. (rear - front) % m
答案:A
解析: 循环队列中,由于指针可能循环(rear可能小于front),所以需要加上m再取模来保证结果为正。
元素个数 = (rear - front + m) % m
- 当 rear >= front 时,结果为 rear - front
- 当 rear < front 时,结果为 rear - front + m(绕了一圈)
习题 2
设循环队列的存储空间为Q[1..35],初始状态为front=rear=35。现经过一系列入队与退队运算后,front=15,rear=15,则循环队列中的元素个数为( )
A. 15 B. 16 C. 20 D. 0或35
答案:D
解析: 在循环队列中,当 front == rear 时,可能是队空(0个元素),也可能是队满(35个元素,因为牺牲了一个空间来区分队空和队满,所以最多35个元素)。
仅根据 front == rear 无法判断是队空还是队满,所以元素个数可能是0或35。
习题 3
什么是循环队列?为什么要使用循环队列?如何区分循环队列的队空和队满?
什么是循环队列: 循环队列是将顺序队列的数组首尾相连,形成一个环。当队头或队尾指针到达数组末尾时,下一个位置回到数组开头。通过取模运算(%)实现指针的循环移动。
为什么要使用循环队列: 普通顺序队列存在”假溢出”问题:随着入队和出队操作,front和rear指针都不断后移,当rear到达数组末尾时,即使数组前面还有空闲空间(因为出队操作释放了前面的空间),也无法再入队。循环队列通过将数组首尾相连,重复利用前面的空闲空间,解决了假溢出问题,提高了空间利用率。
如何区分队空和队满: 循环队列中,队空和队满时都可能出现 front == rear 的情况,需要额外的方法来区分:
-
牺牲一个存储单元(最常用):约定队尾指针的下一个位置是队头时为队满。
- 队空条件:
front == rear - 队满条件:
(rear + 1) % MaxSize == front - 队列最多存放 MaxSize-1 个元素
- 队空条件:
-
增设size成员:记录队列中元素的个数。
- 队空条件:
size == 0 - 队满条件:
size == MaxSize
- 队空条件:
-
增设tag成员:记录最近一次操作的类型。
- tag=0表示最近一次是删除操作,tag=1表示最近一次是插入操作
- 队空条件:
front == rear && tag == 0 - 队满条件:
front == rear && tag == 1
