循环队列:出队

参考视频

5.双向链表_栈_队列_哔哩哔哩_bilibili1小时15分钟到最后(顺序队列的知识)

6.循环队列_讲题_递归_哔哩哔哩_bilibili前20分钟

题目

复制代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define  ElemType  char
#define  NumSize   100
typedef struct 
{
    ElemType dat[NumSize]; //顺序队列
    int real,front;          //队列首、尾指针
}QueueList;
int initQueueList(QueueList *Q) //初始化顺序栈
{
    Q->real =0;
    Q->front =0;
    return (1);
}

int LengthQueueList(QueueList Q) //求队列的长度
{ 
    int i;
    i= (Q.real - Q.front + NumSize) % NumSize; //求队列中数据的总长度
    return(i);//返回队列数据的总数  说明:队列中数据总数 = 最大值 - 1 表明队列满
}

int ListQueueEmpty(QueueList Q)
{
    if(Q.real == Q.front )
    {
        return(0); //队列空返回0
    }    
    return (1);//不空返回1
} 

int ENQueueList(QueueList *Q,ElemType x)//数据入队
{
    
    if((Q->real +1) % NumSize ==  Q->front)
    {
        printf("队列满失败\n");
        return 0;//队列满返回0
    }    
    Q->dat[Q->real] =x;//x入队
    Q->real =( Q->real + 1)% NumSize; //队尾指针加1
    return (1);//入队成功返回1
}
/* 本题要求子函数------出队列 */
int DeQueueList(QueueList *Q,ElemType *x);
int main(void)
{
    int  i;
    ElemType x;    
    QueueList q;//定义队列q        
    initQueueList(&q);//初始化队列
    // 输入11个字符的字符串
    for (i = 0; i < 11; i++)//数据入队列
    {
        x = getchar();        
        ENQueueList(&q, x);
    }
    i = 0;
    while (LengthQueueList(q) != 1) //队列中数据个数据不为1循环执行
    {
        DeQueueList(&q, &x);
            ++i;
        if (i < 3)    ENQueueList(&q, x);
        else
        {    i = 0;
            printf("%c",x);//出列的数据元素一个一个地显示出来
        }
    }
    DeQueueList(&q, &x);//最后一个数据出队 且出队数据放到x中
    printf("%c", x);//显示最后一个数据元素
    return 0;
}

/* 请在这里填写答案 */

解析

普通的循环队列的出队,解决这个需要队列的前置知识,建议参考视频和下面笔记

答案

复制代码
int DeQueueList(QueueList *Q,ElemType *x){
    if(Q->front==Q->real){
        return 0;
    }
    *x=Q->dat[Q->front];
    Q->front=(Q->front+1)%NumSize;
    return 1;
}

前置知识

一、队列的核心概念

1. 基本定义

队列是一种线性数据结构,只允许在队尾(Rear) 插入新元素(入队),在队头(Front) 删除元素(出队),整体遵循 "先进先出(FIFO, First In First Out)" 原则。可以类比生活中的排队:先到的人先办理业务,后到的人只能排在队尾。

2. 核心特点

  • 操作受限:仅支持两端操作,队尾入、队头出,中间元素不可直接访问 / 修改;
  • 顺序性:元素的出队顺序与入队顺序完全一致;
  • 常见分类:
    • 普通顺序队列:基于数组实现,易出现 "假溢出" 问题;
    • 循环队列:对普通顺序队列的优化,通过取模实现数组空间的循环利用,解决假溢出。

二、队列的基础结构定义

无论普通顺序队列还是循环队列,都可以基于数组实现,先定义统一的结构体(需提前宏定义数组最大容量):

复制代码
// 宏定义队列的最大容量
#define MAXSIZE 100
// 定义队列元素类型(可根据需求修改,如char、int等)
typedef int ElemType;

// 队列的结构体定义
typedef struct Queue {
    ElemType data[MAXSIZE];  // 存储队列元素的数组
    int front;               // 队头指针:指向队头元素的位置
    int rear;                // 队尾指针:指向队尾元素的下一个位置
} Queue;

关键说明

  • frontrear 初始值均为 0,此时 front == rear 表示队列为空;
  • 数组 data 是队列的底层存储容器,MAXSIZE 决定队列能容纳的最大元素数。

三、队列的通用基础操作

1. 队列初始化

功能

将队列初始化为空状态,是使用队列的第一步。

复制代码
// 初始化队列:将队头、队尾指针置0
void initQueue(Queue *Q) {
    Q->front = 0;  // 队头指针归0
    Q->rear = 0;   // 队尾指针归0
}
可视化展示

假设 MAXSIZE=5,初始化后队列的状态如下( 表示空位置,front/rear 用箭头标注位置):

plaintext

复制代码
数组索引: 0    1    2    3    4
data:    □    □    □    □    □
          ↑
        front
          ↑
        rear

说明 :初始化后 Q->front == Q->rear,这是判断队空的核心条件。

2. 队列判空

功能:判断队列是否为空,为空返回 1,非空返回 0(新手易混淆返回值逻辑,需注意)。

复制代码
// 判空函数:front == rear 代表队空
int isEmpty(Queue *Q) {
    if (Q->front == Q->rear) {
        return 1;  // 队空,返回1
    } else {
        return 0;  // 队非空,返回0
    }
}

四、循环队列的核心操作(解决假溢出)

普通顺序队列的 "假溢出" 是指:数组未存满,但队尾指针已到数组末尾,无法继续入队。循环队列通过 "取模运算" 让指针绕回数组开头,彻底解决该问题。

1. 循环队列入队

功能

将元素插入队尾,成功返回 1,队列满则返回 0(新手易写错判满条件)。

复制代码
// 循环队列入队函数
int enqueueCircular(Queue *Q, ElemType e) {
    // 循环队列判满条件:(rear + 1) % MAXSIZE == front(预留一个空位避免与队空混淆)
    if ((Q->rear + 1) % MAXSIZE == Q->front) {
        printf("循环队列已满,入队失败\n");
        return 0;
    }
    // 将元素存入队尾指针指向的位置
    Q->data[Q->rear] = e;
    // 队尾指针后移(取模实现"循环",超出数组长度则绕回开头)
    Q->rear = (Q->rear + 1) % MAXSIZE;
    return 1;  // 入队成功
}
可视化展示(以 MAXSIZE=5 为例)
  • 初始状态:front=0,rear=0,队列为空;

  • 入队元素 1:data[0]=1rear=(0+1)%5=1

    plaintext

    复制代码
    数组索引: 0    1    2    3    4
    data:    1    □    □    □    □
              ↑    ↑
            front  rear
  • 继续入队 2、3、4:rear=4

    plaintext

    复制代码
    数组索引: 0    1    2    3    4
    data:    1    2    3    4    □
              ↑                   ↑
            front                rear
  • 此时队未满((4+1)%5=0 == front 才满),再入队 5:data[4]=5rear=(4+1)%5=0

    plaintext

    复制代码
    数组索引: 0    1    2    3    4
    data:    1    2    3    4    5
              ↑                   ↑
            front                rear(rear=0)

    此时 (rear+1)%5=1 != front,仍可入队?不,此时 (0+1)%5=1≠0,但实际数组已存满?注意:循环队列预留 1 个空位判满,所以上述状态中,rear=0 时,(0+1)%5=1≠front=0,但再入队会触发 (0+1)%5=1≠0,直到入队后 rear=4(4+1)%5=0=front 才判满。

新手常见错误 :直接用 Q->rear == MAXSIZE 判满,忽略循环特性,导致数组空间浪费。

2. 循环队列出队

功能

从队头取出元素并存储到指定变量,成功返回 1,队空则返回 0(最易踩指针移动错误)。

复制代码
// 循环队列出队函数
int dequeueCircular(Queue *Q, ElemType *e) {
    // 先判断队列是否为空
    if (Q->front == Q->rear) {
        printf("循环队列为空,出队失败\n");
        return 0;
    }
    // 取出队头元素
    *e = Q->data[Q->front];
    // 队头指针后移(取模实现循环,核心!新手易漏+1)
    Q->front = (Q->front + 1) % MAXSIZE;
    return 1;  // 出队成功
}
可视化展示(承接上述入队 5 后的状态:front=0,rear=0?不,入队 5 后是 front=0,rear=0?修正:入队 1、2、3、4 后 rear=4,入队 5 后 data[4]=5rear=(4+1)%5=0,此时状态:

plaintext

复制代码
数组索引: 0    1    2    3    4
data:    1    2    3    4    5
          ↑                   ↑
        front(0)            rear(0)
  • 出队元素 1:*e=1front=(0+1)%5=1

    plaintext

    复制代码
    数组索引: 0    1    2    3    4
    data:    1    2    3    4    5
                 ↑              ↑
               front(1)        rear(0)
  • 再出队元素 2:*e=2front=(1+1)%5=2

    plaintext

    复制代码
    数组索引: 0    1    2    3    4
    data:    1    2    3    4    5
                      ↑         ↑
                    front(2)   rear(0)

    此时队仍非空(front≠rear),可继续出队,直到 front=0rear=0 时队空。

新手致命错误 :仅写 Q->front = Q->front % MAXSIZE,未加 1,导致队头指针永远不移动,重复取出同一个元素。

五、普通顺序队列(处理假溢出)

普通顺序队列不做循环处理,队尾指针到达数组末尾后,需通过 "元素前移" 利用队头空出的空间,解决假溢出问题。

1. 假溢出调整函数

功能

当队尾越界但队头有空位时,将所有元素前移,空出开头空间。

复制代码
// 假溢出调整:前移元素,复用队头空出的空间
int adjustQueue(Queue *Q) {
    if (Q->front > 0) {  // 队头有空闲空间
        int step = Q->front;  // 前移的步数
        // 将元素从front位置移到数组开头
        for (int i = Q->front; i < Q->rear; i++) {
            Q->data[i - step] = Q->data[i];
        }
        // 更新指针:队头归0,队尾同步前移
        Q->front = 0;
        Q->rear = Q->rear - step;
        return 1;  // 调整成功
    } else {
        printf("队列真满,无空间可调整\n");
        return 0;  // 数组完全存满,调整失败
    }
}
可视化展示(假溢出调整)

假设 MAXSIZE=5,初始入队 1、2、3 后出队 1、2,状态如下:

plaintext

复制代码
数组索引: 0    1    2    3    4
data:    □    □    3    □    □
                ↑         ↑
              front=2    rear=3

此时入队 4、5,rear=5(超出 MAXSIZE=5),触发假溢出调整:

  • step=front=2,元素前移 2 步:data[0]=3data[1]=4data[2]=5
  • 指针更新:front=0rear=3;调整后状态:

plaintext

复制代码
数组索引: 0    1    2    3    4
data:    3    4    5    □    □
          ↑         ↑
        front      rear

2. 普通顺序队列入队

功能

插入元素到队尾,遇假溢出则先调整再入队。

复制代码
// 普通顺序队列入队(处理假溢出)
int enqueueNormal(Queue *Q, ElemType e) {
    // 队尾超出数组范围,尝试调整
    if (Q->rear >= MAXSIZE) {
        if (!adjustQueue(Q)) {  // 调整失败则入队失败
            return 0;
        }
    }
    // 元素入队,队尾指针后移
    Q->data[Q->rear] = e;
    Q->rear++;
    return 1;  // 入队成功
}
可视化展示

初始状态 front=0,rear=0

  • 入队 1:data[0]=1rear=1

    plaintext

    复制代码
    数组索引: 0    1    2    3    4
    data:    1    □    □    □    □
              ↑    ↑
            front  rear
  • 入队 2、3、4:rear=4

    plaintext

    复制代码
    数组索引: 0    1    2    3    4
    data:    1    2    3    4    □
              ↑                   ↑
            front                rear
  • 入队 5:data[4]=5rear=5(此时 rear >= MAXSIZE

    plaintext

    复制代码
    数组索引: 0    1    2    3    4
    data:    1    2    3    4    5
              ↑                     
            front                   rear=5(越界)

    若再入队 6,触发调整(但 front=0,无空闲空间,调整失败,入队失败)。

3. 普通顺序队列出队

功能

从队头取出元素并返回,队空则返回错误标识(0)。

复制代码
// 普通顺序队列出队
ElemType dequeueNormal(Queue *Q) {
    // 判空
    if (Q->front == Q->rear) {
        printf("普通队列为空,出队失败\n");
        return 0;  // 队空返回错误标识
    }
    // 取出队头元素,队头指针后移
    ElemType e = Q->data[Q->front];
    Q->front++;
    return e;  // 返回出队元素
}
可视化展示(承接入队 1、2、3、4、5 后的状态:front=0,rear=5
  • 出队 1:e=1front=1

    plaintext

    复制代码
    数组索引: 0    1    2    3    4
    data:    1    2    3    4    5
                 ↑                ↑
               front=1           rear=5
  • 出队 2:e=2front=2

    plaintext

    复制代码
    数组索引: 0    1    2    3    4
    data:    1    2    3    4    5
                      ↑           ↑
                    front=2      rear=5

    此时数组前 2 个位置空出,但 rear=5 仍越界,再入队需触发假溢出调整。

六、新手常见错误避坑指南

  1. 循环队列指针移动:出队时必须 front = (front + 1) % MAXSIZE,仅取模不加 1 会导致指针静止;
  2. 判满 / 判空混淆:循环队列判满是 (rear+1)%MAXSIZE == front,判空是 front == rear,新手易颠倒;
  3. 普通队列假溢出:直接认为 rear >= MAXSIZE 就是队满,忽略队头空出的空间;
  4. 指针操作未判空:调用出队函数前未检查队列是否为空,导致访问无效元素。

总结

  1. 队列的核心是 "先进先出",循环队列通过取模解决普通顺序队列的假溢出问题,是实际开发中更常用的实现方式;
  2. 循环队列的关键是指针的 "循环移动"(取模运算),判满需预留一个空位避免与判空条件冲突,通过可视化能清晰看到指针的移动规律;
  3. 普通顺序队列需通过元素前移处理假溢出,但效率低于循环队列,仅适用于简单场景;
  4. 初始化、入队、出队的可视化能直观体现指针与数组元素的关系,是理解队列逻辑的关键,新手需重点掌握指针的更新规则。

手写笔记

如下图所示,rear指向队尾元素的下一个

顺序队列易出现假溢出,所以有了循环队列,下面图片解释假溢出和入队

循环队列解决了上述问题

相关推荐
江畔柳前堤2 小时前
大语言模型分布式训练:从并行策略到万卡工程的系统梳理
人工智能·分布式·深度学习·算法·目标检测·机器学习·语言模型
Forever Nore3 小时前
LeetCode 4 寻找两个正序数组的中位数 - 二分
算法·leetcode
罗西的思考4 小时前
【OpenClaw具身硬件】MiniClaw 阅读笔记---(1)基础
人工智能·算法·机器学习
蛋先生DX5 小时前
大模型参数存储格式揭秘:BF不是男朋友
深度学习·算法·llm
爱跳舞的烤冷面6 小时前
自学嵌入式第22天(数据结构——哈希)
数据结构·算法·哈希算法
猎嘤一号6 小时前
博弈论(Game Theory)的理论、算法与工程
人工智能·算法·安全·博弈论
月光船幽幽6 小时前
影子模式下保护 logits 不被修改
人工智能·python·算法
马拉AI6 小时前
腾讯开源 Agent 记忆系统,AI“换对话就忘”的问题有了新解法(附安装使用教程)
人工智能·算法·开源·科研
地平线开发者8 小时前
征程6工具链模型X86推理方式说明
算法
地平线开发者8 小时前
【征程6】校准量化中HistogramObserver解析
算法