队列的链式实现

文章目录

队列的链式实现

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

链队列需要两个指针:

  • 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)

相关推荐
·薯条大王1 天前
经济实惠玩云服务器|一台云服务器多人共用,子账号配置教程
java·linux·运维·服务器·汇编·c++·python
To_OC1 天前
LC 438 找到所有字母异位词:暴力超时后,我靠滑动窗口一招搞定
javascript·算法·leetcode
白狐_7981 天前
408数据结构第5章:二叉树遍历序列题——技巧、判断与真题型总结
数据结构
Forever Nore1 天前
学完C语言力扣第一题做不来正常吗
数据结构·算法
hansang_IR1 天前
【题解】LC:倍增 / 区间并查集(Range Parallel Unionfind)
c++·算法·并查集
东东最爱敲键盘1 天前
day7.c++继承
c++
沫璃染墨1 天前
《从零入门Linux系统篇(十五):系统工具篇·六——GDB调试器详解:从程序执行控制到高级调试技巧》
linux·运维·服务器·c语言·c++
吃着火锅x唱着歌1 天前
Effective C++ 学习笔记 条款40 明智而审慎地使用多重继承
c++·笔记·学习
Tisfy1 天前
LeetCode 3731.找出缺失的元素:哈希 / 排序
算法·leetcode·哈希算法·排序·哈希表