队列的顺序结构——循环队列——入队
//入队
/**
* @brief 入队操作,将元素插入到队列尾部
*
* 该函数用于将一个元素 e 插入到队列 Q 的尾部。
* 在插入元素之前,会检查队列是否已满,如果已满则输出提示信息并返回 0。
* 如果队列未满,则将元素插入到队列尾部,并更新队列的尾指针,最后返回 1 表示插入成功。
*
* @param Q 指向队列的指针
* @param e 要插入的元素
* @return int 插入成功返回 1,队列已满返回 0
*/
int equeue(Queue *Q, ElemType e)
{
// 检查队列是否已满,使用取模运算处理循环队列情况
if ((Q->rear + 1) % MAXSIZE == Q->front)
{
// 若队列已满,输出提示信息
printf("满了\n");
// 返回 0 表示插入失败
return 0;
}
// 将元素 e 插入到队列的尾部
Q->data[Q->rear] = e;
// 更新队列的尾指针,使用取模运算确保尾指针在队列范围内循环
Q->rear = (Q->rear + 1) % MAXSIZE;
// 返回 1 表示插入成功
return 1;
}
为啥要用取模运算?为啥这样算,你咋知道要用取模运算来判断,来写?
-
实现循环效果:循环队列的特点是首尾相连形成一个环状结构。取模运算可以很好地模拟这种循环行为。例如,当 rear 指向数组末尾时,rear + 1 会超出数组范围,但通过取模运算后会回到数组开头,完美地实现了"循环"。
-
简化边界处理:如果不使用取模运算,我们需要额外的条件语句来处理 rear 到达数组末尾的情况。使用取模后,不需要特殊处理,自然就能正确处理边界情况。
-
保持 O(1) 时间复杂度:取模运算的时间复杂度是 O(1),不会影响整体操作的效率。
-
统一计算方式:无论是检查队满还是更新 rear 指针,都使用相同的取模运算,使代码更加简洁和一致。
-
避免溢出错误:在检查队满的条件下,(Q->rear + 1) % MAXSIZE 不会导致整型溢出,因为结果始终在 [0, MAXSIZE-1] 范围内。
- 取模运算用于处理循环队列
- 在循环队列中,队列的存储空间是有限的且被视为循环使用的。例如,假设队列的最大容量为
MAXSIZE
,当队列的尾指针rear
到达MAXSIZE - 1
后,如果再插入元素,尾指针应该回到队列的起始位置(即0
)。 - 取模运算
(Q->rear + 1)%MAXSIZE
可以很方便地实现这种循环效果。当rear
为MAXSIZE - 1
时,(rear+1)%MAXSIZE
就会得到0
,使得尾指针能够正确地循环到队列的开头。
- 在循环队列中,队列的存储空间是有限的且被视为循环使用的。例如,假设队列的最大容量为
- 判断队列已满的原理
- 对于循环队列,判断队列已满的条件是
(Q->rear + 1)%MAXSIZE==Q->front
。这是因为当队列满时,尾指针的下一个位置(即rear+1
,但要考虑循环,所以用取模运算)就是头指针的位置。 - 如果不使用取模运算,在处理循环队列时就很难准确判断队列是否已满以及尾指针的正确更新位置。
- 对于循环队列,判断队列已满的条件是
- 如何确定使用取模运算
- 从循环队列的特性出发,由于队列的存储空间被循环使用,普通的线性操作无法满足其需求。
- 取模运算在数学上正好能够实现这种循环的数值计算。在处理循环结构的索引(如循环队列中的指针)时,取模运算是一种常见且有效的方法来确保索引在有限的范围内循环。
为了彻底理解这个条件,我们通过一个具体例子逐步拆解:
假设队列最大容量 MAXSIZE = 5
此时队列的实际可用空间是 4(因为循环队列需要牺牲一个空间来区分队空和队满)。
场景1:初始空队列
front = 0
(队头指针)rear = 0
(队尾指针)- 队列状态:空队列(
front == rear
)。
复制代码
索引:0 1 2 3 4
数据:[空][空][空][空][空]
↑front
↑rear
场景2:插入4个元素(队列未满)
- 依次插入元素A、B、C、D,此时:
front = 0
,rear = 4
(最后一个元素在索引3,rear
指向下一个空位索引4)。
复制代码
索引:0 1 2 3 4
数据:[A] [B] [C] [D] [空]
↑front ↑rear
- 此时队列未满:
(rear + 1) % MAXSIZE = (4 + 1) % 5 = 0
而front = 0
,所以(rear+1) % MAXSIZE == front
→ 队列满(但实际队列还能插入一个元素?矛盾吗?)
关键点:
循环队列强制牺牲一个空间来区分队空和队满。虽然此时物理上有空位(索引4),但逻辑上认为队列已满(因为再插入元素会导致front == rear
,与队空条件冲突)。
场景3:尝试插入第5个元素(触发队满)
- 插入元素E到索引4,此时:
rear = (4 + 1) % 5 = 0
。
此时front = 0
,rear = 0
→ 与队空条件front == rear
冲突,无法区分空和满。
复制代码
索引:0 1 2 3 4
数据:[A] [B] [C] [D] [E]
↑front
↑rear
因此必须牺牲一个空间:
队列实际最多存储 4 个元素(当 MAXSIZE = 5
),此时条件 (rear + 1) % MAXSIZE == front
能正确判断队满。
最终条件解析
(Q->rear + 1) % MAXSIZE == Q->front
的含义:
- 物理意义:队尾指针的下一个位置(循环意义下)是否与队头指针重合。
- 目的:通过牺牲一个存储空间,避免队空和队满的判断条件冲突。
- 结果:当队列剩余一个空位时,即认为队列已满。