c语言队列1003c语言队列

99ANYc3cd6
预计阅读时长 28 分钟
位置: 首页 C语言 正文

第一部分:C语言队列的基础知识

队列是一种先进先出的线性数据结构,就像在超市排队结账一样,先来的人先完成结账离开,在队列中,元素从队尾加入,从队头移除。

队列的核心操作

  • 初始化: 创建一个空队列。
  • 入队: 在队尾添加一个新元素。
  • 出队: 从队头移除一个元素。
  • 判空: 检查队列是否为空。
  • 判满: 检查队列是否已满(对于固定大小的数组实现)。
  • 获取队头元素: 查看队头元素,但不移除它。

队列的两种主要实现方式

在 C 语言中,我们通常不使用内置的“队列”类型,而是通过其他数据结构来模拟它,最常见的方式有两种:

基于数组的静态实现(循环队列)

这是最经典、最高效的实现方式,可以避免数组元素被“出队”后造成的空间浪费。

  • 数据结构:一个数组、一个指向队头的指针(front)、一个指向队尾的指针(rear)。
  • 循环技巧:利用取模运算 来实现指针的循环移动,当指针到达数组末尾时,下一个位置就回到数组开头。
  • 判空条件front == rear
  • 判满条件(rear + 1) % MAXSIZE == front,这里会牺牲一个存储单元来区分“满”和“空”的状态。

基于链表的动态实现

这种方式更加灵活,队列的大小可以动态增长,不受预先定义大小的限制。

  • 数据结构:一个链表,需要两个指针:front(指向第一个节点,即队头)和 rear(指向最后一个节点,即队尾)。
  • 节点结构:每个节点包含数据域和指向下一个节点的指针。
  • 优点:内存利用率高,没有容量限制。
  • 缺点:相比数组实现,每个节点都需要额外的空间来存储指针,且存在一定的指针操作开销。

第二部分:基于数组的循环队列实现

下面我们来实现一个通用的循环队列,并解释其关键代码。

定义队列结构体

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h> // 使用 bool 类型
#define MAXSIZE 100 // 队列的最大容量
// 定义队列结构体
typedef struct {
    int data[MAXSIZE]; // 存储队列元素的数组
    int front;         // 队头指针
    int rear;          // 队尾指针
} SqQueue;

核心函数实现

// 1. 初始化队列
void InitQueue(SqQueue *Q) {
    Q->front = 0;
    Q->rear = 0;
}
// 2. 判断队列是否为空
bool IsEmpty(SqQueue *Q) {
    return Q->front == Q->rear;
}
// 3. 判断队列是否已满
bool IsFull(SqQueue *Q) {
    return (Q->rear + 1) % MAXSIZE == Q->front;
}
// 4. 入队
bool EnQueue(SqQueue *Q, int element) {
    if (IsFull(Q)) {
        printf("Error: Queue is full. Cannot enqueue %d.\n", element);
        return false; // 入队失败
    }
    Q->data[Q->rear] = element; // 将元素放入队尾
    Q->rear = (Q->rear + 1) % MAXSIZE; // 队尾指针后移(循环)
    return true; // 入队成功
}
// 5. 出队
bool DeQueue(SqQueue *Q, int *element) {
    if (IsEmpty(Q)) {
        printf("Error: Queue is empty. Cannot dequeue.\n");
        return false; // 出队失败
    }
    *element = Q->data[Q->front]; // 获取队头元素
    Q->front = (Q->front + 1) % MAXSIZE; // 队头指针后移(循环)
    return true; // 出队成功
}
// 6. 获取队头元素(不删除)
bool GetHead(SqQueue *Q, int *element) {
    if (IsEmpty(Q)) {
        printf("Error: Queue is empty. Cannot get head.\n");
        return false;
    }
    *element = Q->data[Q->front];
    return true;
}

测试代码

// 打印队列内容(辅助函数)
void PrintQueue(SqQueue *Q) {
    if (IsEmpty(Q)) {
        printf("Queue is empty.\n");
        return;
    }
    printf("Queue: ");
    int i = Q->front;
    while (i != Q->rear) {
        printf("%d ", Q->data[i]);
        i = (i + 1) % MAXSIZE;
    }
    printf("\n");
}
int main() {
    SqQueue myQueue;
    InitQueue(&myQueue);
    EnQueue(&myQueue, 10);
    EnQueue(&myQueue, 20);
    EnQueue(&myQueue, 30);
    PrintQueue(&myQueue); // 输出: 10 20 30
    int head_val;
    if (GetHead(&myQueue, &head_val)) {
        printf("Head element is: %d\n", head_val); // 输出: 10
    }
    int dequeued_val;
    DeQueue(&myQueue, &dequeued_val);
    printf("Dequeued element: %d\n", dequeued_val); // 输出: 10
    PrintQueue(&myQueue); // 输出: 20 30
    EnQueue(&myQueue, 40);
    EnQueue(&myQueue, 50);
    PrintQueue(&myQueue); // 输出: 20 30 40 50
    // 测试循环
    // 假设 MAXSIZE=6, 当前队列有 4 个元素 (20,30,40,50)
    // front=1, rear=5. (rear+1)%6 = 0, 不等于 front, 所以未满
    EnQueue(&myQueue, 60); // 60 应该放在 data[5]
    PrintQueue(&myQueue); // 输出: 20 30 40 50 60
    // rear=(5+1)%6=0. front=1.
    // (rear+1)%6 = (0+1)%6 = 1. 等于 front. 队列已满
    EnQueue(&myQueue, 70); // 应该报错
    PrintQueue(&myQueue); // 输出: 20 30 40 50 60
    DeQueue(&myQueue, &dequeued_val); // 出队 20
    // front=(1+1)%6=2. rear=0.
    // (rear+1)%6 = (0+1)%6 = 1. 不等于 front. 队列未满
    EnQueue(&myQueue, 70); // 70 应该可以入队,放在 data[0]
    PrintQueue(&myQueue); // 输出: 30 40 50 60 70
    return 0;
}

第三部分:经典题目示例 - PAT 乙级 1003

链接:PAT 乙级 1003 要求

“我是不会在代码中写注释的。” —— 某位不愿透露姓名的神秘大神 本题要求你实现一个简单的代码格式化工具,将给定的代码中的连续三个连续的 替换成一个 。

输入格式: 输入为多行代码,直到文件末尾(EOF)为止。

输出格式: 将输入中的连续三个 替换成一个 ,并输出处理后的代码。

输入样例 1:

i//like//programming

输出样例 1:

i/**/like//programming

输入样例 2:

i////like//programming

输出样例 2:

i/**/like/**/programming

解题思路

这个问题的关键在于“连续三个”,我们不能简单地一次替换一个 ,因为那样会破坏后续的匹配。

对于 i////like

  1. 如果我们找到第一组 (在 i/ 和 之间),替换成 ,得到 i/**/like,这样第二组 就被破坏了。
  2. 我们需要找到三个连续的 ,然后将它们中间的两个 变成 。

使用队列的思路: 我们可以逐个字符读取输入,并同时维护一个队列来存放最近的字符。

  1. 初始化一个队列。
  2. 循环读取每个字符 c
  3. c 入队。
  4. 检查队列中是否有连续的三个 。
    • 一个简单的方法是检查队列的最后三个元素(队尾、队尾前一个、队尾前两个)是否都是 。
    • 在循环队列中,可以通过 (rear - 1 + MAXSIZE) % MAXSIZE(rear - 2 + MAXSIZE) % MAXSIZE 来获取倒数第二个和倒数第三个元素。
  5. 如果满足条件
    • 说明找到了 。
    • 我们需要出队两次,移除中间的两个 。
    • 将 入队两次,形成 。
    • 这样, 就被替换成了 。
  6. 如果不满足条件

    继续读取下一个字符。

  7. 当所有字符都读取完毕后,将队列中剩余的所有元素依次出队并输出即可。

C语言代码实现(使用我们上面定义的队列)

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAXSIZE 1000000 // 假设输入的代码行数和长度不会超过这个值
// --- 循环队列的实现 (与上文相同) ---
typedef struct {
    char data[MAXSIZE];
    int front;
    int rear;
} SqQueue;
void InitQueue(SqQueue *Q) {
    Q->front = 0;
    Q->rear = 0;
}
bool IsEmpty(SqQueue *Q) {
    return Q->front == Q->rear;
}
bool IsFull(SqQueue *Q) {
    return (Q->rear + 1) % MAXSIZE == Q->front;
}
bool EnQueue(SqQueue *Q, char element) {
    if (IsFull(Q)) {
        // 在实际比赛中,如果输入规模大,这里可能会溢出,但PAT题目通常保证输入合法
        return false; 
    }
    Q->data[Q->rear] = element;
    Q->rear = (Q->rear + 1) % MAXSIZE;
    return true;
}
bool DeQueue(SqQueue *Q, char *element) {
    if (IsEmpty(Q)) {
        return false;
    }
    *element = Q->data[Q->front];
    Q->front = (Q->front + 1) % MAXSIZE;
    return true;
}
// --- PAT 1003 问题的主逻辑 ---
int main() {
    SqQueue Q;
    InitQueue(&Q);
    char c;
    // 使用 getchar() 逐个字符读取,直到遇到 EOF
    while ((c = getchar()) != EOF) {
        EnQueue(&Q, c);
        // 检查队列末尾的三个字符是否都是 '/'
        // 循环队列中,获取倒数第1, 2, 3个元素
        char first, second, third;
        if (Q.rear >= 3) {
            first = Q.data[Q.rear - 1];
            second = Q.data[Q.rear - 2];
            third = Q.data[Q.rear - 3];
        } else {
            // 处理循环情况,例如队列中只有2个元素,rear=2
            // 我们想检查 data[1], data[0], data[MAXSIZE-1]
            first = Q.data[(Q.rear - 1 + MAXSIZE) % MAXSIZE];
            second = Q.data[(Q.rear - 2 + MAXSIZE) % MAXSIZE];
            third = Q.data[(Q.rear - 3 + MAXSIZE) % MAXSIZE];
        }
        // 如果找到连续的三个 '/'
        if (first == '/' && second == '/' && third == '/') {
            // 出队两次,移除中间的两个 '/'
            DeQueue(&Q, &c); // 移除第一个'/'
            DeQueue(&Q, &c); // 移除第二个'/'
            // 入队 '*' 两次,形成 '*/'
            EnQueue(&Q, '*');
            EnQueue(&Q, '/');
        }
    }
    // 输出队列中所有剩余的字符
    char output_char;
    while (!IsEmpty(&Q)) {
        DeQueue(&Q, &output_char);
        putchar(output_char);
    }
    return 0;
}

代码讲解

  1. 队列定义:我们定义了一个存储 char 类型的循环队列 SqQueue
  2. 逐字符读取while ((c = getchar()) != EOF) 是处理多行文本输入的标准方法,它会一直读取字符,直到输入流结束。
  3. 入队:每读取一个字符,就立即入队。
  4. 核心判断逻辑
    • 在每次入队后,我们检查队列的最后三个字符。
    • 由于队列是循环的,获取倒数第 n 个元素需要使用 (rear - n + MAXSIZE) % MAXSIZE 的公式来处理指针越界问题。
    • 如果这三个字符都是 ,就说明我们找到了目标模式 。
  5. 替换操作
    • DeQueue(&Q, &c); 执行两次,相当于把 (从队尾数的第二个和第三个)移除。
    • EnQueue(&Q, '*');EnQueue(&Q, '/'); 执行两次,将 加入队列,完成了 -> 的转换。
  6. 最终输出:当所有输入处理完毕后,队列中存放的就是格式化后的完整代码,我们只需将队列中的所有元素依次出队并打印即可。

这个例子很好地展示了队列在处理流式数据和需要“记忆”最近几个元素的场景下的强大作用。

-- 展开阅读全文 --
头像
C语言输入1011C语言输入
« 上一篇 02-03
织梦tag标签调用方法与代码示例?
下一篇 » 02-03

相关文章

取消
微信二维码
支付宝二维码

目录[+]