当前位置:   article > 正文

10-数据结构-队列(C语言)_输出每次入队序列

输出每次入队序列

队列


目录

目录

队列

一、队列基础知识

二、队列的基本操作

1.顺序存储

​编辑 (1)顺序存储

(2)初始化及队空队满

(3)入队

(4)出队

(5)打印队列

(6)循环队列

(7)顺序可运行总代码:

2.链式存储

(1)链式存储定义

(2)初始化

(3)进队

(4)出队

(5)打印链队

(6)链队可运行总代码

3.双端队列。

(1)输入受限的双端队列

(2)输出受限的双端队列

(3)例题:

一、队列基础知识

        简介:队列是一种先进先出(FIFO)的线性数据结构,是一种只允许在一端进行插入操作(队尾),在另一端进行删除操作的数据结构(队头)。插入操作在队列的末尾进行,删除操作在队列的前端进行,即队头元素先出队,后插入的元素排在队尾。

        队列是一种广泛应用于计算机科学领域的数据结构,常用于实现消息队列、任务队列、缓冲队列等。在算法设计中,队列可以用于广度优先搜索(BFS)、模拟银行排队等问题。同时,队列还常与栈结构搭配使用,实现更复杂的算法和数据结构。

二、队列的基本操作

        简介:队列可以使用数组或链表实现。常见的队列操作包括:入队(enqueue)、出队(dequeue)、获取队头元素(front)、获取队列长度(size)等。

1.顺序存储

        简介:就是数组+两个标记队头和队尾的变量,构成的结构体。

        优点:简单易实现。

        缺点:容易出现假溢出问题(队列未满时,向队列尾插入元素却因为队列头指针没有移动而导致队列满的情况)。

 (1)顺序存储

        即一个一维数组和两个标记队头和队尾的变量。

代码如下:

  1. //队列的顺序存储
  2. #define MaxSize 10
  3. typedef struct
  4. {
  5. int data[MaxSize];
  6. int front;
  7. int rear;
  8. // int count;//记录队列中实际元素个数,可先不用
  9. }SqQueue;
(2)初始化及队空队满

        初始化:

  1. //队列初始化
  2. void QueueInit(SqQueue *q)
  3. {
  4. q->front=0;
  5. q->rear=0;
  6. }

        队空:当队头和队尾相等的时候便为空。不仅仅限制于都等0,因为两个变量都在一直变化。

  1. //队空
  2. bool QueueEmpty(SqQueue *q)
  3. {
  4. if(q->front == q->rear )
  5. return true;
  6. }

        队满:这里面队满不好判定,当rear>MaxSize-1时,会出现假溢出现象,不算队满。不过可以在队列顺序存储时结构体里面加一个计时器,出队一次,count--,入队一次count++,当count==MaxSize-1时,队满。

  1. typedef struct
  2. {
  3. int data[MaxSize];
  4. int front;
  5. int rear;
  6. int count;
  7. }SqQueue;
  8. //队满
  9. bool QueueFull(SqQueue *q)
  10. {
  11. if(q->count == MaxSize-1)
  12. return true;
  13. }
(3)入队

        从队尾rear处进行遍历,入队赋值。随后rear+1,后撤。

  1. //入队
  2. void EnQueue(SqQueue *q,int x)
  3. {
  4. //如果队满了
  5. if(QueueFull(q)==false)
  6. exit(-1);
  7. //给队尾处赋值
  8. q->data[q->rear]=x;
  9. //移动队尾标记
  10. q->rear++;
  11. //数据数+1
  12. q->count++;
  13. }
(4)出队
  1. //出队
  2. void OutQueue(SqQueue *q,int *e)
  3. {
  4. //判断非法情况,是否为空
  5. if(q->front == q->rear)
  6. exit(-1);
  7. //取出队头元素,赋值给e
  8. *e=q->data[q->front];
  9. //队头指针++
  10. q->front++;
  11. //元素个数-1
  12. q->count--;
  13. }
(5)打印队列
  1. void QueuePrint(SqQueue *q)
  2. {
  3. int i=0;
  4. printf("目前队列中有%d个数据\n",q->count);
  5. //从队头遍历到队尾
  6. for(i=q->front;i<q->rear;i++)
  7. {
  8. printf("%d ",q->data[i]);
  9. }
  10. printf("\n");
  11. }
(6)循环队列

        简介:由于普通的顺序存储队列,存在假溢出现象,导致出队后,出现的空缺,没法补上,这时候循环队列就出手了。它则是多了个取余操作,每次模数组最大值,给溢出的数字,控制在最大值之内,达到循环,形成了一个环。

        即每一次标记队头和队尾遍历,变换时,变为了q->front=(q->front+1)%MaxSize;q->rear=(q->rear+1)%MaxSize;

        此外,循环队列,在判断队满时,一般两种操作:

        一个是牺牲最后一个格子,当rear+1=front时,此时队满。

        另一个操作则是:像我最开头,在结构体定义里面加一个记录实际数据的计数器,每次出队入队,计数器进行相应的加减。当计数器等于MaxSize时,队满。

        此外,循环队列的长度计算为:[MaxSize-(q->rear  -  q->front)]%MaxSize;但如果,之前在结构体里面加了一个计数器,则直接打印计数器即可。

        下面时循环队列的操作。

        入队:(只不过比之前入队,多了一个判断队满的判断,以及移动队尾指针时取余)

  1. void CycEnQueue(SqQueue *q,int x)
  2. {
  3. //如果队满了
  4. if(QueueFull(q)==1) //可以在结构体中加个计数器
  5. exit(-1);
  6. //if(q->rear+1 == q->front) //也可以牺牲一个存储单元,用来判断队满
  7. // exit(-1);
  8. //给队尾处赋值
  9. q->data[q->rear]=x;
  10. //移动队尾标记
  11. q->rear=(q->rear+1)%MaxSize;
  12. //数据数+1
  13. q->count++;
  14. }

        出队:   

  1. void CycOutQueue(SqQueue *q,int *e)
  2. {
  3. //判断非法情况,是否为空
  4. if(q->front == q->rear)
  5. exit(-1);
  6. //取出队头元素,赋值给e
  7. *e=q->data[q->front];
  8. //队头指针++
  9. q->front=(q->front+1)%MaxSize;
  10. //元素个数-1
  11. q->count--;
  12. }

(7)顺序可运行总代码:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. //队列
  4. //队列的顺序存储
  5. #define MaxSize 10
  6. typedef struct
  7. {
  8. int data[MaxSize];
  9. int front;
  10. int rear;
  11. //计数器
  12. int count;
  13. }SqQueue;
  14. //队列初始化
  15. void QueueInit(SqQueue *q)
  16. {
  17. q->front=0;
  18. q->rear=0;
  19. q->count=0;
  20. }
  21. //队空
  22. int QueueEmpty(SqQueue *q)
  23. {
  24. if(q->front == q->rear )
  25. return 1;
  26. }
  27. //队满
  28. int QueueFull(SqQueue *q)
  29. {
  30. if(q->count == MaxSize-1)
  31. return 1;
  32. else
  33. return -1;
  34. }
  35. //入队
  36. void EnQueue(SqQueue *q,int x)
  37. {
  38. //如果队满了
  39. if(QueueFull(q)==1)
  40. exit(-1);
  41. //给队尾处赋值
  42. q->data[q->rear]=x;
  43. //移动队尾标记
  44. q->rear++;
  45. //数据数+1
  46. q->count++;
  47. }
  48. void CycEnQueue(SqQueue *q,int x)
  49. {
  50. //如果队满了
  51. if(q->rear+1 == q->front)
  52. exit(-1);
  53. //给队尾处赋值
  54. q->data[q->rear]=x;
  55. //移动队尾标记
  56. q->rear=(q->rear+1)%MaxSize;
  57. //数据数+1
  58. q->count++;
  59. }
  60. //出队
  61. void OutQueue(SqQueue *q,int *e)
  62. {
  63. //判断非法情况,是否为空
  64. if(q->front == q->rear)
  65. exit(-1);
  66. //取出队头元素,赋值给e
  67. *e=q->data[q->front];
  68. //队头指针++
  69. q->front++;
  70. //元素个数-1
  71. q->count--;
  72. }
  73. void CycOutQueue(SqQueue *q,int *e)
  74. {
  75. //判断非法情况,是否为空
  76. if(q->front == q->rear)
  77. exit(-1);
  78. //取出队头元素,赋值给e
  79. *e=q->data[q->front];
  80. //队头指针++
  81. q->front=(q->front+1)%MaxSize;
  82. //元素个数-1
  83. q->count--;
  84. }
  85. //打印队列
  86. void QueuePrint(SqQueue *q)
  87. {
  88. int i=0;
  89. printf("目前队列中有%d个数据\n",q->count);
  90. //从队头遍历到队尾
  91. for(i=q->front;i<q->rear;i++)
  92. {
  93. printf("%d ",q->data[i]);
  94. }
  95. printf("\n");
  96. }
  97. //队列的链式存储
  98. int main()
  99. {
  100. //创建队列
  101. SqQueue q;
  102. //初始化队列
  103. QueueInit(&q);
  104. //打印队列
  105. QueuePrint(&q);
  106. //入队
  107. EnQueue(&q,0);
  108. EnQueue(&q,1);
  109. EnQueue(&q,2);
  110. EnQueue(&q,3);
  111. QueuePrint(&q);
  112. //出队
  113. int e=0;
  114. OutQueue(&q,&e);
  115. QueuePrint(&q);
  116. printf("出队%d\n",e);
  117. return 0;
  118. }

2.链式存储

        简介:使用链表数据结构实现,每个节点都包含一个元素和指向下一个节点的指针。

        链表队列的优点:可以动态地调整队列长度。

        链表队列的缺点:需要更多的内存空间存储指针信息。另外,由于需要动态申请和释放内存,链表实现的队列在操作上比数组实现的队列稍慢

(1)链式存储定义

        简介:由单链表构成,然后由队头指针和队尾指针,进行操作,因此定义两个结构体,一个结构体是定义队列结点类型的,一个则是封装队头,队尾指针。

  1. /链队结点
  2. typedef struct Linknode
  3. {
  4. int data;
  5. struct Linknode* next;
  6. }LinkNode;
  7. //链队的头指针和尾指针
  8. typedef struct
  9. {
  10. LinkNode* front;
  11. LinkNode* rear;
  12. }LinkQueue;
(2)初始化

这里面初始化,默认带头节点,就是为了是开头操作和其他情况操作一致。

  1. /链队初始化
  2. void InitQueue(LinkQueue* q)
  3. {
  4. //创建头节点
  5. LinkNode* s = (LinkNode*)malloc(sizeof(LinkNode));
  6. if (s == NULL)
  7. exit(-1);
  8. else
  9. {
  10. s->next = NULL;
  11. q->front = q->rear = s;
  12. q->rear->next = NULL;
  13. }
  14. //return q;
  15. }

初始化的时候,头节点定义完后,队头指针和队尾指针都指向它,并且队尾指针的指针域指向空。

(3)进队

进队时,也是先定义一个队列结点,用来加入队。随后用队尾指针进行相关操作。先连接结点,再更新队尾指针。

  1. //入队
  2. void EnQueue(LinkQueue* q, int x)
  3. {
  4. //创建结点
  5. LinkNode* s = (LinkNode*)malloc(sizeof(LinkNode));
  6. if (s == NULL)
  7. exit(-1);
  8. else
  9. {
  10. s->data = x;
  11. s->next = NULL;
  12. //队尾所指向的结点的指针域,指向存储s结点地址
  13. q->rear->next = s;
  14. //更新尾指针
  15. q->rear = s;
  16. }
  17. }
(4)出队

出队时,先判断是否为空队,随后,再进行出队操作,出队时,先定义一个指针,指向需要出队的元素,因为这里由头节点,而队头指针始终指向头节点,因此标记出队元素指针为队头指针的指针域,即头节点的后继节点为实际出队结点。随后头节点的指针域,指向出队结点的后继,并判断,当前出队的元素是否为最后一个结点,即如果q->rear = p,则让队内初始化为空,即队头指针和队尾指针相等。随后释放掉P结点。

  1. //出队
  2. void DeQueue(LinkQueue* q, int* e)
  3. {
  4. //判断非法情况,空队
  5. if (q->front == q->rear)
  6. exit(-1);
  7. //给出队元素赋值
  8. *e = q->front->next->data;
  9. //标记出队元素
  10. LinkNode* p = q->front->next;
  11. //队头指针后移到出队元素后继节点
  12. q->front->next = p->next;
  13. //判断是否仅有一个结点
  14. if (q->rear == p)
  15. {
  16. q->rear = q->front;
  17. }
  18. free(p);
  19. //return q;
  20. }
(5)打印链队

定义一个工作指针,

工作指针,从实际第一个元素结点开始,即头节点的后继节点。

  1. void LinkQueuePrint(LinkQueue* q)
  2. {
  3. LinkNode* pos = q->front->next;//队头元素指向头节点
  4. int i = 0;
  5. while (pos !=NULL)
  6. {
  7. printf("%d->", pos->data);
  8. pos = pos->next;
  9. }
  10. printf("NULL\n");
  11. }
(6)链队可运行总代码
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. //链队结点
  5. typedef struct Linknode
  6. {
  7. int data;
  8. struct Linknode* next;
  9. }LinkNode;
  10. //链队的头指针和尾指针
  11. typedef struct
  12. {
  13. LinkNode* front;
  14. LinkNode* rear;
  15. }LinkQueue;
  16. //链队初始化
  17. void InitQueue(LinkQueue* q)
  18. {
  19. //创建头节点
  20. LinkNode* s = (LinkNode*)malloc(sizeof(LinkNode));
  21. if (s == NULL)
  22. exit(-1);
  23. else
  24. {
  25. s->next = NULL;
  26. q->front = q->rear = s;
  27. q->rear->next = NULL;
  28. }
  29. //return q;
  30. }
  31. //入队
  32. void EnQueue(LinkQueue* q, int x)
  33. {
  34. //创建结点
  35. LinkNode* s = (LinkNode*)malloc(sizeof(LinkNode));
  36. if (s == NULL)
  37. exit(-1);
  38. else
  39. {
  40. s->data = x;
  41. s->next = NULL;
  42. //队尾所指向的结点的指针域,指向存储s结点地址
  43. q->rear->next = s;
  44. //更新尾指针
  45. q->rear = s;
  46. }
  47. //return q;
  48. }
  49. //出队
  50. void DeQueue(LinkQueue* q, int* e)
  51. {
  52. //判断非法情况,空队
  53. if (q->front == q->rear)
  54. exit(-1);
  55. //给出队元素赋值
  56. *e = q->front->next->data;
  57. //标记出队元素
  58. LinkNode* p = q->front->next;
  59. //队头指针后移到出队元素后继节点
  60. q->front->next = p->next;
  61. //判断是否仅有一个结点
  62. if (q->rear == p)
  63. {
  64. q->rear = q->front;
  65. }
  66. free(p);
  67. //return q;
  68. }
  69. void LinkQueuePrint(LinkQueue* q)
  70. {
  71. LinkNode* pos = q->front->next;//队头元素指向头节点
  72. int i = 0;
  73. while (pos !=NULL)
  74. {
  75. printf("%d->", pos->data);
  76. pos = pos->next;
  77. }
  78. printf("NULL\n");
  79. }
  80. int main()
  81. {
  82. LinkQueue q ;
  83. InitQueue(&q);
  84. EnQueue(&q, 0);
  85. EnQueue(&q, 1);
  86. EnQueue(&q, 2);
  87. EnQueue(&q, 3);
  88. LinkQueuePrint(&q);
  89. int e = 0;
  90. DeQueue(&q, &e);
  91. printf("e=%d\n", e);
  92. LinkQueuePrint(&q);
  93. DeQueue(&q, &e);
  94. printf("e=%d\n", e);
  95. LinkQueuePrint(&q);
  96. return 0;
  97. }

3.双端队列。

这一部分了解思想即可,主要用于计算选择和填空。

        简介:

        双端队列(Double-ended Queue)是一种特殊的队列,它允许在队列两端进行插入和删除操作。双端队列可以看作是两个栈首尾相接构成的数据结构。在双端队列中,可以在队列头部和尾部进行元素的插入和删除操作,因此它的操作有:从队头插入元素、从队头删除元素、从队尾插入元素、从队尾删除元素等。

双端队列可以用数组或链表实现。在数组中实现时,需要注意队列头部和尾部指针的位置。当队列长度大于数组长度时,需要进行扩容操作。在链表中实现时,只需要维护头结点和尾结点即可。

双端队列的应用非常广泛,比如操作系统中的进程调度队列、窗口滑动中的数据缓存等场景都可以使用双端队列来实现。        

说白了,就是再原来队列的基础上,多了好几个功能,即两端都可以进行入队和出队操作。

        之后,便引申出了一个题型。输入受限的双端队列,输出受限的双端队列。

(1)输入受限的双端队列

        即一段仅能输出,另一端输入输出都可以,就是仅能输出那一段,不能输入,就是输入受限。

        这里记住一个技巧:若1234为入队序列,那么输入受限的话,有这样公式:..1..2..3..4,即先先出4的话,2就不能紧跟着出。因为2位于1 3中间。这种技巧是可以得到不可能得到的序列

(2)输出受限的双端队列

        即一段仅能输入,另一端不限制,就是仅能输入那一段,不能输出,就是输出受限。

        技巧:若1234入队序列,那么有这样公式:12...3..4,这时4输出了,那么3一定不会12之间,因为12是紧挨着的。这种技巧是可以得到不可能得到的序列

(3)例题:

        有一双端队列,输入序列为1,2,3,4,分别求出一下条件的输出序列:

        1.能由输入受限得到,输出得不到。

        2.能由输出受限得到,输入得不到。

        3.输入输出都得不到,

解:应用题的话,需要一个个证明,有些多,我觉得一般都是选择和填空,因此我们采用技巧取做。

        技巧可以得到输入受限不可能得到的序列,和输出受限不可能得到的序列,随后我们让这两个求差集,即可。

输入受限得不到:

        规律:..1..2..3..4,那么4先出,则2一定不会紧跟着出来,

所以不可能得到的为:4,2,1,3 和 4,2,3,1

输出受限得不到的:
        规律:12...3..4,那么4先出。则3一定不会再12中间,因为12是紧邻着的,

所以不可能得到的为:4,1,3,2 和 4,2,3,1

随后我们根据条件去筛选即可。

(1)输入得到,输出得不到。 我们从输出得不到的里面,去筛选,输入可以得到的,

所以为4,1,3,2,因为4,2,3,1在输入受限中也得不到,所以排除。

(2)输出得到,输入得不到:我们从输入得不到的里面,去筛选,输出可以得到的,所以为:
4,2,1,3,因为4,2,3,1输出也得不到,所以排除。

(3)输入输出都得不到:因此我们,找这俩的交集,所以为:4,2,3,1


至此队列基本理论结束!以后记得常来复习。基本操作要滚瓜烂熟,先明白整体,再去记忆具体。

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

闽ICP备14008679号