一. 为什么要用链表实现队列
Question:不是已经有数组实现队列了么?为什么还要用链表实现呢?没事找事?傻b!
大家不妨思考一个问题:
数组队列只能存固定容量的队列
这会导致:多的队列的存不下,少的队列用不完浪费内存空间。
如果要队列太多,那就换个扩充数组容量,也就是换个更大的数组,这时候又要把数组中所有内容全部拷贝到新数组,复杂度为O(N)取决于数组内队列元素N的个数,太麻烦了!
那么能不能有线性的队列呢?队列多的情况能容纳,少的情况又可以不浪费内存空间。链表
嵌入式话多:
在RTOS中
消息队列:使用数组(环形缓冲区)实现。
任务状态管理:使用链表实现。
二. 链表如何实现队列
因为队列操作复杂度为O(1)。并且插入和删除都是两个不同的方向,你链表怎么实现?
这个链表我知道头插法的复杂度是O(1)。
但你这里还有个删除啊,因为队列的插入和删除是两个不同方向,所以你删除要遍历到尾节点,这不是变成了O(n)么,这要如何解决?
前辈们的解决办法是引入一个尾指针。时刻指向尾节点,且头指针是链表一直存在的,所以不用再添加头指针。
头指针用于释放元素。尾指针用于删除元素
三. 实现代码
一. 变量声明
struct Node
{
int data;
struct Node *next;
};
struct Node *front = NULL; // 队列头,初始值为NULL代表空
struct Node *rear = NULL; // 队列尾,初始值为NULL代表空
二. 插入函数Enqueue
坑:
①切记这个尾节点要更新,因为时刻更新才能保证插入元素的复杂度为O(1)
也就是这段代码:
else
{
rear->next = Temp; // 指向下一个元素,这里别忘记了。把尾元素的next指向Temp
rear = Temp; // 更新rear指向!!!
}
void EnQueue(int x) // 插入队列链表
{
struct Node *Temp = (struct Node *)malloc(sizeof(struct Node)); // 在堆上开辟一段内存
Temp->data = x;
Temp->next = NULL;
if (front == NULL && rear == NULL) // 为啥要判断两个呢?不能单独判断一个么.单独判断一个值会导致插入元素从头开始,但队列规定要从两个方向!
{
front = rear = Temp;
return; // 这里执行结束就要返回
}
else
{
rear->next = Temp; // 指向下一个元素,这里别忘记了。把尾元素的next指向Temp
rear = Temp; // 更新rear指向!!!
}
}
三. 删除函数Dequeue
没啥好说的,跟队列删除一样。
只不过如果头和尾元素指针指向同一个链对象,这时候删除最后一个元素,这时要初始化头front和尾rear的指向为NULL
void Dequeue() // 删除队列链表
{
struct Node *Temp = front; // 创建临时变量用于暂存链表头,因为后面要释放
if (IsEmpty()) // 队列链表为空,释放什么啊?!
{
printf("Queue List is NULL \n");
return;
}
else if (front == rear) // 如果这俩指向同一个元素,那就把他们一起释放
{
front = rear = NULL; // 两个指向为NULL
}
else
{
front = front->next; // 跳帧首元素指向
}
四. 判断队列是否为空
bool IsEmpty() // 判断队列链表是否为空.空返回true,非空返回true
{
if (front == NULL && rear == NULL)
return true;
else
return false;
}
五. 返回队列头
int Front() // 返回链表队列头
{
struct Node *Temp = front;
if (IsEmpty())
{
printf("Queue List is NULL \n");
return 0;
}
else
{
return front->data;
}
}
六. 遍历打印链表
这里也可这样写,思路是:
一.头和尾节点重合。那么代表打印结束。
void Print()
{
if (IsEmpty()) // 队列链表为空,释放什么啊?!
{
printf("Queue List is NULL \n");
return;
}
else
{
struct Node *Temp = front;
printf("QueueList is : ");
while (Temp != rear)
{
printf("%d ", Temp->data);
Temp = Temp->next;
}
printf("%d ", rear->data);
printf("\n");
}
}
二.头节点下个元素为NULL,代表走到队列链表的结束位置,那么代表打印结束。
void Print()
{
if (IsEmpty()) // 队列链表为空,释放什么啊?!
{
printf("Queue List is NULL \n");
return;
}
else
{
struct Node *Temp = front;
printf("QueueList is : ");
while (Temp != NULL)
{
printf("%d ", Temp->data);
Temp = Temp->next;
}
printf("\n");
}
}
四. 整体代码
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
struct Node
{
int data;
struct Node *next;
};
struct Node *front = NULL; // 队列头,初始值为NULL代表空
struct Node *rear = NULL; // 队列尾,初始值为NULL代表空
void EnQueue(int x); // 插入队列
void Dequeue(); // 删除队列
bool IsEmpty(); // 队列是否为空
int Front(); // 返回队列头
void Print(); // 打印数组元素
int main()
{
EnQueue(2); // 队列中插入元素2
EnQueue(3); // 队列中插入元素3
EnQueue(3); // 队列中插入元素3
Print(); // 打印
Dequeue(); // 队列中删除元素2
Print(); // 打印
return 0;
}
void EnQueue(int x) // 插入队列链表
{
struct Node *Temp = (struct Node *)malloc(sizeof(struct Node)); // 在堆上开辟一段内存
Temp->data = x;
Temp->next = NULL;
if (front == NULL && rear == NULL) // 为啥要判断两个呢?不能单独判断一个么.单独判断一个值会导致插入元素从头开始,但队列规定要从两个方向!
{
front = rear = Temp;
return; // 这里执行结束就要返回
}
else
{
rear->next = Temp; // 指向下一个元素,这里别忘记了。把尾元素的next指向Temp
rear = Temp; // 更新rear指向!!!
}
}
void Dequeue() // 删除队列链表
{
struct Node *Temp = front; // 创建临时变量用于暂存链表头,因为后面要释放
if (IsEmpty()) // 队列链表为空,释放什么啊?!
{
printf("Queue List is NULL \n");
return;
}
else if (front == rear) // 如果这俩指向同一个元素,那就把他们一起释放
{
front = rear = NULL; // 两个指向为NULL
}
else
{
front = front->next; // 跳帧首元素指向
}
free(Temp); // 因为Temp是临时指针变量,后续会自动销毁.不用担心他是否悬空
}
bool IsEmpty() // 判断队列链表是否为空.空返回true,非空返回true
{
if (front == NULL && rear == NULL)
return true;
else
return false;
}
int Front() // 返回链表队列头
{
struct Node *Temp = front;
if (IsEmpty())
{
printf("Queue List is NULL \n");
return 0;
}
else
{
return front->data;
}
}
void Print()
{
if (IsEmpty()) // 队列链表为空,释放什么啊?!
{
printf("Queue List is NULL \n");
return;
}
else
{
struct Node *Temp = front;
printf("QueueList is : ");
while (Temp != NULL)
{
printf("%d ", Temp->data);
Temp = Temp->next;
}
printf("\n");
}
}