1. 对比 链式队列 和 链式栈
- 链式栈:使用无头单向链表。最开始的头结点作为栈顶,逐次栈针上移。插入删除是对 表尾结点操作。
- 链式队列:使用有头单向链表。插入:最初头结点会向下增长 即队尾指针下移。删除会队头指针下移,甚至可以删除头结点,让新结点作为头结点。
2. 概念
- 逻辑结构:线性结构
- 物理结构:链式存储
- 操作特点:先进先出,尾进头出
2.1. 示意图
队头指针,指向 队列头结点(最先进入的结点,也可以不是头)
队尾指针,指向 队尾结点(最后进入的结点)
2.2. 空链队情况 示意图
空队列时,front
和rear
都指向头结点
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));
}