当前位置:   article > 正文

数据结构:队列_数据结构队列

数据结构队列

目录

 一、队列的结构与概念

二、队列的实现

2.1、队列的一些主要功能

2.2.1 入队列

2.2.2 出队列

2.2.3 队列判空 

2.2.4 取队头数据和取队尾数据

2.2.5 判断队列中的数据个数

2.2.6 队列销毁

总结 


 一、队列的结构与概念

概念:队列只允许在一端进行插入数据操作,在另一端进行删除数据的特殊线性表,队列具有先进先出。其中入队列的一端我们称为队尾,出队列的一端我们称为队头。

那么从图中我们便可以大致看出,数据是从队尾进入的,出数据是从队头出的。

在队列底层结构类型的选择上,我们主要选择两种,一种是数组,另一种是链表。本篇博客主要以链表作为底层结构类型进行讲解 。因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。

二、队列的实现

2.1、队列的一些主要功能

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<assert.h>
  4. #include<stdbool.h>
  5. //定义队列结构
  6. typedef int QDataType;
  7. typedef struct QueueNode //其实就相当于是一个链表
  8. {
  9. int data;//存储数据
  10. struct QueueNode* next;//指向下一个节点的指针
  11. }QueueNode;
  12. typedef struct Queue
  13. {
  14. QueueNode* phead; //指向头节点的指针
  15. QueueNode* ptail;//指向尾节点的指针
  16. int size;//用来统计队列中的数据个数
  17. }Queue;
  18. //第一步:队列的初始化
  19. void QueueInit(Queue* pd);
  20. //入队列,队尾
  21. void QueuePush(Queue* pd, QDataType x);
  22. //出队列,队头
  23. void QueuePop(Queue* pd);
  24. //队列判空
  25. bool QueueEmpty(Queue* pd);
  26. //取队列头数据
  27. QDataType QueueFront(Queue* pd);
  28. //取队尾数据
  29. QDataType QueueBack(Queue* pd);
  30. //判断队列中的有效元素
  31. int QueueSize(Queue* pd);//这里因为返回的肯定是一个整形的数据,所以直接使用int进行定义
  32. //队列的销毁
  33. void QueueDestroy(Queue* pd);

 从上述代码来看,我们可以看出队列这里给出的共有7个功能现在我们一个一个展开来讲。

2.2.1 入队列
  1. void QueuePush(Queue* pd, QDataType x)
  2. {
  3. assert(pd);
  4. //申请一个新的节点
  5. QueueNode* NewNode = (QueueNode*)malloc(sizeof(QueueNode));
  6. if (NewNode == NULL)
  7. {
  8. perror("malloc fail");
  9. exit(1);
  10. }
  11. //申请完新节点之后一定要记得初始化链表,将X赋给data,next指针置为空
  12. NewNode->data = x;
  13. NewNode->next = NULL;
  14. //先判断队列是不是为空
  15. if (pd->phead == NULL)
  16. {
  17. pd->phead = pd->ptail = NewNode;
  18. }
  19. //队列不为空,将NewNode赋值给ptail
  20. else
  21. {
  22. pd->ptail->next= NewNode;
  23. pd->ptail = NewNode;//将ptail移到新节点的位置
  24. }
  25. pd->size++;
  26. }

首先我们先判断接收的指针是否为一个空指针,如果为空指针就会触发断言报错。好,现在来讲解代码,首先我们插入一个新的数据我们需要申请一个新的节点,那么申请新的节点这里我们需要用到malloc,申请多大的空间呢?申请一个QueueNode 这个链表结构体大小的空间。申请完空间之后,我们要将值赋给这个新节点下面的data,同时将新节点的下一个数据置空,那么我这里就画个图演示一下。

画得有点粗糙,不过也可以方便理解,当我们插入一个新节点之后,我们需要将ptail(尾节点)的下一个节点指向新节点,然后将ptail移到新的节点。

可以看到,我么已经成功插入了四个数据。

2.2.2 出队列
  1. void QueuePop(Queue* pd)
  2. {
  3. assert(pd);
  4. //判断队列是否为空
  5. assert(!QueueEmpty(pd));
  6. //只剩一个节点的情况,避免ptail成为野指针
  7. if (pd->phead == pd->ptail)
  8. {
  9. free(pd->phead);
  10. pd->phead = pd->ptail = NULL;
  11. }
  12. else
  13. {
  14. //删除第一个节点的元素
  15. QueueNode* next = pd->phead->next;
  16. free(pd->phead);
  17. pd->phead = next;
  18. }
  19. pd->size--;
  20. }

那么出队列我们是从队头进行出队列。由于是删除队头的一个数据,所以我们不需要再进行申请一个新的空间,但是我们首先要进行判断这个队列它本身是不是一个空的队列,所以我们首先要进行判空,如果是一个空队列,那么就无法进行出队列的操作。但是出队列的过程中我们需要考虑到两种情况,首先就是如果队列只有一个数据了我们该怎么办,另外一种当然就是还有很多数据的情况啦。假设现在我们只有一个数据了,那么就是ptail = phead(首节点)的情况,那么这个时候我们释放掉ptail或者phead都是可以的,因为都在同一个位置,那么释放完之后我们记得要给ptail和phead置空,避免野指针的出现。那么数据较多的情况,我们首先新建立一个指针next,将phead的下一个节点存储到next里面,防止释放掉phead之后找不到phead的下一个节点,释放完之后我们再将next的值赋给phead就可以了。这里我就不演示了,大家可以自行进行调试。

2.2.3 队列判空 
  1. bool QueueEmpty(Queue* pd)
  2. {
  3. assert(pd);
  4. return pd->phead == NULL && pd->ptail == NULL;
  5. }

那么,队列判空就非常的简单啦,只要phead和ptail都为空,那么队列就会为空。这里我们使用的是一个布尔类型,当队列为空时返回true,不是则返回false。

2.2.4 取队头数据和取队尾数据
  1. QDataType QueueFront(Queue* pd)
  2. {
  3. assert(pd);//不可以传进来一个空指针
  4. assert(!QueueEmpty(pd));//队列里面要有数据,没有数据无法读取
  5. return pd->phead->data;
  6. }
  7. //取出队尾数据
  8. QDataType QueueBack(Queue* pd)
  9. {
  10. assert(pd);
  11. assert(!QueueEmpty(pd));
  12. return pd->ptail->data;
  13. }

由于取出队头和队尾的数据这个过程比较的简单,我就把它们俩放到一起来讲了。同样我们要判断这是不是一个空队列,如果是一个空队列我们又怎么取得出里面的数据呢?取出队头我们就返回头节点对应的数据就可以了,队尾同样的。

2.2.5 判断队列中的数据个数
  1. int QueueSize(Queue* pd)
  2. {
  3. //int size = 0;
  4. //assert(pd);
  5. //QueueNode* pcur = pd->phead;//定义一个指针指向头节点
  6. //while (pcur)//当PCUR为空指针的时候跳出循环
  7. //{
  8. // size++;
  9. // pcur = pcur->next;
  10. //}
  11. return pd->size;
  12. }

判断元素个数有两种方法,其中一种是遍历,另外一种就是在队列的结构体中在加上一个成员size。我选择后者的原因是第二种效率会更高。假设我们使用第一种遍历的方法去判断有效数据个数,那么我们每一次判断都要进行一次遍历,假设我们现在有N个数据,那么时间复杂度就会是O(N),可见效率是非常慢的,所以我们这里选择使用size。在插入数据的时候size++,在删除数据的时候size--。大家可以回过头看看插入和删除的代码是不是最后一行都有一个size;

2.2.6 队列销毁
  1. void QueueDestroy(Queue* pd)
  2. {
  3. assert(pd);
  4. assert(!QueueEmpty(pd));//如果队列本身就为空,就没有必要再进行销毁了
  5. QueueNode* pcur = pd->phead; //定义一个PCUR指针,然后循环去销毁队列
  6. while (pcur)
  7. {
  8. QueueNode* next = pcur->next;
  9. free(pcur);
  10. pcur = next;
  11. }
  12. pd->phead = pd->ptail = NULL;
  13. pd->size = 0;
  14. }

队列的销毁稍微复杂一点点,首先当然还是要判断这是不是一个空的队列,如果本身就是一个空的队列,那我们还销毁个der啊。在这里我们定义一个新的指针pcur 同时指向第一个phead节点。开始循环进行销毁,当pcur为空时,结束循环。首先我们要将pcur->next 给next,防止pcur被释放之后找不到下一个节点,然后将next重新给pcur。最后销毁完成之后,我们要记得给phead和ptail置空,同时size置0。

总结 

好了,队列的数据结构到这里就差不多结束了,总体来说当我们有顺序表和链表的基础之后,队列这个数据结构相对来说还是非常简单的。最后大家一定要多动手操作,一定要自己手敲一遍。

       

求三连!!!!!!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/正经夜光杯/article/detail/956547
推荐阅读
相关标签
  

闽ICP备14008679号