队列的链式实现

文章目录

队列的链式实现

链队列是队列的链式存储结构,本质上是操作受限单链表 。其解决了顺序队列(循环队列)"容量固定、可能溢出"的缺陷。

链队列需要两个指针:

  • front (队头指针):指向头结点(注意:不是第一个数据结点)。头结点的 data 闲置,next 指向真正的首元结点。
  • rear(队尾指针):指向最后一个数据结点(尾结点)。

带头结点后 ,空队时 front == rear(均指向头结点),所有操作逻辑完全统一

图示:

text 复制代码
空队: front(rear) -> [头结点 | next=NULL]

入队10: front -> [头结点 | next] -> [10 | NULL]
                  				rear-^

入队20: front -> [头结点 | next] -> [10 | next] -> [20 | NULL]
                							 rear -^

代码实现

结构定义与初始化

与顺序队列不同,链队列通常将frontrear封装在一个结构体中,便于函数传参。

带头结点

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

// ① 结点定义(与单链表完全一样)
typedef struct LNode {
    int data;
    struct LNode *next;
} LNode;

// ② 链队列定义(封装了队头和队尾指针)
typedef struct {
    LNode *front; // 队头指针(指向头结点)
    LNode *rear;  // 队尾指针(指向最后一个结点)
} LinkQueue;

// 带头结点
// 初始化 :创建空队列(创建头结点)
bool InitQueue(LinkQueue &Q) {
    // 1. 申请头结点空间    队尾指针也指向头结点(表示空队)
    Q.front = Q.rear = (LNode *)malloc(sizeof(LNode));
    if (Q.front == NULL) 
		return false; // 内存分配失败

    // 2. 头结点的 next 置空
    Q.front->next = NULL;
    
    return true;
}

//  判空
bool QueueEmpty(LinkQueue Q) {
    return Q.front == Q.rear; // 或者 Q.front->next == NULL  若 front = rear 则为空 
}

//  判满  链队列不存在 "满 "的概念!
// 唯一的限制是堆内存耗尽。通常不写判满函数,而是在入队时检测 malloc 返回值。
bool QueueFull(LinkQueue Q) {
    return false; // 理论永远不满(只要内存够)
}

不带头结点

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

typedef struct LNode {
    int data;
    struct LNode *next;
} LNode;

typedef struct {
    LNode *front; // 队头指针:指向第一个数据结点(不是头结点!!!!!)
    LNode *rear;  // 队尾指针:指向最后一个数据结点
} LinkQueue;

// 不带头结点
//  初始化  创建空队列(不分配头结点)
void InitQueue(LinkQueue &Q) {
    Q.front = NULL; // 直接置空
    Q.rear = NULL;  // 直接置空
}

//  判空 
bool QueueEmpty(LinkQueue Q) {
    return Q.front == NULL; // 只要队头为空,队列就为空
    // 也可以这样判断:Q.rear == NULL; 
}

入队(尾插法)

带头结点

步骤:新建结点 → 接到 rear 后面 → 更新rear为新结点

cpp 复制代码
// 带头结点
//  入队  在队尾(rear 后面)插入元素 e
bool EnQueue(LinkQueue &Q, int e) {
    // 1. 申请新结点空间
    LNode *s = (LNode *)malloc(sizeof(LNode));
    if (s == NULL) 
		return false; // 内存耗尽,相当于 "队满 "

    // 2. 初始化新结点
    s->data = e;
    s->next = NULL;

    // 3. 将新结点挂到当前尾结点后面
    Q.rear->next = s;

    // 4. 尾指针后移(指向新的尾结点)
    Q.rear = s;

    return true;
}

不带头结点

cpp 复制代码
// 不带头结点
//  入队  在队尾插入元素 e
bool EnQueue(LinkQueue &Q, int e) {
    LNode *s = (LNode *)malloc(sizeof(LNode));
    if (s == NULL) 
		return false;

    s->data = e;
    s->next = NULL;

    // 必须特别判断是不是第一个结点
    // 不带头结点的队列,第一个元素入队时需要特别处理 
    if (Q.front == NULL) { 
        // 情况1:空队,front 和 rear 都指向新结点
        Q.front = s; // 修改队头队尾指针 
        Q.rear = s;
    } else {
        // 情况2:非空队,挂在尾结点后面
        Q.rear->next = s; // 新结点插入到 rear 结点之后 
        Q.rear = s;  // 修改 rear 指针 
    }
    return true;
}

出队

删除队头第一个有效结点

特殊边界:如果删除后队列为空,需要将rear重置回头结点,防止 rear 悬空。

带头结点

删除头结点的后继。

cpp 复制代码
// 带头结点
//  出队  删除队头元素,并通过 e 带回其值
bool DeQueue(LinkQueue &Q, int &e) {
    // 1. 判空
    if (Q.front == Q.rear) 
		return false;

    // 2. p 指向首元结点(第一个数据结点)
    LNode *p = Q.front->next;
    e = p->data; // 保存数据

    // 3. 头结点跨过 p,指向 p 的下一个结点
    Q.front->next = p->next;

    // 注意:如果 p 恰好是最后一个结点(即出队后队列变空)
    // 必须把 rear 重新指向头结点,否则 rear 将指向已释放的内存!
    if (p == Q.rear) {
        Q.rear = Q.front;
    }

    // 4. 释放 p 结点内存
    free(p);
    return true;
}

不带头结点

cpp 复制代码
// 不带头结点
//  出队  删除队头元素,并通过 e 带回其值
bool DeQueue(LinkQueue &Q, int &e) {
    // 1. 判空
    if (Q.front == NULL) 
		return false;

    // 2. p 指向队头结点(直接就是 front)
    LNode *p = Q.front;
    e = p->data;

    // 3. 队头指针后移(指向下一个数据结点)
    Q.front = p->next;

    // 如果删除的是唯一结点(出队后队列变空)
    if (Q.front == NULL) {  
        // 必须把 rear 也置为 NULL,否则 rear 指向已释放的内存!
        Q.rear = NULL;
    }
    
    // 或这样写 
//    if(Q.rear == p ){
//    	Q.rear = NULL;
//    	Q.front = NULL;
//	} 

    free(p);
    return true;
}

查队头

cpp 复制代码
// 带头结点
//  查队头  只读操作,不删除元素
bool GetHead(LinkQueue Q, int &e) {
    if (Q.front == Q.rear) // 空队
    	return false; 
    e = Q.front->next->data; // 头结点的后继才是第一个数据
    return true;
}

// 不带头结点
//  查队头  只读操作
bool GetHead(LinkQueue Q, int &e) {
    if (Q.front == NULL) 
    	return false;
    e = Q.front->data; // front 直接指向数据结点,无需绕 ->next
    return true;
}

销毁

cpp 复制代码
// 带头结点
//  销毁  释放所有结点(包括头结点)
void DestroyQueue(LinkQueue &Q) {
    LNode *p = Q.front; // p 从头结点开始
    while (p != NULL) {
        LNode *q = p;
        p = p->next;
        free(q);
    }
    // 防止野指针(虽然 Q 是引用,但置空是好习惯)
    Q.front = NULL;
    Q.rear = NULL;
}

// 不带头结点
//  销毁 释放所有数据结点
void DestroyQueue(LinkQueue &Q) {
    LNode *p = Q.front;
    while (p != NULL) {
        LNode *q = p;
        p = p->next;
        free(q);
    }
    Q.front = NULL;
    Q.rear = NULL;
}

链队列是采用链式存储结构的队列,通常使用带头结点的单链表实现,并附设 frontrear 两个指针分别指向头结点和尾结点。入队操作对应单链表的尾插法(rear->next = s; rear = s;),出队操作对应删除头结点的后继结点。在出队时,若被删结点是尾结点,必须将 rear 重置为 front。链队列克服了顺序队列容量固定的缺陷,入队、出队、判空操作的时间复杂度均为 O(1)

相关推荐
selfsongs10 小时前
指针、内存管理、现代 C++ 特性
c++
可编程芯片开发10 小时前
基于MATLAB的PEF湍流风场生成器模拟与仿真
算法
罗西的思考11 小时前
【Agentic RL / 强化学习 / OPD】OpenClaw-RL 源码阅读笔记 --- (9)--- Reward Judging
人工智能·算法·机器学习
吃着火锅x唱着歌11 小时前
Effective C++ 学习笔记 条款37 绝不重新定义继承而来的缺省参数值
c++·笔记·学习
2401_8414956411 小时前
【数据结构】B+树
数据结构·数据库·c++·b+树·概念·结构·操作原理
小保CPP11 小时前
OCR C++ Tesseract按行识别字符
c++·人工智能·ocr·模式识别·光学字符识别
阿米亚波12 小时前
【C++】流式数据输入处理(不完全整理)
开发语言·c++·笔记
Mr.HeBoYan12 小时前
一次持续三天才出现的丢包故障——深入解析 DPDK Memory Ordering、rte_ring 与 CPU Memory Barrier (下)
linux·网络·算法·架构·dpdk
wabs66613 小时前
关于图论【深度优先搜索的理论基础】
算法·深度优先·图论
皓月斯语13 小时前
【C++基础】三目运算符
开发语言·数据结构·c++