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

news/2024/11/25 14:16:48/

在这里插入图片描述

文章目录

  • 一、队列的概念与结构
    • 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~


http://www.ppmy.cn/news/1548976.html

相关文章

【MogDB】MogDB5.2.0重磅发布第八篇-支持PLSQL编译全局缓存

前言 在我之前的文章中有提过&#xff0c;原生PG对于重度存储过程的应用系统适配&#xff0c;具有一个致命缺陷&#xff0c;即原生PG中的plsql是会话级缓存&#xff0c;这意味着每个会话在第一次执行某个存储过程时&#xff0c;都需要对这个存储过程进行编译&#xff0c;并且将…

.NET9 - 新功能体验(一)

被微软形容为“迄今为止最高效、最现代、最安全、最智能、性能最高的.NET版本”——.NET 9已经发布有一周了&#xff0c;今天想和大家一起体验一下新功能。 此次.NET 9在性能、安全性和功能等方面进行了大量改进&#xff0c;包含了数千项的修改&#xff0c;今天主要和大家一起体…

力扣 无重复字符的最长字串-3

无重复字符的最长字串-3 class Solution { public:// 解决方法&#xff1a;双指针int lengthOfLongestSubstring(string s) { // 如果字符串为空&#xff0c;直接返回0if (s.length() 0)return 0;// 如果字符串不为空&#xff0c;字符串每个字符都不同的情况下&#xff0c;最…

鸿蒙NEXT开发案例:血型遗传计算

【引言】 血型遗传计算器是一个帮助用户根据父母的血型预测子女可能的血型的应用。通过选择父母的血型&#xff0c;应用程序能够快速计算出孩子可能拥有的血型以及不可能拥有的血型。这个过程不仅涉及到了简单的数据处理逻辑&#xff0c;还涉及到UI设计与交互体验的设计。 【…

引用类型的局部变量线程安全问题分析——以多线程对方法局部变量List类型对象实例的add、remove操作为例

引用类型的局部变量线程安全问题 背景注意事项分析步骤拆解&#xff08;文字描述时序图&#xff09; 背景 最近博主在看B站上的一个JUC并发编程视频中&#xff0c;碰到了一个比较有争议性的局部变量线程安全讨论问题。 先贴代码如下&#xff1a; Slf4j(topic "c.Threa…

数组作为函数参数--选择排序

#include<stdio.h> #define n 6 void s(int a[])//写a的话&#xff0c;是地址 { int i,j; for(i0;i<n-2;i) { for(ji1;j<n-1;j) { if(a[j]<a[i]) { int ta[i]; a[i]a[j];…

大模型时代的具身智能系列专题(十三)

迪士尼研究中心 瑞士苏黎世迪斯尼研究中心致力于不同领域的业务活动&#xff0c;其中包括电影、电视、公园和度假村以及消费产品。我们针对所有这些领域进行科研工作。我们开发能使我们将后道生产元素整合到前级生产中的技术。由此可节省许多昂贵的效果&#xff0c;这些效果最…

python爬虫初体验(五)—— 边学边玩小游戏

1. 打开浏览器 利用webbrowser 模块的 open()函数可以启动一个新浏览器&#xff0c;打开指定的 URL。 import webbrowser webbrowser.open(http://inventwithpython.com/) 2. 猜数字游戏 # -*- coding: utf-8 -*- # This is a guess the number game. import randomsecretN…