目录

1. 队列的介绍

1.1 队列的概念

1.2 队列的优点

1.3 队列的实现方式

2. 队列的链表实现

2.1 方案确定        

2.2 初始化队列

2.3 队列的判空

2.4 入队

2.5 出队

2.6 获取队头队尾元素

3. 队列的数组实现

3.1 初始化队列

3.2 队列的判空

3.3 入队

3.4 出队

3.5 获取队头队尾元素


1. 队列的介绍

1.1 队列的概念

        队列是一种在一端插入然后在另外一端删除的线性表,插入的那端被称为队头(front),删除的那端被称为队尾(rear)。

1.2 队列的优点

        队列的优点在于处理排队场景时效率较高,举个例子,大家都去登录某个网页,但是由于网站的登录人数是有限制的,因此需要登录时间最长也就是最早登录的用户强制离线。这里就利用了队列的先进先出的特性。

1.3 队列的实现方式

        和栈类似,队列的实现方式有两种,链表实现和数组实现,其存储方式又被称为链式存储和顺序存储。我个人推荐使用链表实现队列,因为用数组实现队列时需要初始化固定队列允许的最大长度,在队列元素数量较小时会造成空间的浪费,在队列元素数量过多时又得去手动更改最大存储长度。

        队列的链表实现需要二级指针和链表相关知识,不是很熟悉相关应用的话可以先看下面这两篇文章。

【C语言】二级指针的应用_c语言 二级指针-CSDN博客

【C语言】常用的数据结构-单链表-CSDN博客

2. 队列的链表实现

2.1 方案确定        

        在用链表实现队列之前,我们需要解决三个问题。

(1)链表节点的指针域要包含几个指针才能满足队列功能?

(2)针对队列先进先出的特性,我们是选择(表头插入,表尾删除)的方式还是(表头删除,表尾插入)的方式)呢?

(3)链表中的头结点是否需要存储数据?

        首先回答第一个的问题,指针域推荐包含两个指针,那么为什么要包含两个指针呢,像单链表节点一样节点中只用一个指针可以吗。答案是可以的但是这样做耗时太长。举例来说,如果节点中只有一个指针,在表尾插入新元素时需要先查找一遍整个链表,找到最后一个节点后再进行插入操作,这样会浪费大量时间。为了解决这个问题,我们在结构体中声明两个指针,next指针用于存储下一个节点信息,rear指针用于指向链表末尾的节点。从而达到快速定位链表末尾的效果。

        第二个问题要选择是(表头删除,表尾插入)的方式,这是因为(表头插入 ,表尾删除)这种方式在删除表尾时无法确定倒数第二个节点的位置。从而无法将上一个节点的next指针指向NULL。

        第三个问题我建议头结点不要存储数据,将头节点当做记录节点来使用,如果头结点存储数据的情况下删除元素会使表头地址不断地变化,从而导致删除代码的流程变复杂。

2.2 初始化队列

        队列的初始化非常简单,创建一个新的节点并且把指针域指向NULL 即可。因为初始化时需要

 在函数中修改指针指向的地址,所以需要使用二级指针。

//声明队列节点
struct node
{
    int element;
    struct node *next;   //用于记录下一个节点地址
    struct node *rear;   //用于记录队尾节点地址
}

//初始化队列函数
void init_queue(struct node **pNode)
{
    *pNode = (struct node*)malloc(sizeof(struct node));
    if(NULL == *pNode)
    {
        printf("栈初始化申请内存失败");
        return NULL;
    }
    (*pNode)->next = NULL;
    (*pNode)->rear = NULL;
}
 
//测试用例
struct node *pQueue = NULL;
init_queue(&pQueue);

2.3 队列的判空

        因为头结点不存储数据,所以对队列的判空其实就是对头结点的下一个节点的判空。

//队列判空  1-空 0-非空
bool is_empty(struct node *pQuene)
{
    assert(NULL != pQuene);
    return (NULL == pQuene->next);
}

//测试用例
struct node *pQuene;
is_empty(pQuene);

2.4 入队

         入队就是在队列中插入新元素,我们使用尾插法,在链表尾部插入新元素。

//从队尾入队
void en_queue(struct node *pQueue, int element)
{
    //校验入参
    assert(NULL == pQueue);
    //入参指针不要直接使用
    struct node *pNode = pQueue;

    //创建新节点
    struct node *pNew = NULL;
    pNew = (struct node*)malloc(sizeof(struct node));
    if(NULL == pNew)
    {
        printf("新节点申请内存失败");
        return;
    }
    pNew->element = element;
    
    //从队尾插入新元素
    if(is_empty(pNode))   
    {   
        //连接新节点
        pNode->next = pNew;
        pNew->next = NULL;
        
        //记录队尾位置
        pNode->rear = pNew;
    }
    else
    {
        //连接新节点,pNode->rear是之前队尾地址
        pNode->rear->next = pNew;
        pNew->next = NULL;
        
        //重新记录队尾位置
        pNode->rear = pNew;
    }
}

//测试用例
struct node *pQuene;
en_queue(pQuene, 10);

2.5 出队

        出队就是删除队列元素,我们刚才选择在队尾入队,现在在队头出队,就是删除头节点的下一个节点。

//从队头出队
void de_queue(struct node *pQueue)
{
    //校验入参
    assert(NULL == pQueue);
    //入参指针不要直接使用
    struct node *pNode = pQueue;
    
    //删除操作
    if(is_empty(pNode))
    {   
        printf("空队列,无法删除");
    }
    else
    {   
        pNode->next = pNode->next->next;
        free(pNode->next);
    }
}

//测试用例
struct node *pQueue;
de_queue(pQueue);

2.6 获取队头队尾元素

        获取队头队尾元素非常简单,获取队头元素就是获取pNode->next地址的元素,获取队尾元素就是获取pNode->rear地址的元素 。

//获取队列元素   flag为1返回队头,flag为0返回队尾
int get_Quene_element(struct node *pQuene, int flag)
{
    //校验入参
    assert(NULL == pQueue);
    //入参指针不要直接使用
    struct node *pNode = pQueue;

    if(is_empty(pNode))
    {   
        printf("空队列,无法获取");
        return 0;
    }

    if(flag)
    {
        return pNode->next->element;    
    }
    else
    {
        return pNode->rear->element;
    }

}

//测试用例
struct node *pQuene;
int Queuehead_value, QueueRear_value;
Queuehead_value = get_Quene_element(pQuene, 1);
QueueRear_value = get_Quene_element(pQuene, 0);
​

3. 队列的数组实现

        正如前文所说,用数组实现队列需要固定长度并造成空间浪费,但是它的实现比链表要简单很多。我们这里采用双下标的方式来实现链表的操作,用front下标来实现队头元素的删除,用rear下标来实现队尾元素的插入。

3.1 初始化队列

        初始化队列就是创建一个结构体后将front和rear下标都赋非法值。

#define MAXIDX 100  //队列最大容量

//队列结构体
struct Queue 
{
    int data[MAXIDX];
    int front;
    int reat;
}

//初始化队列
void init_queue(strcut Queue *queue)
{
    (*queue).front = -1;
    (*queue).rear = -1;
}

//测试用例
struct Queue queue;
init_queue(&queue);

3.2 队列的判空

          队列的判空就是看front值是否合法。

//队列判空   1为空   0为非空
bool is_QueueEmpty(struct Queue queue)
{
    return (queue.front < 0) ? TRUE : FALSE;
}

3.3 入队

        入队就是将新元素插入到队尾即可,需要注意队满时无法入队。

//从队尾入队
void en_queue(struct node *Queue, int element)
{
    //队满时无法入队
    if(rear >= (MAXIDX + 1))
    {
        printf("队列满,无法入队");
        return ;
    }

    //入队
    (*Queue).data[++rear] = element;
}

//测试用例
struct node pQuene;
en_queue(&pQuene, 10);

3.4 出队

        出队就是将队头元素取出,即front++即可,需要注意队空时无法出队。

//从队头出队
void en_queue(struct node *Queue, int element)
{
    //队空时无法出队
    if(front <= 0)
    {
        printf("队列空,无法出队");
        return ;
    }

    //出队
    (*Queue).data[++front] = 0;
}

//测试用例
struct node pQuene;
en_queue(&pQuene, 10);

3.5 获取队头队尾元素

        获取队头队尾元素就是读取front和rear中的元素即可。

//获取队列元素   flag为1返回队头,flag为0返回队尾
int get_Quene_element(struct node pQuene, int flag)
{
    if(is_empty(pNode))
    {   
        printf("空队列,无法获取");
        return 0;
    }

    if(flag)
    {
        return pNode.data[front];    
    }
    else
    {
        return pNode.data[rear];
    }

}

//测试用例
struct Quene queue;
int Queuehead_value, QueueRear_value;
Queuehead_value = get_Quene_element(queue, 1);
QueueRear_value = get_Quene_element(queue, 0);
​

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐