栈和队列修炼指南(基本操作+OJ练习)

本文涉及的产品
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
简介: 栈和队列修炼指南(基本操作+OJ练习)

栈和队列修炼指南

1. 栈

1. 1 概念及结构

  • 栈:是一种特殊的线性表,其只允许在固定的一端进行插入和删除元素的操作。进行数据插入和删除操作的一端称为栈顶,另一端为栈底
  • 栈中的数据元素遵守后进先出原则(LIFO)原则
  • 压栈:栈的插入操作称为进栈/压栈/入栈,其位置在栈顶
  • 出栈:栈的删除操作称为出栈,其位置也在栈顶

1.2 分类(数组栈和链式栈)

数组栈(推荐方式,因为在数组尾插代价更小)

链式栈:相较数组栈无优势,且一般将链表尾作为栈底,链表头作为栈顶(单链表情况下)

1.3 数组栈

1.3.1 结构的定义

typedef int STElemType;
typedef struct Stack
{
  STElemType *data; //动态栈
  int top;
  int capacity;
}ST;

1.3.2 初始化

void StackInit(ST *pt)
{
  pt->data = (SElemType *)malloc(N*sizeof(SElemTyp  e));
    if (!pt->data)
    {
      perror("malloc");
      exit(1);
  }
  pt->capacity = N; //N表示初始的最大容量
  pt->top = 0;  //此时top指向的是栈顶元素的下一个位置,也可以定义为pt->top=-1,这样,top就是指向栈顶元素
}

1.3.3 销毁

void StackDestroy(ST *pt)
{
  free(pt->data);
  pt->top=pt->capacity=0;
}

1.3.4 判断栈是否为空

bool StackEmpty(ST *pt)
{
  return pt->top==0;  //若为真即栈为空,则返回1,否则返回0
}

1.3.5入栈

void StackPush(ST *pt,SElemType x)
{
  if(pt->top==pt->capacity)     //如果容量已满
  {
    pt->capacity *= 2;    //将容量扩为原来的两倍
    ST* temp = realloc(pt->data, pt->capacity*sizeof(SElemType));
    if(!temp)
    {
      perror("malloc");
      exit(1);
    }
        pt->data = temp;
  }
    //入栈
  pt->data[pt->top]=x;
  pt->top++;
}

1.3.6 出栈

void StackPop(ST *pt) 
{
  assert(!stackEmpty(pt));  //栈不能为空
    //出栈
  pt->top--;
}

1.3.7 返回栈顶元素

SElemType StackTop(ST *pt)
{
  assert(!stackEmpty(pt));  //栈不能为空
  return pt->data[pt->top-1]; 
}

1.3.8 返回栈的元素个数

int StackSize(ST *pt)
{
  return pt->top;
}

1.3.9 将栈的元素全部取出

void StackPrint(ST *pt)
{
  assert(!stackEmpty(pt));  //栈不能为空
  while(!StackEmpty(pt))
  {
    printf("%d ",StackTop(pt)); //遵循先入后出原则,从上往下取
    pt->top--;
  }
}

1.4 练习

学习完栈的基本概念和相关操作后,你可以利用栈的特性做下面的OJ题:

有效括号序列👉题目解析

逆波兰表达式求值👉题目解析

删除字符串中的所有相邻重复项👉题目解析

包含min函数的栈👉题目解析


2. 队列

2.1 概念及结构

  • 队列:只允许在一端进行入数据操作,在另一端进行删除数据操作的特殊线性表
  • 遵循先进先出的原则FIFO
  • 入队列:进行插入操作的一段叫做队尾
  • 出队列:进行删除操作的一段叫做队头

2.2. 分类(数组队列和链队列)

数组队列:由于出队列出的是队头元素,因此数组队列出数据的效率低下,不推荐使用

链队列:入和出数据的效率都很高,是队列常用的表示法

2.3 链队列

2.3.1 结构的定义

typedef int QDataType;  //存储的数据类型
typedef struct QueueNode  //链队列的节点
{
  struct QueueNode *next;
  QDataType data;
}QueueNode;
typedef struct Queue  //定义存放指向队头,队尾指针的结构体
{
  QueueNode *head;  //指向队头
  QueueNode *tail;  //指向队尾
}Queue;

2.3.2 初始化

void QueueInit(Queue *pq)
{
  assert(pq);
  pq->head = NULL;
  pq->tail = NULL;
}

2.3.3 销毁

void QueueDestroy(Queue *pq)
{
  QueueNode *cur = pq->head;  //定义临时变量
  while (cur)
  {  
    pq->head = pq->head->next;  //链表下滑
    free(cur);    //释放空间
    cur = pq->head; //更新临时变量
  } 
  pq->tail = NULL;  //空间释放完毕后head已经为空,但tail成为了野指针,所以要置空
}

2.3.4 判断队列是否为空

bool QueueEmpty(Queue *pq) 
{
  assert(pq);
  return pq->head == NULL;
}

2.3.4 入队

void QueuePush(Queue *pq,QDataType x)
{
  assert(pq);
  QueueNode *newnode=(QueueNode *)malloc(sizeof(QueueNode));    //创建新节点
    if (NULL == newNode)
    {
        perror("malloc");
        exit(1);
  }
  newnode->data=x;
  newnode->next=NULL;
  if(QueueEmpty(pq))  //如果队列为空
  {
    pq->head=newnode; //使队头、队尾指针同时指向新节点
    pq->tail=newnode;
  }
  else
  {
    pq->tail->next=newnode; //使队尾指针的指向下一个节点的指针指向新节点
    pq->tail=newnode; //更新队尾指针
  }
}

2.3.5 出队

void QueuePop(Queue *pq) 
{
  assert(pq);
  assert(!QueueEmpty(pq));  //队列不能为空
  QueueNode *cur=pq->head;  //定义临时变量保存队头指针
  pq->head=pq->head->next;  //使队头指针指向下一个节点
  free(cur);    //释放原来的队头
  if(pq->head==NULL)
    pq->tail=NULL;  //如果节点已经全部出队,则要将队尾指针置空,防止形成野指针
}

2.3.6 返回队头/队尾数据域

//返回队头元素
QDataType QueueFront(Queue *pq)  
{
  assert(pq);
  assert(!QueueEmpty(pq));
  return pq->head->data;
}
//返回队尾元素 
QDataType QueueBack(Queue *pq)
{
    assert(pq);
    assert(!QueueEmpty(pq));
    return pq->tail->data;
}

2.3.7 返回队列元素个数

int QueueSize(Queue *pq)
{
  QueueNode *cur=pq->head;
  int size=0;
  while(cur)
  {
    size++;
    cur=cur->next;
  }
  return size;
}
//也可以在队列结构体中增加size变量,每入队一个size就加一

2.4 练习

队列常常被用来对一些复杂数据结构的广度优先遍历,但由于目前还未学习,故不作深入讨论

除了这种最基本的只能从队尾插入数据,从队头删除数据的队列外,其实还有循环队列、双端队列、单调队列等许多复杂但功能强大的队列结构,如果小伙伴们感兴趣,也可以看看:

👉循环队列

👉双端队列 & 单调队列

如果小伙伴们愿意挑战,也可以做一做滑动窗口的最大值👉题目解析


3. 栈和队列的相互表示

这里拿两道OJ题来进行说明:

用两个栈表示队列👉题目解析

用两个队列表示栈👉题目解析

相关文章
|
1月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
142 77
|
4天前
|
DataX
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
1月前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
44 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
1月前
|
C++
【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
38 7
|
3月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
332 9
|
3月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
55 1
|
1月前
|
存储 C语言 C++
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
46 9
|
3月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
99 5
|
3月前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
116 21
|
3月前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。

热门文章

最新文章