欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 教育 > 幼教 > 【初阶数据结构与算法】线性表之队列的定义与实现

【初阶数据结构与算法】线性表之队列的定义与实现

2025/9/25 22:38:06 来源:https://blog.csdn.net/hdxbufcbjuhg/article/details/143910326  浏览:    关键词:【初阶数据结构与算法】线性表之队列的定义与实现

在这里插入图片描述

文章目录

  • 一、队列的概念与结构
    • 1. 概念
    • 2.队列结构定义
  • 二、队列的实现
    • 1.队列的初始化和销毁
      • 初始化
      • 销毁
    • 2.队列的节点申请和入队列操作
      • 队列的节点申请
      • 入队列
    • 3.队列的判空和出队列操作
      • 队列的判空
      • 出队列
    • 4.取队头和队尾元素
      • 取队头元素
      • 取队尾元素
    • 5.获取队列有效节点个数
  • 三、队列源码

一、队列的概念与结构

1. 概念

   队列是一种只允许在⼀端进⾏插⼊数据操作,在另⼀端进⾏删除数据操作的特殊线性表,它具有先进先出FIFO(后进后出)的特性
   用来插入数据的那一端称为队尾,用来删除数据的一端则称为对头,插入数据被称为入队列,删除数据被称为出队列,队列的大致示意图如下:
在这里插入图片描述
   队列的数据从队尾插入,从队头删除,根据上图的示意,我们就可以发现,A先进入队列,B后进入队列,那么在出队的时候,就还是A先出,B后出,所以队列确实满足先进先出,后进后出的特性

2.队列结构定义

   队列和栈一样既可以选择数组做底层,也可以选择链表作为它的底层,那么哪一个更好呢?这个就需要我们去分析一下了
   如果采用数组,那么我们入队列就可以直接在数组最后添加数据,时间复杂度为O(1),比较方便,但是当我们要从数组头删数据就比较麻烦了,因为数组头删的时间复杂度为O(N),效率并不是很高
   我们再来分析一下使用链表怎么样,如果我们使用链表实现队列,那么在头部删除没有问题了,时间复杂度为O(1),但是我们要在最后插入数据,对于链表来说,尾插的时间复杂度是O(N),那么到底选哪一个才好?
   这里就不卖关子了,我们队列这个结构最好使用链表完成,我们要知道为什么链表尾插的时间复杂度为O(N),因为我们找不到尾结点,需要循环遍历链表找到尾结点才能尾插,那么我们能不能想办法直接找到尾结点呢
   当然可以,链表不好的地方就是不方便找尾结点,既然知道了不足,我们在定义队列时就可以这样定义:让队列里面包含两个指针,分别指向链表的头和尾节点,反正队列只需要操作队头和队尾的数据
   我们拿到队尾和队头的节点后基本上就可以完成队列的操作了,其它节点我们并不关心,但是由于队列是建立在链表结构之上,所以我们要先定义一个类似于链表节点的队列节点结构,然后才能让队列指向它的头和尾,具体结构如下:

typedef int QDataType;//队列中一个节点的定义
//看起来和链表差不多
//但是要注意这里它已经变成队列的节点了
typedef struct QueueNode
{QDataType data;struct QueueNode* next;
}QueueNode;//真正使用的对列的定义
typedef struct Queue
{//指向队头的指针QueueNode* phead;//指向队尾的指针QueueNode* ptail;
}Queue;

   在上面的队列结构里,只有两个指向队列头和尾的指针,整个队列的操作就可以基本上只通过这两个指针来操作了,我们队列节点就是仿造链表节点来进行定义的,所以可以说我们队列的底层结构还是链表,只是对它进行封装后就变成了我们的队列
   那么这样的结构是否完美了呢?既然我们要实现一个队列,就应该尽可能的想到它的更多应用场景,如果我们需要计算这个队列的长度,是不是就会很麻烦,又要遍历整个队列才能得出结果,时间复杂度为O(N)
   所以为了优化这一点,在队列结构中做了优化,我们加入了一个新的成员,它用来记录队列的长度,而队列节点结构的定义不变,如下:

typedef int QDataType;//队列一个节点的定义
typedef struct QueueNode
{QueueDataType data;struct QueueNode* next;
}QueueNode;//真正使用的对列的定义
typedef struct Queue
{//用来记录队列的长度int size; //指向队头的指针QueueNode* phead;//指向队尾的指针QueueNode* ptail;
}Queue;

   如果对链表还不熟悉的话,推荐先学习链表:【初阶数据结构与算法】线性表之单链表的定义与实现

二、队列的实现

1.队列的初始化和销毁

初始化

   队列里面保存的是指向队头和队尾的指针,以及保存队列的长度的size,分别置空即可,如下:

//队列初始化
void QueueInit(Queue* pq)
{assert(pq);pq->size = 0;pq->phead = pq->ptail = NULL;
}

销毁

   队列的销毁也很简单,只需要通过队列中存储的头指针,遍历销毁整个队列即可,最后将存储的指针和队列大小置空,如下:

//队列销毁
void QueueDestroy(Queue* pq)
{assert(pq);QueueNode* pcur = pq->phead;while (pcur){QueueNode* del = pcur;pcur = pcur->next;free(del);}pq->phead = pq->ptail = NULL;pq->size = 0;
}

2.队列的节点申请和入队列操作

队列的节点申请

   由于我们入队列必须要申请节点,所以我们将其封装为一个函数,队列的节点申请和链表的节点申请类似,直接malloc一个节点大小的空间出来,然后根据给出的数据简单初始化一下这个节点即可,但是不要忘记了判断malloc返回值,以免节点申请失败,如下:

//队列节点申请
QueueNode* QueueBuyNode(QDataType x)
{QueueNode* newnode = malloc(sizeof(QueueNode));if (newnode == NULL){perror("malloc");return;}newnode->data = x;newnode->next = NULL;return newnode;
}

入队列

   解决了队列节点的申请后,我们就可以直接来实现入队列的操作了,由于我们队列结构中已经有了尾结点的地址,所以我们可以直接让尾结点的next指针指向新节点,然后让队列中保存的尾结点更新一下
   但是我们要注意一下,我们在第一次入队列时,队列为空,那么尾结点自然也就不存在,此时按照上面的步骤去操作就要出错,所以我们最好判断一下,如果队列为空,直接让头和尾指向新节点,让size++,否则就按照上面的逻辑走,如下:

//入队列
void QueuePush(Queue* pq, QDataType x)
{assert(pq);QueueNode* newnode = QueueBuyNode(x);if (pq->phead = NULL){pq->phead = pq->ptail = newnode;pq->size++;return;}pq->ptail->next = newnode;pq->ptail = newnode;pq->size++;
}

3.队列的判空和出队列操作

队列的判空

   我们要出队列就一定要保证队列中有数据,所以我们先来实现队列的判空,队列的判空也非常简单,只需要判断队列中的phead是否为空即可,如下:

//队列判空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->phead == NULL;
}

出队列

   有了队列判空之后,我们就可以在出队列前判断一下队列是否为空,不为空我们才继续进行出队列操作,我们之前说过,出队列是在删除队列的头
   由于我们队列结构中有队列的头,所以删除队列的头结点也很简单,可以先创建一个队列节点来保存头结点的下一个节点,然后直接释放掉头结点,释放掉之后让pq中的头指针重新指向保存的节点
   当然,出队列说明队列的有效节点个数要少一个了,所以不要忘了让size- -,如下:

//出队列
void QueuePop(Queue* pq)
{assert(!QueueEmpty(pq));QueueNode* next = pq->phead->next;free(pq->phead);pq->phead = next;pq->size--;
}

4.取队头和队尾元素

取队头元素

   取队头元素就是取出队列中第一个元素,直接返回phead中的元素即可,如下:

//取队头数据
QDataType QueueFront(Queue* pq)
{assert(pq);return pq->phead->data;
}

取队尾元素

   取队尾元素就是取出队列中最后一个元素,直接返回ptail中的元素即可,如下:

//取队尾数据
QDataType QueueBack(Queue* pq)
{assert(pq);return pq->ptail->data;
}

5.获取队列有效节点个数

   我们队列有效元素个数一直是size在记录,直接将其返回就可以了,如下:

//获取队列有效元素个数
int QueueSize(Queue* pq)
{assert(pq);return pq->size;
}

三、队列源码

#pragma once#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>typedef int QDataType;//队列一个节点的定义
typedef struct QueueNode
{QDataType data;struct QueueNode* next;
}QueueNode;//真正使用的对列的定义
typedef struct Queue
{int size;QueueNode* phead;QueueNode* ptail;
}Queue;//队列初始化
void QueueInit(Queue* pq)
{assert(pq);pq->size = 0;pq->phead = pq->ptail = NULL;
}//队列销毁
void QueueDestroy(Queue* pq)
{assert(pq);QueueNode* pcur = pq->phead;while (pcur){QueueNode* del = pcur;pcur = pcur->next;free(del);}pq->phead = pq->ptail = NULL;pq->size = 0;
}//队列节点申请
QueueNode* QueueBuyNode(QDataType x)
{QueueNode* newnode = malloc(sizeof(QueueNode));if (newnode == NULL){perror("malloc");return NULL;}newnode->data = x;newnode->next = NULL;return newnode;
}//入队列
void QueuePush(Queue* pq, QDataType x)
{assert(pq);QueueNode* newnode = QueueBuyNode(x);if (pq->phead == NULL){pq->phead = pq->ptail = newnode;pq->size++;return;}pq->ptail->next = newnode;pq->ptail = newnode;pq->size++;
}//队列判空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->phead == NULL;
}//出队列
void QueuePop(Queue* pq)
{assert(!QueueEmpty(pq));QueueNode* next = pq->phead->next;free(pq->phead);pq->phead = next;pq->size--;
}//取队头数据
QDataType QueueFront(Queue* pq)
{assert(pq);return pq->phead->data;
}//取队尾数据
QDataType QueueBack(Queue* pq)
{assert(pq);return pq->ptail->data;
}//获取队列有效元素个数
int QueueSize(Queue* pq)
{assert(pq);return pq->size;
}

   那么今天关于队列的定义和实现就分享到这里,有什么疑问欢迎提出,下一篇我将会带大家做几道关于栈和队列的练习,随后就进入二叉树的学习,小小期待一下吧!
   bye~

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词