深圳幻海软件技术有限公司 欢迎您!

数据结构--》深入了解栈和队列,让算法更加高效

2023-06-26

        本文将带你深入了解数据结构栈和队列,这两种基础的线性数据结构在算法中的重要性不言而喻。我们将会详细介绍栈和队列的概念、分类、实现以及应用场景,在理解栈和队列的基础上,还将探讨如何通过栈和队列来高效地解决算

        本文将带你深入了解数据结构栈和队列,这两种基础的线性数据结构在算法中的重要性不言而喻。我们将会详细介绍栈和队列的概念、分类、实现以及应用场景,在理解栈和队列的基础上,还将探讨如何通过栈和队列来高效地解决算法问题。

        无论你是初学者还是进阶者,本文将为你提供简单易懂、实用可行的知识点,帮助你更好地掌握栈和队列,进而提升算法解题的能力,开启数据结构与算法的奇妙之旅。

目录

栈的基本概念

栈的顺序存储与链式存储实现

栈在表达式中的应用

队列的基本概念

队列的顺序和链式实现


栈的基本概念

        栈(Stack)是一种线性数据结构,它的特点是只能在末端(栈顶)进行插入和删除操作,并且访问元素时也是从栈顶开始。栈的行为类似于弹簧被压缩的过程,当一个元素被插入栈中时,它就被压入栈底;而当一个元素被删除时,它就从栈顶弹出。

        由于栈的后进先出(Last-In-First-Out,LIFO)的特性,因此它常用于需要“回溯”的算法实现,例如递归、深度优先搜索等。在计算机科学中,栈也被广泛应用于表达式求值、函数调用、内存管理等方面。

栈可以通过数组或链表来实现。通过数组实现的栈称为顺序栈,而通过链表实现的栈称为链式栈。无论通过何种方式实现,栈的基本操作包括入栈(push)、出栈(pop)、查看栈顶元素(top)以及判断栈是否为空(empty)。

栈的基本操作如下

InitStack(&S):初始化栈。构造一个空栈S,分配内存空间。

DestroyStack(&L):销毁栈。销毁并释放栈S所占用的内存空间。

Push(&S,x):进栈,若栈S未满,则将x加入使之成为栈顶。

Pop(&S,&x):出栈,若栈S非空,则弹出栈顶元素,并用x返回。

GetTop(S,&x):读栈顶元素。若栈S非空,则用x返回栈顶元素

其他常用操作

StackEmpty(S):判断一个栈S是否为空。若S为空,则返回true,否则返回false。

栈的常考题型为:

回顾重点,其主要内容整理成如下内容: 

栈的顺序存储与链式存储实现

栈可以通过两种方式来实现:顺序存储链式存储

        顺序存储的栈,即顺序栈,通常使用数组来实现。在顺序栈中,栈底在数组的低地址处,栈顶在数组的高地址处。栈顶指针(top)是一个整型变量,它指向当前栈顶元素所在的位置。当栈为空时,top的值为-1。入栈操作就是将元素插入到top位置的后面,然后将top指针向上移动一个位置;出栈操作就是将top指针向下移动一个位置,然后将该位置的元素删除。

  1. const int MAX_SIZE = 100; // 定义栈的最大容量
  2. int stack[MAX_SIZE]; // 定义栈数组
  3. int top = -1; // 初始化栈顶指针为-1,表示栈为空
  4. // 入栈操作
  5. void push(int x) {
  6. if (top == MAX_SIZE - 1) { // 栈满,无法继续插入元素
  7. cout << "Error: Stack overflow!" << endl;
  8. return;
  9. }
  10. top++; // 栈顶指针上移
  11. stack[top] = x; // 将元素x插入栈顶位置
  12. }
  13. // 出栈操作
  14. void pop() {
  15. if (top == -1) { // 栈空,无法进行出栈操作
  16. cout << "Error: Stack underflow!" << endl;
  17. return;
  18. }
  19. top--; // 栈顶指针下移
  20. }
  21. // 获取栈顶元素
  22. int getTop() {
  23. if (top == -1) { // 栈为空,栈顶指针无效
  24. cout << "Error: Stack is empty!" << endl;
  25. return -1;
  26. }
  27. return stack[top]; // 返回栈顶元素
  28. }
  29. // 判断栈是否为空
  30. bool isEmpty() {
  31. return top == -1; // 栈顶指针为-1时,栈为空
  32. }

栈的顺序存储还有一种共享栈的方式。共享栈是一种特殊的顺序栈,它可以同时存储两个栈。具体来说,共享栈有两个栈顶指针,分别指向两个栈的栈顶元素。它们从两端向中间生长,当它们相遇时就表示栈满了。共享栈常用于两个栈需要共用一段连续的存储空间的情况,例如某些操作系统的内核栈和用户栈都共用一个物理内存区域。下面是使用 C++ 实现共享栈的完整代码示例:

  1. #include <iostream>
  2. using namespace std;
  3. const int MAX_SIZE = 100; // 定义栈的最大容量
  4. int stack[MAX_SIZE]; // 定义存储栈元素的数组
  5. int top1 = -1; // 定义第一个栈的栈顶指针,初始值为-1
  6. int top2 = MAX_SIZE; // 定义第二个栈的栈顶指针,初始值为MAX_SIZE
  7. // 向第一个栈中插入元素
  8. void pushStack1(int x) {
  9. if (top1 + 1 == top2) { // 共享栈已满
  10. cout << "Error: Stack overflow!" << endl;
  11. return;
  12. }
  13. top1++; // 第一个栈的栈顶指针上移
  14. stack[top1] = x; // 在第一个栈中插入元素
  15. }
  16. // 向第二个栈中插入元素
  17. void pushStack2(int x) {
  18. if (top1 + 1 == top2) { // 共享栈已满
  19. cout << "Error: Stack overflow!" << endl;
  20. return;
  21. }
  22. top2--; // 第二个栈的栈顶指针下移
  23. stack[top2] = x; // 在第二个栈中插入元素
  24. }
  25. // 从第一个栈中删除元素
  26. void popStack1() {
  27. if (top1 == -1) { // 第一个栈为空
  28. cout << "Error: Stack underflow!" << endl;
  29. return;
  30. }
  31. top1--; // 第一个栈的栈顶指针下移
  32. }
  33. // 从第二个栈中删除元素
  34. void popStack2() {
  35. if (top2 == MAX_SIZE) { // 第二个栈为空
  36. cout << "Error: Stack underflow!" << endl;
  37. return;
  38. }
  39. top2++; // 第二个栈的栈顶指针上移
  40. }
  41. // 获取第一个栈的栈顶元素
  42. int getTop1() {
  43. if (top1 == -1) { // 第一个栈为空,栈顶指针无效
  44. cout << "Error: Stack is empty!" << endl;
  45. return -1;
  46. }
  47. return stack[top1]; // 返回第一个栈的栈顶元素
  48. }
  49. // 获取第二个栈的栈顶元素
  50. int getTop2() {
  51. if (top2 == MAX_SIZE) { // 第二个栈为空,栈顶指针无效
  52. cout << "Error: Stack is empty!" << endl;
  53. return -1;
  54. }
  55. return stack[top2]; // 返回第二个栈的栈顶元素
  56. }
  57. // 判断第一个栈是否为空
  58. bool isEmpty1() {
  59. return top1 == -1; // 第一个栈的栈顶指针为-1时,栈为空
  60. }
  61. // 判断第二个栈是否为空
  62. bool isEmpty2() {
  63. return top2 == MAX_SIZE; // 第二个栈的栈顶指针为MAX_SIZE时,栈为空
  64. }
  65. int main() {
  66. pushStack1(1);
  67. pushStack1(2);
  68. pushStack2(3);
  69. pushStack2(4);
  70. cout << "Top of Stack 1: " << getTop1() << endl;
  71. cout << "Top of Stack 2: " << getTop2() << endl;
  72. popStack1();
  73. popStack2();
  74. cout << "Top of Stack 1: " << getTop1() << endl;
  75. cout << "Top of Stack 2: " << getTop2() << endl;
  76. return 0;
  77. }

在主函数中,我们演示了如何使用 pushStack1()、pushStack2()、popStack1()、popStack2()、getTop1() 和 getTop2() 等操作来对共享栈进行插入、删除和获取元素等操作,并输出了两个栈的栈顶元素。

回顾重点,其主要内容整理成如下内容:  

栈的链式存储实现方式是使用链表来存储栈中的元素。栈顶指针 Top 指向链表的头节点,每插入一个元素就在链表的头部插入一个节点,每删除一个元素就删除链表头部的节点,这样就实现了栈的先进后出的特性。 以下是使用 C++ 语言实现的栈链式存储的完整代码:

  1. #include <iostream>
  2. using namespace std;
  3. // 定义链表的节点结构体
  4. struct Node {
  5. int data; // 节点中存储的数据
  6. Node* next; // 指向下一个节点的指针
  7. };
  8. // 定义链表的头指针和栈顶指针
  9. Node* head = NULL;
  10. Node* top = NULL;
  11. // 向栈中插入元素
  12. void push(int x) {
  13. Node* newNode = new Node; // 创建新节点
  14. newNode->data = x; // 将元素值赋给新节点的 data 域
  15. newNode->next = top; // 新节点的 next 指向当前栈顶节点
  16. top = newNode; // 更新栈顶指针
  17. }
  18. // 从栈中删除元素
  19. void pop() {
  20. if (top == NULL) { // 栈为空,无法删除
  21. cout << "Error: Stack is empty!" << endl;
  22. return;
  23. }
  24. Node* temp = top; // 暂存当前栈顶节点的地址
  25. top = top->next; // 更新栈顶指针
  26. delete temp; // 释放暂存节点内存空间
  27. }
  28. // 获取栈顶元素
  29. int getTop() {
  30. if (top == NULL) { // 栈为空,栈顶指针无效
  31. cout << "Error: Stack is empty!" << endl;
  32. return -1;
  33. }
  34. return top->data; // 返回栈顶节点的 data 域
  35. }
  36. // 判断栈是否为空
  37. bool isEmpty() {
  38. return top == NULL; // 栈顶指针为空时,栈为空
  39. }
  40. int main() {
  41. push(1);
  42. push(2);
  43. push(3);
  44. cout << "Top of Stack: " << getTop() << endl;
  45. pop();
  46. pop();
  47. cout << "Top of Stack: " << getTop() << endl;
  48. return 0;
  49. }

在主函数中,我们演示了如何使用 push()、pop() 和 getTop() 等操作来对栈进行插入、删除和获取元素等操作,并输出了栈顶元素。 

栈在表达式中的应用

栈在表达式中的应用非常广泛,可以用来进行中缀表达式的转换计算后缀表达式等操作。中缀表达式是指形如 a+b 或 (a+b)*c-d/(e+f) 这样的表达式,其中运算符位于两个操作数的中间。但是在计算机中,通常采用后缀表达式(也叫逆波兰表达式)来表示算术表达式,即把运算符放在操作数后面,例如 a b + c * d /。

队列的基本概念

        队列是一种线性数据结构,具有先进先出(FIFO)的特点。与栈不同,队列只允许在队尾插入元素,在队头删除元素。队列通常用于需要按照时间顺序处理事物的场合,例如多任务并发处理或者打印机缓存任务等,它们按照顺序依次加入队列,并按照先进先出的顺序进行处理。

队列的基本操作如下:

InitQueue(&Q):初始化队列,构造一个空队列Q。

DestroyQueue(&Q):销毁队列。销毁并释放队列Q所占用的内存空间。

EnQueue(&Q,x):入队,若队列Q未满,将x加入,使之成为新的队尾。

DeQueue(&Q,&x):出队,若队列Q非空,删除对头元素,并用x返回。

GetHead(Q,&x):读队头元素,若队列Q非空,则将队头元素赋值给x。

回顾重点,其主要内容整理成如下内容:  

队列的顺序和链式实现

队列可以用数组或链表等数据结构实现,分别称为顺序队列链式队列

顺序队列是基于数组的实现,使用一个数组来存储队列中的元素,使用头尾指针分别指向队列的头部和尾部。新元素插入到队列尾部,旧元素从队列头部删除,每次插入或删除元素后需要更新头尾指针。缺点是当队列元素个数达到数组容量时,需要进行数据搬移,时间复杂度为 O(n)。以下是一个 C++ 语言实现的顺序队列的简单示例代码:

  1. #include <iostream>
  2. using namespace std;
  3. const int MAXSIZE = 100; // 队列的最大容量
  4. int queue[MAXSIZE]; // 用数组实现队列
  5. int head = 0, tail = 0; // 头尾指针
  6. // 向队列尾部插入元素
  7. void enqueue(int x) {
  8. if (tail == MAXSIZE) { // 队列已满
  9. cout << "Error: Queue is full!" << endl;
  10. return;
  11. }
  12. queue[tail++] = x; // 将新元素添加到队列尾部,同时更新尾指针
  13. }
  14. // 从队列头部删除元素
  15. void dequeue() {
  16. if (head == tail) { // 队列为空
  17. cout << "Error: Queue is empty!" << endl;
  18. return;
  19. }
  20. head++; // 更新头指针,删除队首元素
  21. }
  22. // 获取队头元素
  23. int getFront() {
  24. if (head == tail) { // 队列为空
  25. cout << "Error: Queue is empty!" << endl;
  26. return -1;
  27. }
  28. return queue[head]; // 返回队首元素
  29. }
  30. // 判断队列是否为空
  31. bool isEmpty() {
  32. return head == tail; // 头尾指针相等时,队列为空
  33. }
  34. int main() {
  35. enqueue(1);
  36. enqueue(2);
  37. enqueue(3);
  38. cout << "Front of Queue: " << getFront() << endl;
  39. dequeue();
  40. dequeue();
  41. cout << "Front of Queue: " << getFront() << endl;
  42. return 0;
  43. }

需要注意的是,在顺序队列中,每次删除元素时并没有真正将元素从数组中移除,而是单纯地将头指针向后移动。因此,当头指针后面有很多无用元素时,需要调整数组内部的元素位置,以节省内存空间。

回顾重点,其主要内容整理成如下内容:  

链式队列是基于链表的实现,使用一个链表来存储队列中的元素,使用头指针指向队首节点,尾指针指向队尾节点。新元素插入到链表尾部,旧元素从链表头部删除,每次插入或删除元素只需操作指针即可,无需进行数据搬移。链式队列相对于顺序队列的优点是没有容量限制,但由于链表需要额外的指针存储,空间开销较大一些。 以下是一个 C++ 语言实现的顺序队列的简单示例代码:

  1. #include <iostream>
  2. using namespace std;
  3. struct Node {
  4. int data;
  5. Node* next;
  6. };
  7. Node* head = NULL; // 头指针指向队列头部
  8. Node* tail = NULL; // 尾指针指向队列尾部
  9. // 向队列尾部插入元素
  10. void enqueue(int x) {
  11. Node* newNode = new Node;
  12. newNode->data = x;
  13. newNode->next = NULL;
  14. if (tail == NULL) { // 队列为空
  15. head = tail = newNode;
  16. return;
  17. }
  18. tail->next = newNode; // 将新节点添加到队列尾部
  19. tail = newNode; // 更新尾指针
  20. }
  21. // 从队列头部删除元素
  22. void dequeue() {
  23. if (head == NULL) { // 队列为空
  24. cout << "Error: Queue is empty!" << endl;
  25. return;
  26. }
  27. Node* temp = head;
  28. head = head->next; // 更新头指针
  29. if (head == NULL) { // 如果队列中只有一个元素,删除后需要更新尾指针
  30. tail = NULL;
  31. }
  32. delete temp;
  33. }
  34. // 获取队头元素
  35. int getFront() {
  36. if (head == NULL) { // 队列为空
  37. cout << "Error: Queue is empty!" << endl;
  38. return -1;
  39. }
  40. return head->data;
  41. }
  42. // 判断队列是否为空
  43. bool isEmpty() {
  44. return head == NULL; // 头指针为空时,队列为空
  45. }
  46. int main() {
  47. enqueue(1);
  48. enqueue(2);
  49. enqueue(3);
  50. cout << "Front of Queue: " << getFront() << endl;
  51. dequeue();
  52. dequeue();
  53. cout << "Front of Queue: " << getFront() << endl;
  54. return 0;
  55. }

需要注意的是,在链式队列中,每次删除元素时需要注意特殊情况,比如队列中只有一个元素的情况。还需要注意内存泄漏问题,每次删除元素后需要释放对应节点的内存空间。 

回顾重点,其主要内容整理成如下内容:  

双端队列的介绍:双端队列(deque)是一种可以在队列两端进行插入和删除操作的数据结构。它可以被看作是两个栈拼接而成,因此常常被称为“双端栈”。

回顾重点,其主要内容整理成如下内容:  

文章知识点与官方知识档案匹配,可进一步学习相关知识
算法技能树首页概览48445 人正在系统学习中
学习分享/商务合作/技术交流
微信名片