17.队列:使用链表实现队列

一. 为什么要用链表实现队列

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");
    }
}

五. 运行情况

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值