当前位置:   article > 正文

【数据结构】栈和队列(链表模拟队列)_链表 队列 和栈

链表 队列 和栈

 


学习本章节必须具备 单链表的前置知识,

建议提前学习:点击链接学习:单链表各种功能函数 细节 详解

本章节是学习用 单链表模拟队列

1. 单链表实现队列 思路如下

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先 进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一 端称为队头


1.1 使用 数组 还是 链表 模拟 队列 结构?

因为需要 模拟队列 先进先出的 特性:队头 只能出,队尾 只能进

若 使用 数组模拟,每次 pop 队头操作,就需要 全部元素向前面移动,时间复杂度为 O(n)

综上,因为需要 考虑位置变化,选择 链表 实现 队列 较优


1.2. 需要使用 什么类型的 链表模拟队列?

单向

带头 / 不带头 都可以 :因为哨兵位主要用于 双向链表 找尾 ,为了方便删除,这里差别不大

不循环

我们下面实现的 是 单向不带头不循环链表

实际上,单向或双向,循环或不循环,带头或不带头 完全取决于 你自己要实现一个功能的需求,不是说 一定要固定使用 哪一个套 ,需要灵活选择使用


1.3. 单向链表 实现队列 的链表节点结构体创建:

  1. typedef int QDataType;
  2. typedef struct QueueNode
  3. {
  4. QDataType value; // 节点数据
  5. struct QueueNode* next; // 指向下一个节点
  6. }QNode;

1.4. 考虑效率,创建 头尾指针结构体

因为 队列需要:队头 push,队尾 pop

涉及到对 链表 的 尾部操作必须意识到:需要先进行 找尾操作,时间复杂度为 O(n)

方案:因为涉及头尾频繁操作:可以 直接 同时定义 头指针 phead 和 尾指针 ptail

技巧:同类型的变量可以封装成一个结构体

因为 phead 和 ptail 是可以不断变化的,每个相关函数都需要同时传递 phead 和 ptail 两个变量

则可以将多个同类型的变量 封装成 一个结构体,方便操作

这样,传递变量时 直接传递一个 结构体的指针就行了

  1. typedef struct Queue
  2. {
  3. QNode* phead;
  4. QNode* ptail;
  5. }Queue;
  1. // 区别:减少了传递变量的数量,利于协助开发
  2. // void QueuePush(QNode* phead, QNode* ptail);
  3. void QueuePush(Queue* pq);
  4. // void QueuePop(QNode* phead, QNode* ptail);
  5. void QueuePop(Queue* pq);


1.5. Push / Pop :入队 和 出队操作

Push 在队尾入队,Pop 在队头出队

  1. void QueuePop(Queue* pq)
  2. {
  3. assert(pq);
  4. // pop 的删除操作 需要分类讨论:链表节点个数为 0、为 1、为 两个以上
  5. // 为 0 :直接判空,退出操作:phead == ptail == NULL
  6. assert(pq->phead); // 头节点为空 就一定代表为空了
  7. // 为 1:phead == ptail 但是 phead != NULL 的情况:即一定指向一个节点
  8. if (pq->phead == pq->ptail && pq->phead != NULL) {
  9. free(pq->phead);
  10. pq->phead = pq->ptail = NULL;
  11. }
  12. else // 为 两个以上:先记录第二个节点,free 掉头节点,更新头节点
  13. {
  14. QNode* tmp = pq->phead->next;
  15. free(pq->phead);
  16. pq->phead = tmp;
  17. }
  18. }

为什么 ” 头节点为空 或 尾节点为空 就一定代表链表为空了 “?


1.6. 观察上面代码:有需要 判断链表节点数量的 需求,为了简化代码与优化过程,可以 直接定义一个 size ,放进结构体中,时刻记录 链表节点数量

  1. // 结构体更改:
  2. typedef struct Queue
  3. {
  4. QNode* phead;
  5. QNode* ptail;
  6. int size;
  7. }Queue;
  8. // 加入 size 后 的 Push 和 Pop 函数
  9. void QueuePop(Queue* pq)
  10. {
  11. assert(pq);
  12. assert(pq->phead);
  13. if (pq->size == 1) {
  14. free(pq->phead);
  15. pq->phead = pq->ptail = NULL;
  16. }
  17. else if (pq->size >= 2)
  18. {
  19. QNode* next = pq->phead->next; // 保留下一个
  20. free(pq->phead);
  21. pq->phead = next;
  22. }
  23. pq->size--; // 注意 pop 代表弹出一个节点,数量 - 1
  24. }
  25. void QueuePush(Queue* pq, QDataType x)
  26. {
  27. assert(pq);
  28. // push 前先创建一个新节点
  29. QNode* newNode = (QNode*)malloc(sizeof(QNode));
  30. if (newNode == NULL) {
  31. perror("malloc fail");
  32. return;
  33. }
  34. newNode->value = x;
  35. newNode->next = NULL;
  36. if (pq->ptail) // 若 ptail != NULL 说明此时链表不为空
  37. {
  38. pq->ptail->next = newNode; // 旧的尾节点和一个新的点 进行链接
  39. pq->ptail = newNode; // 重新更新尾节点
  40. }
  41. else // 若链表为空,则 phead 和 ptail 都要 处理
  42. {
  43. pq->phead = pq->ptail = newNode;
  44. }
  45. pq->size++; // 数量++
  46. }


2. 综上所述,最终代码:

Queue.c

  1. #include"Queue.h"
  2. // Push 入队,Pop 出队
  3. void QueuePop(Queue* pq)
  4. {
  5. assert(pq);
  6. assert(pq->phead);
  7. if (pq->size == 1) {
  8. free(pq->phead);
  9. pq->phead = pq->ptail = NULL;
  10. }
  11. else if (pq->size >= 2)
  12. {
  13. QNode* next = pq->phead->next; // 保留下一个
  14. free(pq->phead);
  15. pq->phead = next;
  16. }
  17. pq->size--; // 注意 pop 代表弹出一个节点,数量 - 1
  18. }
  19. void QueuePush(Queue* pq, QDataType x)
  20. {
  21. assert(pq);
  22. // push 前先创建一个新节点
  23. QNode* newNode = (QNode*)malloc(sizeof(QNode));
  24. if (newNode == NULL) {
  25. perror("malloc fail");
  26. return;
  27. }
  28. newNode->value = x;
  29. newNode->next = NULL;
  30. if (pq->ptail) // 若 ptail != NULL 说明此时链表不为空
  31. {
  32. pq->ptail->next = newNode; // 旧的尾节点和一个新的点 进行链接
  33. pq->ptail = newNode; // 重新更新尾节点
  34. }
  35. else // 若链表为空,则 phead 和 ptail 都要 处理
  36. {
  37. pq->phead = pq->ptail = newNode;
  38. }
  39. pq->size++; // 数量++
  40. }
  41. // 初始化
  42. void QueueInit(Queue* pq)
  43. {
  44. assert(pq);
  45. pq->phead = pq->ptail = NULL;
  46. pq->size = 0;
  47. }
  48. // 销毁链表:就是 单链表的 销毁操作
  49. void QueueDestory(Queue* pq)
  50. {
  51. assert(pq);
  52. QNode* cur = pq->phead;
  53. while (cur) {
  54. QNode* next = cur->next;
  55. free(cur);
  56. cur = next;
  57. }
  58. pq->phead = pq->ptail = NULL; // 最后别忘了头尾指针置为 NULL
  59. pq->size = 0;
  60. }
  61. // Front 返回队头元素
  62. QDataType QueueFront(Queue* pq)
  63. {
  64. assert(pq);
  65. assert(pq->phead); // 若链表为空 自然没有头节点;
  66. return pq->phead->value;
  67. }
  68. // Back 返回队尾元素
  69. QDataType QueueBack(Queue* pq)
  70. {
  71. assert(pq);
  72. assert(pq->ptail); // 若链表为空 自然没有尾节点;
  73. return pq->ptail->value;
  74. }
  75. // Empty 判断是否为空
  76. bool QueueEmpty(Queue* pq)
  77. {
  78. assert(pq);
  79. return pq->size == 0;
  80. }
  81. // Size 返回节点数量
  82. int QueueSize(Queue* pq)
  83. {
  84. assert(pq);
  85. return pq->size;
  86. }

Queue.h

  1. #pragma once
  2. #include<stdio.h>
  3. #include<stdlib.h>
  4. #include<stdbool.h>
  5. #include<assert.h>
  6. typedef int QDataType;
  7. typedef struct QueueNode
  8. {
  9. QDataType value;
  10. struct QueueNode* next;
  11. }QNode;
  12. typedef struct Queue
  13. {
  14. QNode* phead;
  15. QNode* ptail;
  16. int size;
  17. }Queue;
  18. // 初始化
  19. void QueueInit(Queue* pq);
  20. // Push 入队,Pop 出队
  21. void QueuePush(Queue* pq, QDataType x);
  22. void QueuePop(Queue* pq);
  23. // Front 队头元素,Back 队尾元素
  24. QDataType QueueFront(Queue* pq);
  25. QDataType QueueBack(Queue* pq);
  26. // Empty 判断是否为空,Size 返回节点数量
  27. bool QueueEmpty(Queue* pq);
  28. int QueueSize(Queue* pq);
  29. // 销毁链表
  30. void QueueDestory(Queue* pq);

Main.c

  1. #include"Queue.h"
  2. int main()
  3. {
  4. Queue q; // 创建队列结构体
  5. QueueInit(&q); // 初始化:用于初始化链表的头尾节点:phead / ptail
  6. for (int i = 1; i <= 5; ++i) { // 入队列 几个元素: 1 2 3 4 5
  7. QueuePush(&q, i);
  8. }
  9. // 一个个读取队列元素
  10. while (!QueueEmpty(&q))
  11. {
  12. printf("%d ", QueueFront(&q));
  13. QueuePop(&q);
  14. }
  15. QueueDestory(&q);
  16. return 0;
  17. }

3. LeetCode:225.队列实现栈

使用两个队列实现栈

核心思路:

保持一个队列存数据,一个队列为空
push 入数据,入到不为空的队列
pop 出数据,将 非空队列中 前 n-1 个数据 导入 空队列

代码实现

  1. // 以下均是 链式队列的 相关函数,复制粘贴过来罢了
  2. ///
  3. typedef int QDataType;
  4. typedef struct QueueNode
  5. {
  6. QDataType value;
  7. struct QueueNode* next;
  8. }QNode;
  9. typedef struct Queue
  10. {
  11. QNode* phead;
  12. QNode* ptail;
  13. int size;
  14. }Queue;
  15. void QueuePop(Queue* pq)
  16. {
  17. assert(pq);
  18. assert(pq->phead);
  19. if (pq->size == 1) {
  20. free(pq->phead);
  21. pq->phead = pq->ptail = NULL;
  22. }
  23. else if (pq->size >= 2)
  24. {
  25. QNode* next = pq->phead->next; // 保留下一个
  26. free(pq->phead);
  27. pq->phead = next;
  28. }
  29. pq->size--; // 注意 pop 代表弹出一个节点,数量 - 1
  30. }
  31. void QueuePush(Queue* pq, QDataType x)
  32. {
  33. assert(pq);
  34. // push 前先创建一个新节点
  35. QNode* newNode = (QNode*)malloc(sizeof(QNode));
  36. if (newNode == NULL) {
  37. perror("malloc fail");
  38. return;
  39. }
  40. newNode->value = x;
  41. newNode->next = NULL;
  42. if (pq->ptail) // 若 ptail != NULL 说明此时链表不为空
  43. {
  44. pq->ptail->next = newNode; // 旧的尾节点和一个新的点 进行链接
  45. pq->ptail = newNode; // 重新更新尾节点
  46. }
  47. else // 若链表为空,则 phead 和 ptail 都要 处理
  48. {
  49. pq->phead = pq->ptail = newNode;
  50. }
  51. pq->size++; // 数量++
  52. }
  53. // 初始化
  54. void QueueInit(Queue* pq)
  55. {
  56. assert(pq);
  57. pq->phead = pq->ptail = NULL;
  58. pq->size = 0;
  59. }
  60. // 销毁链表:就是 单链表的 销毁操作
  61. void QueueDestory(Queue* pq)
  62. {
  63. assert(pq);
  64. QNode* cur = pq->phead;
  65. while (cur) {
  66. QNode* next = cur->next;
  67. free(cur);
  68. cur = next;
  69. }
  70. pq->phead = pq->ptail = NULL; // 最后别忘了头尾指针置为 NULL
  71. pq->size = 0;
  72. }
  73. // Front 返回队头元素
  74. QDataType QueueFront(Queue* pq)
  75. {
  76. assert(pq);
  77. assert(pq->phead); // 若链表为空 自然没有头节点;
  78. return pq->phead->value;
  79. }
  80. // Back 返回队尾元素
  81. QDataType QueueBack(Queue* pq)
  82. {
  83. assert(pq);
  84. assert(pq->ptail); // 若链表为空 自然没有尾节点;
  85. return pq->ptail->value;
  86. }
  87. // Empty 判断是否为空
  88. bool QueueEmpty(Queue* pq)
  89. {
  90. assert(pq);
  91. return pq->size == 0;
  92. }
  93. // Size 返回节点数量
  94. int QueueSize(Queue* pq)
  95. {
  96. assert(pq);
  97. return pq->size;
  98. }
  99. // 以上均是 链式队列的 相关函数,复制粘贴过来罢了
  100. ///
  101. ///
  102. // 下面是题目主体
  103. typedef struct {
  104. Queue q1, q2; // 创建两个队列
  105. } MyStack;
  106. MyStack* myStackCreate() {
  107. MyStack* pst = (MyStack*)malloc(sizeof(MyStack)); // 创建一个栈
  108. QueueInit(&(pst->q1));
  109. QueueInit(&(pst->q2));
  110. return pst;
  111. }
  112. void myStackPush(MyStack* obj, int x) {
  113. // push 到 不为空的 队列
  114. if(QueueEmpty(&(obj->q1))) {
  115. QueuePush(&(obj->q2), x);
  116. }
  117. else {
  118. QueuePush(&(obj->q1), x);
  119. }
  120. }
  121. int myStackPop(MyStack* obj) {
  122. // 找到非空的 队列,将 size-1 个元素放进 另一个空队列,同时最后一个元素pop掉
  123. // 有两种情况:q1 为空,q2 不为空, q2 为空,q1 不为空
  124. // 可以先假设,后调整
  125. // 先假设 队列1 为空,队列2 不为空,后面判断后调整
  126. Queue* pEmptyQ = &(obj->q1);
  127. Queue* pNonEmptyQ = &(obj->q2);
  128. if(!QueueEmpty(&(obj->q1))){
  129. pEmptyQ = &(obj->q2);
  130. pNonEmptyQ = &(obj->q1);
  131. }
  132. // 将不为空队列 的前 n-1 个元素放进 空队列中
  133. while(QueueSize(pNonEmptyQ) > 1) {
  134. int x = QueueFront(pNonEmptyQ);
  135. QueuePush(pEmptyQ, x);
  136. QueuePop(pNonEmptyQ);
  137. }
  138. int t = QueueFront(pNonEmptyQ);
  139. QueuePop(pNonEmptyQ);
  140. return t;
  141. }
  142. int myStackTop(MyStack* obj) {
  143. if(QueueEmpty(&(obj->q1))) {
  144. return QueueBack(&(obj->q2));
  145. }
  146. else {
  147. return QueueBack(&(obj->q1));
  148. }
  149. }
  150. bool myStackEmpty(MyStack* obj) {
  151. return QueueEmpty(&(obj->q1)) && QueueEmpty(&(obj->q2)); // 两个都为空才是栈空
  152. }
  153. void myStackFree(MyStack* obj) {
  154. if(QueueEmpty(&(obj->q1))) {
  155. QueueDestory(&(obj->q2));
  156. }
  157. else if(QueueEmpty(&(obj->q2))) {
  158. QueueDestory(&(obj->q1));
  159. }
  160. }


4. LeetCode:223.栈实现队列

使用 两个栈 模拟队列

思路

定义一个 pushStack :专门用来接收 push入队列的 数据
定义一个 popStack :专门用来 pop 队列数据
当 popStack 为空时,此时需要 pop 操作,则将 pushStack 的数据全部 放进 popStack ,补充数据(注意是全部);若popStack 不为空,则进行 pop 操作即可
当 需要 push 操作,直接往 pushStack 中放数据即可

演示: push 2 次,pop 1 次,push 3 次, pop 3 次

【若文章有什么错误,欢迎评论区讨论或私信指出】 

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

闽ICP备14008679号