【数据结构初阶】第五篇——栈和队列

简介: 【数据结构初阶】第五篇——栈和队列


栈的概念及结构


:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据的插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的元素遵循后进先出的原则。

压栈:栈的插入操作叫做进栈/入栈/压栈,入数据在栈顶。

出栈:栈的删除操作叫做出栈,出数据也在栈顶。

image.png

image.png

栈的实现


栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些,因为数组在尾上插入数据的代价比较小。

image.png

结构如下:

typedef int STDataType;//栈中存储的元素类型(这里用整型举例)
typedef struct Stack
{
  STDataType* a;//栈
  int top;//栈顶
  int capacity;//容量,方便增容
}Stack;

栈的初始化


首先,我们需要用结构体创建一个栈,这个结构体需要包括栈的基本内容(栈,栈顶,栈的容量)。

//初始化栈
void StackInit(Stack* pst)
{
  assert(pst);
  pst->a = (STDataType*)malloc(sizeof(STDataType)* 4);//初始化栈可存储4个元素
  pst->top = 0;//初始时栈中无元素,栈顶为0
  pst->capacity = 4;//容量为4
}

销毁栈


因为栈的内存空间是动态开辟出来的,当我们使用完后必须释放其内存空间,避免内存泄漏

//销毁栈
void StackDestroy(Stack* pst)
{
  assert(pst);
  free(pst->a);//释放栈
  pst->a = NULL;//及时置空
  pst->top = 0;//栈顶置0
  pst->capacity = 0;//容量置0
}

入栈


进行入栈操作前,我们需要检测栈的当前状态,若已满,则需要先对其进行增容,然后才能进行入栈操作。

//入栈
void StackPush(Stack* pst, STDataType x)
{
  assert(pst);
  if (pst->top == pst->capacity)//栈已满,需扩容
  {
    STDataType* tmp = (STDataType*)realloc(pst->a, sizeof(STDataType)*pst->capacity * 2);
    if (tmp == NULL)
    {
      printf("realloc fail\n");
      exit(-1);
    }
    pst->a = tmp;
    pst->capacity *= 2;//栈容量扩大为原来的两倍
  }
  pst->a[pst->top] = x;//栈顶位置存放元素x
  pst->top++;//栈顶上移
}

出栈


出栈操作比较简单,即让栈顶的位置向下移动一位即可。但需检测栈是否为空,若为空,则不能进行出栈操作。

//出栈
void StackPop(Stack* pst)
{
  assert(pst);
  assert(!StackEmpty(pst));//检测栈是否为空
  pst->top--;//栈顶下移
}

获取栈顶元素


获取栈顶元素,即获取栈的最上方的元素。若栈为空,则不能获取。

//获取栈顶元素
STDataType StackTop(Stack* pst)
{
  assert(pst);
  assert(!StackEmpty(pst));//检测栈是否为空
  return pst->a[pst->top - 1];//返回栈顶元素
}

检测栈是否为空


检测栈是否为空,即判断栈顶的位置是否是0即可。若栈顶是0,则栈为空。

//检测栈是否为空
bool StackEmpty(Stack* pst)
{
  assert(pst);
  return pst->top == 0;
}

获取栈中有效元素个数


因为top记录的是栈顶,使用top的值便代表栈中有效元素的个数。

//获取栈中有效元素个数
int StackSize(Stack* pst)
{
  assert(pst);
  return pst->top;//top的值便是栈中有效元素的个数
}

队列


队列的概念和结构


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

image.png

队列的结构,我们选取单链表来实现,秩序进行头删和为插的不足即可。如果选数组,那么每一次删头我们都要挪动一遍数据,这种方式不优,所以我们还是选取用单链表来实现。

定义的结构如下

typedef int QDataType;//队列中存储的元素类型(这里用整型举例)
typedef struct QListNode
{
  struct QListNode* next;//指针域
  QDataType data;//数据域
}QListNode;
typedef struct Queue
{
  QListNode* head;//队头
  QListNode* tail;//队尾
}Queue;

队列的实现


队列的初始化


//初始化队列
void QueueInit(Queue* pq)
{
  assert(pq);
  //起始时队列为空
  pq->head = NULL;
  pq->tail = NULL;
}

销毁队列


队列中的每一个结点所占用的内存空间都是动态开辟的,当我们使用完队列后需要及时释放队列中的每一个结点。

//销毁队列
void QueueDestroy(Queue* pq)
{
  assert(pq);
  QListNode* cur = pq->head;//接收队头
  //遍历链表,逐个释放结点
  while (cur)
  {
    QListNode* next = cur->next;
    free(cur);
    cur = next;
  }
  pq->head = NULL;//队头置空
  pq->tail = NULL;//队尾置空
}

入队


入队列,即申请一个新结点并将其链接到队尾,然后改变队尾的指针指向即可。需要注意的是:若队列中原本无数据,那么我们只需让队头和队尾均指向这个新申请的结点即可。

//队尾入队列
void QueuePush(Queue* pq, QDataType x)
{
  assert(pq);
  QListNode* newnode = (QListNode*)malloc(sizeof(QListNode));//申请新结点
  if (newnode == NULL)
  {
    printf("malloc fail\n");
    exit(-1);
  }
  newnode->data = x;//新结点赋值
  newnode->next = NULL;//新结点指针域置空
  if (pq->head == NULL)//队列中原本无结点
  {
    pq->head = pq->tail = newnode;//队头、队尾直接指向新结点
  }
  else//队列中原本有结点
  {
    pq->tail->next = newnode;//最后一个结点指向新结点
    pq->tail = newnode;//改变队尾指针指向
  }
}

出队


出队列,即释放队头指针指向的结点并改变队头指针的指向即可。若队列中只有一个结点,那么直接将该结点释放,然后将队头和队尾置空即可。

//队头出队列
void QueuePop(Queue* pq)
{
  assert(pq);
  assert(!QueueEmpty(pq));//检测队列是否为空
  if (pq->head->next == NULL)//队列中只有一个结点
  {
    free(pq->head);
    pq->head = NULL;
    pq->tail = NULL;
  }
  else//队列中有多个结点
  {
    QListNode* next = pq->head->next;
    free(pq->head);
    pq->head = next;//改变队头指针指向
  }
}

获取对头元素


获取队列头部元素,即返回队头指针指向的数据即可。

//获取队列头部元素
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;//返回队尾指针指向的数据
}

判断队列是否为空


检测队列是否为空,即判断队头指针指向的内容是否为空。

//检测队列是否为空
bool QueueEmpty(Queue* pq)
{
  assert(pq);
  return pq->head == NULL;
}

获取队列中元素个数


队列中有效元素个数,即队列中的结点个数。我们只需遍历队列,统计队列中的结点数并返回即可。

//获取队列中有效元素个数
int QueueSize(Queue* pq)
{
  assert(pq);
  QListNode* cur = pq->head;//接收队头
  int count = 0;//记录结点个数
  while (cur)//遍历队列
  {
    count++;
    cur = cur->next;
  }
  return count;//返回队列中的结点数
}
相关文章
|
1月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
159 9
|
1月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
26 1
|
17天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
38 5
|
1月前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
|
1月前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。
|
1月前
|
存储
系统调用处理程序在内核栈中保存了哪些上下文信息?
【10月更文挑战第29天】系统调用处理程序在内核栈中保存的这些上下文信息对于保证系统调用的正确执行和用户程序的正常恢复至关重要。通过准确地保存和恢复这些信息,操作系统能够实现用户模式和内核模式之间的无缝切换,为用户程序提供稳定、可靠的系统服务。
51 4
|
1月前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第3章之IKUN和I原达人之数据结构与算法系列学习栈与队列精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
25天前
|
算法
数据结构之购物车系统(链表和栈)
本文介绍了基于链表和栈的购物车系统的设计与实现。该系统通过命令行界面提供商品管理、购物车查看、结算等功能,支持用户便捷地管理购物清单。核心代码定义了商品、购物车商品节点和购物车的数据结构,并实现了添加、删除商品、查看购物车内容及结算等操作。算法分析显示,系统在处理小规模购物车时表现良好,但在大规模购物车操作下可能存在性能瓶颈。
42 0
|
2月前
数据结构(栈与列队)
数据结构(栈与列队)
22 1
|
2月前
|
算法 程序员 索引
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
栈的基本概念、应用场景以及如何使用数组和单链表模拟栈,并展示了如何利用栈和中缀表达式实现一个综合计算器。
43 1
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器

热门文章

最新文章