栈与队列之队列入门攻略:从核心概念到链表实现

🏠个人主页:黎雁

🎬作者简介:C/C++/JAVA后端开发学习者

❄️个人专栏:C语言数据结构(C语言)EasyX游戏规划程序人生

✨ 从来绝巘须孤往,万里同尘即玉京

文章目录

栈与队列之队列入门攻略:从核心概念到链表实现✨

你好!欢迎来到线性表系列栈与队列篇的第二篇内容~

在上一篇中,我们吃透了"后进先出"的栈结构,而今天的主角------队列,恰恰是栈的"反向搭档",它遵循"先进先出"原则,在日常开发和算法解题中同样不可或缺,比如消息队列、任务排队、二叉树层序遍历等场景都能看到它的身影。

继栈的数组实现后,我们这篇将聚焦队列的核心知识,从概念到实现,手把手带你搞定这个"排队专用"的线性表!


文章摘要

本文为线性表系列栈与队列篇第二篇入门内容,聚焦队列这一受限线性表。通俗讲解队列"先进先出"的核心特性,对比数组与链表两种实现方式的优劣并选定单向链表为最优方案。通过工程化的头文件+源文件结构,完整实现队列的初始化、销毁、入队、出队等核心操作,详解队头队尾指针的边界处理细节,补充空队列、单节点队列等特殊场景的处理逻辑,零基础吃透队列的底层实现。

阅读时长 :约20分钟
阅读建议

  1. 基础薄弱者:先理解队列"先进先出"特性,再对照代码梳理指针逻辑
  2. 工程开发者:重点关注内存释放、队空/队满判断等健壮性细节
  3. 面试备考者:牢记队列的特性、实现方式及典型应用场景
  4. 查漏补缺者:直接查看出队操作中单节点队列的特殊处理逻辑

一、知识回顾:栈 vs 队列的核心差异

在学习队列之前,我们先对比栈和队列的核心区别,快速建立认知:

数据结构 核心特性 操作端 最优实现方式
后进先出(LIFO) 仅栈顶一端 数组(尾插尾删)
队列 先进先出(FIFO) 队尾入、队头出 链表(头删尾插)

简单总结:栈是"一口进出",队列是"一头进、另一头出"!


二、队列的核心概念:什么是"先进先出"?📚

1. 队列的定义

队列是一种特殊的线性表,只允许在一端插入元素 (队尾),在另一端删除元素 (队头),遵循先进先出(FIFO = First In First Out) 原则。

生活中的队列例子随处可见:

  • 排队买奶茶:先排队的人先买到奶茶 → 完美契合"先进先出"
  • 打印机打印任务:先提交的任务先打印 → 典型的队列应用
  • 消息队列:先产生的消息先被消费 → 工程中最常用的队列场景

2. 队列的关键操作术语

操作名称 说明 操作位置
入队/进队 向队列中添加元素 队尾(唯一入队端)
出队/离队 从队列中删除元素 队头(唯一出队端)
队头 允许删除元素的一端 -
队尾 允许插入元素的一端 -

3. 队列的实现方式对比:数组 vs 链表

队列同样有两种实现方式,我们详细分析优劣,选择最优方案:

实现方式 核心思路 优点 缺点
数组 队尾入队(尾插)、队头出队(头删) 实现简单、随机访问快 队头出队需挪动所有元素,时间复杂度O(N),效率极低
链表(推荐) 单向链表:队尾入队(尾插)、队头出队(头删) (用tail指针记录队尾,避免找尾) ① 入队/出队效率O(1) ② 按需分配内存,无需扩容 需额外维护队头/队尾指针,处理空队列/单节点队列边界

结论:单向链表实现队列是最优选择!

  • 链表头删、尾插的时间复杂度都是O(1),完美匹配队列的操作需求
  • 只需维护head(队头)和tail(队尾)两个指针,即可高效完成所有操作
  • 避免了数组出队时挪动元素的性能损耗

三、队列的工程化实现:链表版队列完整代码💻

我们依旧采用"头文件+源文件"的工程化结构实现队列,保证代码规范性。

1. 头文件定义(queue.h):接口声明

c 复制代码
#pragma once
#include <stdio.h>
#include <stdbool.h>
#include <assert.h>
#include <stdlib.h>

// 定义队列存储的数据类型
typedef int QDataType;

// 队列节点结构体(单向链表节点)
typedef struct QueueNode
{
    struct QueueNode* next; // 指向下一个节点的指针
    QDataType data;         // 节点存储的数据
}QNode;

// 队列结构体(维护队头、队尾指针)
typedef struct Queue
{
    QNode* head; // 队头指针(出队端)
    QNode* tail; // 队尾指针(入队端)
}Queue;

// 队列核心操作接口
void QueueInit(Queue* pq);        // 初始化队列
void QueueDestroy(Queue* pq);     // 销毁队列(释放内存)
void QueuePush(Queue* pq, QDataType x); // 入队(队尾插入)
void QueuePop(Queue* pq);         // 出队(队头删除)
QDataType QueueFront(Queue* pq);  // 获取队头元素
QDataType QueueBack(Queue* pq);   // 获取队尾元素
int QueueSize(Queue* pq);         // 获取队列元素个数
bool QueueEmpty(Queue* pq);       // 判断队列是否为空

2. 源文件实现(queue.c):核心逻辑

c 复制代码
#include "queue.h"

// 初始化队列
void QueueInit(Queue* pq) 
{
    assert(pq); // 确保队列指针不为NULL
    pq->head = NULL; // 初始队头为空
    pq->tail = NULL; // 初始队尾为空
}

// 销毁队列(遍历释放所有节点)
void QueueDestroy(Queue* pq) 
{
    assert(pq);
    QNode* cur = pq->head;
    while (cur) // 逐个释放节点
    {
        QNode* next = cur->next; // 保存下一个节点
        free(cur);
        cur = next;
    }
    // 重置指针,防止野指针
    pq->head = NULL;
    pq->tail = NULL;
}

// 入队(队尾插入)
void QueuePush(Queue* pq, QDataType x) 
{
    assert(pq);
    // 创建新节点
    QNode* newnode = (QNode*)malloc(sizeof(QNode));
    if (newnode == NULL) 
    {
        perror("malloc fail");
        exit(1);
    }
    newnode->data = x;
    newnode->next = NULL;

    // 处理空队列(第一个节点)
    if (pq->tail == NULL) 
    {
        pq->head = newnode;
        pq->tail = newnode;
    }
    else // 队列非空,尾插
    {
        pq->tail->next = newnode;
        pq->tail = newnode; // 更新队尾指针
    }
}

// 出队(队头删除)
void QueuePop(Queue* pq) 
{
    assert(pq);
    assert(!QueueEmpty(pq)); // 队空禁止出队

    // 处理单节点队列(删除后队头队尾都为空)
    if (pq->head->next == NULL) 
    {
        free(pq->head);
        pq->head = NULL;
        pq->tail = NULL;
    }
    else // 多节点队列,头删
    {
        QNode* next = pq->head->next;
        free(pq->head);
        pq->head = next; // 更新队头指针
    }
}

// 获取队头元素
QDataType QueueFront(Queue* pq) 
{
    assert(pq);
    assert(!QueueEmpty(pq)); // 队空禁止获取
    return pq->head->data;
}

// 获取队尾元素
QDataType QueueBack(Queue* pq) 
{
    assert(pq);
    assert(!QueueEmpty(pq)); // 队空禁止获取
    return pq->tail->data;
}

// 获取队列元素个数
int QueueSize(Queue* pq) 
{
    assert(pq);
    int size = 0;
    QNode* cur = pq->head;
    while (cur) // 遍历统计节点数
    {
        size++;
        cur = cur->next;
    }
    return size;
}

// 判断队列是否为空
bool QueueEmpty(Queue* pq) 
{
    assert(pq);
    return pq->head == NULL; // 队头为空则队列空
}

四、核心易错点解析:边界场景处理⚠️

队列实现中最容易出错的是边界场景,我们重点梳理:

  1. 空队列入队:需同时更新head和tail指针,否则会出现tail为NULL的错误
  2. 单节点队列出队:删除后必须将head和tail都置NULL,否则tail会指向已释放的节点(野指针)
  3. 出队前判空 :必须用assert(!QueueEmpty(pq))防止空队列出队,避免访问NULL指针

五、写在最后

恭喜你!这一篇中你已经掌握了队列的核心知识:

  • 理解了队列"先进先出"的核心特性,对比了和栈的关键差异
  • 选定了链表作为队列的最优实现方式,吃透了头删尾插的逻辑
  • 完成了队列的工程化代码实现,掌握了边界场景的处理技巧

队列和栈是数据结构的"基础搭档",二者结合能解决很多复杂问题:

  • 算法层面:二叉树层序遍历、BFS(广度优先搜索)依赖队列
  • 工程层面:消息队列、任务调度、限流削峰都离不开队列

下一篇,我们将进入实战环节,用栈和队列解决经典OJ题,把理论转化为解题能力!😜


点赞+收藏+关注,跟着系列内容一步步吃透栈和队列!你的支持是我创作的最大动力~👍

相关推荐
一路往蓝-Anbo几秒前
第 2 篇:单例模式 (Singleton) 与 懒汉式硬件初始化
开发语言·数据结构·stm32·单片机·嵌入式硬件·链表·单例模式
心态还需努力呀19 分钟前
【鸿蒙 PC 命令行适配】c-ares 在鸿蒙 PC 上的移植与交叉编译实战(可复现指南)
c语言·开源·harmonyos·鸿蒙·openharmony
张张努力变强32 分钟前
C++ 类和对象(五):初始化列表、static、友元、内部类等7大知识点全攻略
开发语言·数据结构·c++·算法
代码无bug抓狂人33 分钟前
(蓝桥杯省B)R格式
c语言·蓝桥杯
老鼠只爱大米34 分钟前
LeetCode经典算法面试题 #23:合并K个升序链表(分支法、优先队列等多种实现方案详细解析)
算法·leetcode·链表·优先队列·多路归并·分治法·合并链表
养军博客40 分钟前
C语言五天速成(可用于蓝桥杯备考)
c语言·数据结构·算法
Yupureki41 分钟前
《算法竞赛从入门到国奖》算法基础:搜索-BFS初识
c语言·数据结构·c++·算法·visual studio·宽度优先
嵌入式×边缘AI:打怪升级日志1 小时前
Libmodbus 源码总体分析:框架、数据结构与核心函数详解
开发语言·数据结构·php
叫我辉哥e19 小时前
### 技术文章大纲:C语言造轮子大赛
c语言·开发语言
进击的小头12 小时前
行为型模式:策略模式的C语言实战指南
c语言·开发语言·策略模式