3.3.2【链式队列 有头单向链表】

1. 对比 链式队列 和 链式栈

  • 链式栈:使用无头单向链表。最开始的头结点作为栈顶,逐次栈针上移。插入删除是对 表尾结点操作。
  • 链式队列:使用有头单向链表。插入:最初头结点会向下增长 即队尾指针下移。删除会队头指针下移,甚至可以删除头结点,让新结点作为头结点。

2. 概念

  1. 逻辑结构:线性结构
  2. 物理结构:链式存储
  3. 操作特点:先进先出,尾进头出

2.1. 示意图

队头指针,指向 队列头结点(最先进入的结点,也可以不是头)

队尾指针,指向 队尾结点(最后进入的结点)

2.2. 空链队情况 示意图

空队列时,frontrear都指向头结点

3. 接口实现

3.1. 定义 链式队列 结点结构体

// 定义 链式队列 结点结构体
typedef int LQdatatype;
typedef struct LinkListNode
{
    LQdatatype data;
    struct LinkListNode *next;
} LLN, *LL;

3.2. 定义 操作 链式队列 的结构体

// 定义 操作 链式队列 的结构体
typedef struct LinkQueue
{
	LL front; 	// 队头结点指针(LLN * front)(struct LinkListNode *)
    LL rear; 	// 队尾结点指针
} LQ;

3.3. 创建空的链式队列(仅有头结点)

仅有头结点,队头队尾指针都指向它

3.4. 入队列(插入新结点,队尾指针后移)

旧rear的next链接pnew

新rear指向pnew

队头不动,只动队尾

3.5. 出队列(队头指针后移,释放旧头结点)

3.5.1. 有问题的写法(要知道,但不用)

// 5. 数据出列
int LQPop(LQ *PQ)
{
    // 5.1 容错判断(空了没法出)
    if (PQ->front == PQ->rear)
    {
        printf("LQPop failed, LQ is empty.\n");
        return -1;
    }

    // 5.2 PDel指向零号结点(出列结点)
    LL PDel = PQ->front->next;

    // 5.3 定义变量接收出列数据(可与5.3交换位置)
    int temp = PDel->data;

    // 5.4 链接头结点与一号结点(出列结点后继)
    // PQ->front->next = PDel->next;
    PQ->front->next = PQ->front->next->next;

    // 5.5 释放零号结点(出列结点)
    free(PDel);
    PDel = NULL;

    // 5.6 返回出列数据
    return temp;
}

问题:缺少对链式队列中仅剩一个头结点和零号结点的情况的判断。

无法更新 队尾指针可能后续会操作非法空间

3.5.2. 无问题的写法(对比记,用这个)

思路:直接每次更新队头指向,放弃最初的头结点,队头指针指向的结点和 头结点等价。避免跨越链接结点这种操作。

LQDataType LQPop(LQ *PQ)
{
    // 先判断 空吗?空则不能出
    // 指向同个头结点,即是空
    if (PQ->rear == PQ->front)
    {
        DEBUG("LQPop failed, LQ is empty");
        return (LQDataType)(-1);
    }

    /* 注意:出队,操作头结点,的思路:特殊! */
    /* 用这种经典特殊,但不出错的写法 */

    // 指向被删的 旧队头结点
    LL Pdel = PQ->front;

    // 队头指针下移 指向新的 队头结点(新头结点)
    // 这才是有效数据结点,不论何时 队头结点,都是数据域无效的。
    PQ->front = Pdel->next;

    // 释放 旧的 头结点
    free(Pdel);
    Pdel = NULL;

    // 返回出列数据
    return PQ->front->data;
}

3.6. 遍历打印 链式队列

用 遍历 有头单向链表思路

注意,需要用 伪指针 代替 头指针 移动

因为,PQ的成员 队头队尾指针的指向,可以被写操作被改动

3.7. 队列长(除头结点外,有效元素个数即 长度)

遍历 有头单向链表。

可能头结点不是 最初的头结点,但逻辑上等价即可

3.8. 清空(不停出队,直到只剩头结点)

一直到,front和rear都指向 同个头结点

4. 总体代码

#include <stdio.h>
#include <stdlib.h>
#define DEBUG(Str) printf("%s %s %s %d\n", Str, __func__, __FILE__, __LINE__)

// 定义 链式队列 结点结构体
typedef int LQDataType;
typedef struct LinkListNode
{
    LQDataType data;
    struct LinkListNode *next;
} LLN, *LL;

// 定义 操作 链式队列 的结构体
typedef struct LinkQueue
{
    LL front; // 队头结点指针(LLN * front)(struct LinkListNode *)
    LL rear;  // 队尾结点指针
} LQ;

// 创建空的链式队列(仅有头结点)
LQ *LQInit(void)
{
    // 开辟空间 存放 操作链式队列 的结构体
    LQ *PQ = (LQ *)malloc(sizeof(LQ));
    if (NULL == PQ)
    {
        DEBUG("LQInit failed, PQ malloc err");
        return NULL;
    }

    // 申请 原始 头结点,同时初始化 操作链队的 结构体
    PQ->front = PQ->rear = (LL)malloc(sizeof(LLN));
    if (NULL == PQ->front) //哪个指针都行
    {
        DEBUG("LQInit failed, front&rear malloc err");
        return NULL;
    }

    // 对链表结点进行初始化,即next置NULL
    // PQ->rear->next = NULL;//哪个指针都行
    PQ->front->next = NULL;

    // 返回操作 链队 的 结构体
    return PQ;
}

// 入队列(插入新结点,队尾指针后移)
int LQPush(LQ *PQ, LQDataType data)
{
    // 创建一个新结点保存即将插入的数据
    LL PNew = (LL)malloc(sizeof(LLN));
    if (NULL == PNew)
    {
        DEBUG("PQPush faild, PNew malloc err.");
        return -1;
    }

    // 初始化新结点
    PNew->data = data;
    PNew->next = NULL;

    // 新结点 被链接到 旧rear结点的 之下
    PQ->rear->next = PNew;

    // 定向 新的 队尾结点
    PQ->rear = PNew;

    return 0;
}

// 数据出队列(队头指针后移,释放旧头结点)
LQDataType LQPop(LQ *PQ)
{
    // 先判断 空吗?空则不能出
    // 指向同个头结点,即是空
    if (PQ->rear == PQ->front)
    {
        DEBUG("LQPop failed, LQ is empty");
        return (LQDataType)(-1);
    }

    /* 注意:出队,操作头结点,的思路:特殊! */
    /* 用这种经典特殊,但不出错的写法 */

    // 指向被删的 旧队头结点
    LL Pdel = PQ->front;

    // 队头指针下移 指向新的 队头结点(新头结点)
    // 这才是有效数据结点,不论何时 队头结点,都是数据域无效的。
    PQ->front = Pdel->next;

    // 释放 旧的 头结点
    free(Pdel);
    Pdel = NULL;

    // 返回出列数据
    return PQ->front->data;
}

// 队列长(除头结点外,有效元素个数即 长度)
// 遍历,有头单向链表:
// 注意,不论队头结点是否有数据,都是无效的,因配合 出队代码
int LQLength(LL H)
{
    int len = 0;
    while (H->next != NULL)
    {
        H = H->next;
        len++;
    }
    return len;
}

// 清空(不停出队,直到只剩头结点
void LQClear(LQ *PQ)
{
    while (PQ->front != PQ->rear)
        LQPop(PQ);
}

// 遍历打印 链式队列
// 遍历 有头单向链表
void LQPrint(LQ *PQ)
{
    LL H = PQ->front; // 伪指针 代替头指针移动

    while (H->next != NULL) // 等价 while (H->next)
    {
        H = H->next;
        printf("%d\t", H->data);
    }
    printf("\n");
}

int main(int argc, char const *argv[])
{
    // 创建空的链式队列(仅有头结点)
    LQ *PQ = LQInit();

    // 入队列(插入新结点,队尾指针后移)
    for (size_t i = 1; i < 6; i++)
        LQPush(PQ, i * 11);

    // 队列长(除头结点外,有效元素个数即 长度)
    printf("LQ len:%d\n", LQLength(PQ->front));

    // 出队一个
    printf("front data:%d\n", LQPop(PQ));
    printf("-----LQ len:%d\n", LQLength(PQ->front));

    // 打印 遍历
    LQPrint(PQ);

    // 清空 链式队列
    LQClear(PQ);
    printf("-----LQ len:%d\n", LQLength(PQ->front));
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值