当前位置:   article > 正文

NzN的数据结构--队列的实现

NzN的数据结构--队列的实现

         上一节中已经向大家介绍了栈的两种实现方法,今天我们来学习另一种线性存储结构--队列。先赞后看是好习惯!!!

目录

一、队列的概念及结构

二、用链表实现队列

1. 队列的初始化和销毁

2. 入队列

3. 出队列

4. 取队头

5. 取队尾

6. 判断队列是否为空

7. 计算队列大小

三、用数组实现队列

四、队列的应用


一、队列的概念及结构

        队列:只允许在一端插入数据,在另一端删除数据的特殊线性表,队列具有先进先出FIFO(First In First Out) 原则。

        入队列:进行插入操作的一端称为队尾

        出队列:进行删除操作的一端称为队头

二、用链表实现队列

        为了实现队列,我们需要一种数据结构,可以在一端添加元素,并在另一端删除元素,链表和数组都符合要求。那我们先来试试用链表实现队列。

        首先在头文件中声明队列的结果以及相关功能接口。

  1. //Queue.h
  2. #pragma once
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. #include<stdbool.h>
  6. #include<assert.h>
  7. typedef int QDataType;
  8. typedef struct QueueNode
  9. {
  10. int val;
  11. struct QueueNode* next;
  12. }QNode;
  13. //再定义一个结构体可以避免出现二级指针
  14. typedef struct Queue
  15. {
  16. QNode* phead;
  17. QNode* ptail;
  18. int size;
  19. }Queue;
  20. //队列的初始化和销毁
  21. void QueueInit(Queue* pq);
  22. void QueueDestroy(Queue* pq);
  23. //入队列
  24. void QueuePush(Queue* pq, QDataType x);
  25. //出队列
  26. void QueuePop(Queue* pq);
  27. //取队头
  28. QDataType QueueFront(Queue* pq);
  29. //取队尾
  30. QDataType QueueBack(Queue* pq);
  31. //判断队列是否为空
  32. bool QueueEmpty(Queue* pq);
  33. //计算队列大小
  34. int QueueSize(Queue* pq);

1. 队列的初始化和销毁

  1. void QueueInit(Queue* pq)
  2. {
  3. assert(pq);
  4. pq->phead = NULL;
  5. pq->ptail = NULL;
  6. pq->size = 0;
  7. }
  8. void QueueDestroy(Queue* pq)
  9. {
  10. assert(pq);
  11. QNode* cur = pq->phead;
  12. while (cur)
  13. {
  14. QNode* next = cur->next;
  15. //队列中元素依次释放
  16. free(cur);
  17. cur = next;
  18. }
  19. pq->phead = pq->ptail = NULL;
  20. pq->size = 0;
  21. }

2. 入队列

  1. void QueuePush(Queue* pq, QDataType x)
  2. {
  3. assert(pq);
  4. QNode* newnode = (QNode*)malloc(sizeof(QNode));
  5. if (newnode == NULL)
  6. {
  7. perror("malloc");
  8. exit(1);
  9. }
  10. newnode->val = x;
  11. newnode->next = NULL;
  12. if (pq->ptail)
  13. {
  14. //队列里已经有元素了
  15. pq->ptail->next = newnode;
  16. pq->ptail = newnode;
  17. }
  18. else
  19. {
  20. //插入的是队列的第一个元素
  21. pq->phead = pq->ptail = newnode;
  22. }
  23. pq->size++;
  24. }

3. 出队列

  1. void QueuePop(Queue* pq)
  2. {
  3. assert(pq);
  4. //温柔检查
  5. /*if (pq->phead == NULL)
  6. return;*/
  7. //暴力检查(推荐)
  8. assert(pq->phead != NULL);
  9. //一个节点
  10. if (pq->phead->next == NULL)
  11. {
  12. free(pq->phead);
  13. pq->phead = pq->ptail = NULL;
  14. }
  15. //多个节点
  16. else
  17. {
  18. QNode* next = pq->phead->next;
  19. free(pq->phead);
  20. pq->phead = next;
  21. }
  22. pq->size--;
  23. }

4. 取队头

  1. QDataType QueueFront(Queue* pq)
  2. {
  3. assert(pq);
  4. // 暴力检查
  5. assert(pq->phead != NULL);
  6. return pq->phead->val;
  7. }

5. 取队尾

  1. QDataType QueueBack(Queue* pq)
  2. {
  3. assert(pq);
  4. // 暴力检查
  5. assert(pq->ptail != NULL);
  6. return pq->ptail->val;
  7. }

6. 判断队列是否为空

  1. bool QueueEmpty(Queue* pq)
  2. {
  3. assert(pq);
  4. return pq->size == 0;
  5. }

7. 计算队列大小

  1. int QueueSize(Queue* pq)
  2. {
  3. assert(pq);
  4. return pq->size;
  5. }

三、用数组实现队列

        我们可以使用一个变量front指向队首元素的索引,并维护一个变量size用于记录队列长度。定义rear=front+size ,这个公式计算出的rear指向队尾元素之后的下一个位置。

        基于此设计,数组中包含元素的有效区间为[front,rear-1]

  • 入队操作:将输入元素赋值给rear索引处,并将size+1 。
  • 出队操作:只需将front+1 ,并将size-1 。

       在不断进行入队和出队的过程中,front和rear都在向右移动,当它们到达数组尾部时就无法继续移动了。为了解决此问题,我们可以将数组视为首尾相接的“环形数组”。

        对于环形数组,我们需要让front或rear在越过数组尾部时,直接回到数组头部继续遍历。这种周期性规律可以通过“取余操作”来实现,代码如下所示:

  1. typedef struct {
  2. int *nums;//用于存储队列元素的数组
  3. int front;//队首指针,指向队首元素
  4. int queSize;//尾指针,指向队尾 + 1
  5. int queCapacity;//队列容量
  6. } ArrayQueue;
  7. //队列初始化
  8. ArrayQueue *QueueInit(int capacity) {
  9. ArrayQueue *queue = (ArrayQueue *)malloc(sizeof(ArrayQueue));
  10. // 化数组
  11. queue->queCapacity = capacity;
  12. queue->nums = (int *)malloc(sizeof(int) * queue->queCapacity);
  13. queue->front = queue->queSize = 0;
  14. return queue;
  15. }
  16. //销毁队列
  17. void QueueDestroy(ArrayQueue *queue) {
  18. free(queue->nums);
  19. free(queue);
  20. }
  21. //判断队列是否为空
  22. bool QueueEmpty(ArrayQueue *queue) {
  23. return queue->queSize == 0;
  24. }
  25. //访问队首元素
  26. int QueueTop(ArrayQueue *queue) {
  27. assert(size(queue) != 0);
  28. return queue->nums[queue->front];
  29. }
  30. //入队
  31. void QueuePush(ArrayQueue *queue, int num) {
  32. if (size(queue) == capacity(queue)) {
  33. printf("队列已满\r\n");
  34. return;
  35. }
  36. //计算队尾指针,指向队尾索引+1
  37. //通过取余操作实现rear越过数组尾部后回到头部
  38. int rear = (queue->front + queue->queSize) % queue->queCapacity;
  39. //将num添加至队尾
  40. queue->nums[rear] = num;
  41. queue->queSize++;
  42. }
  43. //出队
  44. int QueuePop(ArrayQueue *queue) {
  45. int num = peek(queue);
  46. //队首指针向后移动一位,若越过尾部,则返回到数组头部
  47. queue->front = (queue->front + 1) % queue->queCapacity;
  48. queue->queSize--;
  49. return num;
  50. }

        以上实现的队列仍然具有局限性:其长度不可变。然而,这个问题不难解决,我们可以将数组替换为动态数组,从而引入扩容机制。

【总结】

        队列也可以通过数组和链表的结构实现,但使用链表的结构实现更优一些,因为如果使用数组,出队列在数组头上出数据,效率比较低。

四、队列的应用

        队列最实际的应用就是保持公平性的排队。第一个排队的就是1号,下一个人的号就是队尾的号+1。如果两个人同时来到窗口取号,就会产生一个竞争,这里涉及到操作系统中的原子操作和加锁概念等,也可以叫做生产者消费者模型。

        除此之外,队列还可以用于广度优先遍历bfs,而深度优先遍历dfs常通过递归/栈来实现。

  • 淘宝订单。购物者下单后,订单将加入队列中,系统随后会根据顺序处理队列中的订单。在双十一期间,短时间内会产生海量订单,高并发成为工程师们需要重点攻克的问题。
  • 各类待办事项。任何需要实现“先来后到”功能的场景,例如打印机的任务队列、餐厅的出餐队列等,队列在这些场景中可以有效地维护处理顺序。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/399946
推荐阅读
相关标签
  

闽ICP备14008679号