[数据结构]—栈和队列

简介: [数据结构]—栈和队列

💓1.栈



💞1.栈的概念及结构


栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端

称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。

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

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

image.png

image.png

💞2.栈的实现


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

image.png

image.png

💞 3.代码实现

💘1.总体实现

1.栈的初始化

void STInit(ST* pst);

2.释放了动态数组中的空间

void STDestroy(ST* pst);

3.入栈操作

void STPush(ST* pst, STDataType x);

4. 栈顶元素删除

void STPop(ST* pst);

5.获取栈顶元素

STDataType STTop(ST* pst);

6.判断栈(ST)是否为空

bool STEmpty(ST* pst);

7.获取栈大小

int STSize(ST* pst);

💘2.详细解析

💔1.栈的初始化

void STInit(ST* pst)
{
  assert(pst);
  pst->a = NULL;
  pst->capacity = 0;
  // 表示top指向栈顶元素的下一个位置
  pst->top = 0;
  // 表示top指向栈顶元素
  //pst->top = -1;
}

这是一个栈的初始化函数,函数定义如下:

void STInit(ST* pst);

其中,ST是一个结构体类型,包含栈元素的数组指针a,栈的容量capacity,以及指向栈顶元素的下一个位置的指针top。

函数内部的实现如下:


assert(pst);  // 检查指针参数是否为NULL
pst->a = NULL;  // 初始化栈元素数组指针为NULL
pst->capacity = 0;  // 初始化栈的容量为0
// 表示top指向栈顶元素的下一个位置
pst->top = 0;
// 表示top指向栈顶元素
//pst->top = -1;


函数内部的注释提供了两种实现方式,表示top指针的指向方式:

pst->top = 0; 表示top指向栈顶元素的下一个位置。例如,初始状态下栈被认为是空的,因此top指向数组的第一个位置(即下标为0的位置)。

pst->top = -1; 表示top指向栈顶元素。例如,初始状态下栈被认为是空的,因此top指向数组最后一个位置(即下标为capacity-1的位置)。在这种情况下,当向栈中压入第一个元素时,需要先将top指向0,表示栈中有一个元素,而不是-1。

💔2.释放了动态数组中的空间

void STDestroy(ST* pst)
{
  assert(pst);
  free(pst->a);
  pst->a = NULL;
  pst->top = pst->capacity = 0;
}

该函数释放了动态数组中的空间,并将栈中保存的指针置为 NULL,防止出现悬挂指针的情况。

具体分析:

assert(pst);

该语句使用 assert 宏函数判断传入的指针 pst 是否为空,如果为空将会出现错误。

free(pst->a);

该语句使用 free 函数释放了动态数组 a 所占用的空间。

pst->a = NULL;

将栈中保存的数组指针 a 置为 NULL,防止出现悬挂指针的情况。

pst->top = pst->capacity = 0;

将栈中保存的元素数量 top 和数组容量 capacity 置为 0,表示栈已经被清空了。

💔3.入栈操作

该函数用于将元素x压入栈中。

函数中的assert(pst)用于确保输入的栈指针pst不为空。

当栈已满时,需要重新分配更大的内存空间以存储更多的元素。在这里,使用动态内存分配函数realloc()来重新分配空间。如果分配失败,则会输出错误信息并返回。

当空间分配成功后,元素x将被添加到栈的顶部,pst->top会自增,表示该栈顶指针已向上移动一位。

最后,该函数会在操作完成后返回。


void STPush(ST* pst, STDataType x)
{
  assert(pst);
  if (pst->top == pst->capacity)
  {
  int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
  STDataType* tmp = (STDataType*)realloc(pst->a, sizeof(STDataType) * newcapacity);
  if (tmp == NULL)
  {
    perror("realloc fail");
    return;
  }
  pst->a = tmp;
  pst->capacity = newcapacity;
  }
  pst->a[pst->top] = x;
  pst->top++;
}

💔4. 栈顶元素删除

首先使用assert宏函数对传入的指针pst进行断言,确保其不为空。然后再次使用assert宏函数,判断栈顶指针top是否大于0,确保栈中有元素可以弹出。最后将top指针减1,实现弹出栈顶元素的操作。

void STPop(ST* pst)
{
  assert(pst);
  // 不为空
  assert(pst->top > 0);
  pst->top--;
}

💔 5.获取栈顶元素

首先使用断言(assert)判断指针是否为空,如果为空则程序会崩溃。

接着判断栈顶是否大于0,如果不大于0则说明栈中无元素,也会导致程序崩溃。

最后返回栈顶元素,由于是取栈顶元素,所以要使用栈顶指针(top)减1来访问栈顶元素。

STDataType STTop(ST* pst)
{
  assert(pst);
  // 不为空
  assert(pst->top > 0);
  return pst->a[pst->top - 1];
}

💔6.判断栈(ST)是否为空

函数中的参数 pst 是一个指向栈的指针,使用 assert 宏对其进行断言,确保其不为空。

这个函数主要通过判断栈顶指针(top)是否为 0 来确定栈是否为空。如果 top 等于 0,那么栈中没有任何元素,函数返回 true,表示栈为空;否则返回 false,表示栈中还有元素。

STDataType STTop(ST* pst)
{
  assert(pst);
  // 不为空
  assert(pst->top > 0);
  return pst->a[pst->top - 1];
}

💔7.获取栈大小

栈的大小是指当前栈中元素的数量。

参数pst是一个指向栈的指针,函数内部调用了assert(pst)来判断指针是否为空,如果为空则直接终止程序的执行。

函数返回值为栈的大小,即栈顶指针top的值。在使用该函数之前,需要确保栈已经被初始化。

int STSize(ST* pst)
{
  assert(pst);
  return pst->top;
}

💘3.整体代码

💔1.Stack.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int STDataType;
typedef struct Stack
{
  STDataType* a;
  int top;  // 标识栈顶位置的
  int capacity;
}ST;
void STInit(ST* pst);
void STDestroy(ST* pst);
// 栈顶插入删除
void STPush(ST* pst, STDataType x);
void STPop(ST* pst);
STDataType STTop(ST* pst);
bool STEmpty(ST* pst);
int STSize(ST* pst);

💔2.Stach.c

#include"Stack.h"
void STInit(ST* pst)
{
  assert(pst);
  pst->a = NULL;
  pst->capacity = 0;
  // 表示top指向栈顶元素的下一个位置
  pst->top = 0;
  // 表示top指向栈顶元素
  //pst->top = -1;
}
void STDestroy(ST* pst)
{
  assert(pst);
  free(pst->a);
  pst->a = NULL;
  pst->top = pst->capacity = 0;
}
// 栈顶插入删除
void STPush(ST* pst, STDataType x)
{
  assert(pst);
  if (pst->top == pst->capacity)
  {
  int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
  STDataType* tmp = (STDataType*)realloc(pst->a, sizeof(STDataType) * newcapacity);
  if (tmp == NULL)
  {
    perror("realloc fail");
    return;
  }
  pst->a = tmp;
  pst->capacity = newcapacity;
  }
  pst->a[pst->top] = x;
  pst->top++;
}
void STPop(ST* pst)
{
  assert(pst);
  // 不为空
  assert(pst->top > 0);
  pst->top--;
}
STDataType STTop(ST* pst)
{
  assert(pst);
  // 不为空
  assert(pst->top > 0);
  return pst->a[pst->top - 1];
}
bool STEmpty(ST* pst)
{
  assert(pst);
  /*if (pst->top == 0)
  {
  return true;
  }
  else
  {
  return false;
  }*/
  return pst->top == 0;
}
int STSize(ST* pst)
{
  assert(pst);
  return pst->top;
}

💔3.test.c

#include"Stack.h"
int main()
{
  ST s;
  STInit(&s);
  STPush(&s, 1);
  STPush(&s, 2);
  STPush(&s, 3);
  printf("%d ", STTop(&s));
  STPop(&s);
  printf("%d ", STTop(&s));
  STPop(&s);
  STPush(&s, 4);
  STPush(&s, 5);
  //    一     对     多
  // 入栈顺序  --  出栈顺序
  while (!STEmpty(&s))
  {
  printf("%d ", STTop(&s));
  STPop(&s);
  }
  printf("\n");
  return 0;
}

💓2.队列



💞1.队列的概念及结构


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

FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头

image.png

💞2.队列的实现


队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。

image.png

💞3.代码实现

💘1.总体实现

void QueueInit(Queue* pq);

void QueueDestroy(Queue* pq);

void QueuePush(Queue* pq, QDataType x);

void QueuePop(Queue* pq);

QDataType QueueFront(Queue* pq);

QDataType QueueBack(Queue* pq);

bool QueueEmpty(Queue* pq);

int QueueSize(Queue* pq);

💘2. 详细解析

💔1.初始化队列

参数是一个指向Queue结构体的指针。

首先利用assert()函数检查pq指针是否为空,若为空则程序会终止运行,避免出现不可预知的错误。

然后,将队列的头指针phead和尾指针ptail都置为空,即队列初始时是空的。队列的大小size也被初始化为0,表示队列中没有元素。

整个函数的逻辑比较简单,主要目的是将队列的各个成员变量初始化为合适的初始值,以便后续实现队列的相关操作。

void QueueInit(Queue* pq)
{
  assert(pq);
  pq->phead = pq->ptail = NULL;
  pq->size = 0;
}

💔2.销毁队列

参数是一个指向队列的指针。

首先,使用 assert 函数检查指针 pq 是否为空,如果为空则程序直接终止。

然后,定义一个指针 cur 指向队列头部元素。使用 while 循环,遍历整个队列:将 cur 的下一个元素指向 next,再将 cur 释放掉。cur 等于 next,继续遍历。 最后将队列的头部指针 pq->phead 和尾部指针 pq->ptail 都指向 NULL,队列大小 pq->size 置为 0。

这个函数的作用是遍历整个队列,逐个释放节点所占用的空间,防止内存泄漏。


void QueueDestroy(Queue* pq)
{
  assert(pq);
  QNode* cur = pq->phead;
  while (cur)
  {
  QNode* next = cur->next;
  free(cur);
  cur = next;
  }
  pq->phead = pq->ptail = NULL;
  pq->size = 0;
}

💔3.往队列中插入一个元素

函数参数说明:

- pq:指向队列的指针,需要保证指针非空。

- x:插入队列的元素值。

函数实现:

1. 判断指向队列的指针是否为空,如果为空,则直接返回。

2. 申请一个新节点,并判断申请是否成功。如果申请失败,则打印错误信息并返回。

3. 设置新节点的值为要插入的元素值x,将新节点的next指针置为NULL。

4. 如果队列为空,则将队列的头指针和尾指针都指向新节点。

5. 如果队列非空,则将尾节点的next指针指向新节点,然后将尾节点指针指向新节点。

6. 更新队列的元素数量。

7. 函数结束。


void QueuePush(Queue* pq, QDataType x)
{
  assert(pq);
  QNode* newnode = (QNode*)malloc(sizeof(QNode));
  if (newnode == NULL)
  {
  perror("malloc fail");
  return;
  }
  newnode->val = x;
  newnode->next = NULL;
  if (pq->ptail == NULL)
  {
  pq->ptail = pq->phead = newnode;
  }
  else
  {
  pq->ptail->next = newnode;
  pq->ptail = newnode;
  }
  pq->size++;
}

💔4.队列的出队

第一行代码使用断言(assert)来确保队列指针pq的非空性,若为空则会停止程序运行。

第二行代码使用断言(assert)来确保队列头指针(pq->phead)的非空性,若为空则会停止程序运行。

第三行代码定义一个结构体指针del,用来指向将要被删除的节点。

第四行代码将队列头指针(pq->phead)指向下一个节点,即将队头出队,并将该节点的内存释放。

第五至八行代码判断队列是否为空队列,如果是则将队尾指针(pq->ptail)也置为空指针,否则不需要做任何操作。

最后一行代码将队列的大小(size)减少1。


void QueuePop(Queue* pq)
{
  assert(pq);
  // 
  assert(pq->phead);
  QNode* del = pq->phead;
  pq->phead = pq->phead->next;
  free(del);
  del = NULL;
  if (pq->phead == NULL)
  pq->ptail = NULL;
  pq->size--;
}

💔5.获取队列头部元素

输入参数为一个指向队列的指针pq,返回值为队列头部元素的值。其中使用了assert宏来进行断言,确保pq和pq->phead都不为NULL,如果其中任意一个为NULL则会触发断言失败,程序会崩溃。最后返回pq->phead的值。


QDataType QueueFront(Queue* pq)
{
  assert(pq);
  // 
  assert(pq->phead);
  return pq->phead->val;
}

💔6.获取队列最后一个元素

函数签名说明:

QDataType是一个数据类型,用于表示队列中元素的类型;

Queue是一个结构体类型,表示队列;

Queue* pq表示指向队列的指针。

函数实现:

首先,使用assert()宏函数对传入的参数进行断言处理,确保指针pq和队列的尾指针ptail都不为空。

然后,返回队列尾部节点(即最后一个元素)的值,即pq->ptail->val。由于队列的尾指针指向的就是队列的尾部节点,所以可以直接通过ptail获取队列尾部节点的值。

整个函数逻辑简单,实现了获取队列最后一个元素的功能。


QDataType QueueBack(Queue* pq)
{
  assert(pq);
  // 
  assert(pq->ptail);
  return pq->ptail->val;
}

💔7.判断队列是否为空

参数pq是一个指向Queue类型的指针,assert(pq)用于判断pq是否为空。函数返回队列的头节点是否为空,如果为空则队列为空,返回true;否则返回false。

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

💔8.获取队列当前元素数量

它接受一个指向队列结构体的指针作为参数。

第一行使用了assert宏,它会检查参数pq是否为空指针,如果是则程序会中止运行并输出错误信息。

第三行直接返回队列结构体中的size成员,即队列当前的元素数量。

int QueueSize(Queue* pq)
{
  assert(pq);
  return pq->size;
}

💘3.整体代码


💔1.Queue.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int QDataType;
typedef struct QueueNode
{
  QDataType val;
  struct QueueNode* next;
}QNode;
typedef struct Queue
{
  QNode* phead;
  QNode* ptail;
  int size;
}Queue;
void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);
void QueuePush(Queue* pq, QDataType x);
void QueuePop(Queue* pq);
QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);
bool QueueEmpty(Queue* pq);
int QueueSize(Queue* pq);

💔2.Queue.c

#include"Queue.h"
void QueueInit(Queue* pq)
{
  assert(pq);
  pq->phead = pq->ptail = NULL;
  pq->size = 0;
}
void QueueDestroy(Queue* pq)
{
  assert(pq);
  QNode* cur = pq->phead;
  while (cur)
  {
  QNode* next = cur->next;
  free(cur);
  cur = next;
  }
  pq->phead = pq->ptail = NULL;
  pq->size = 0;
}
void QueuePush(Queue* pq, QDataType x)
{
  assert(pq);
  QNode* newnode = (QNode*)malloc(sizeof(QNode));
  if (newnode == NULL)
  {
  perror("malloc fail");
  return;
  }
  newnode->val = x;
  newnode->next = NULL;
  if (pq->ptail == NULL)
  {
  pq->ptail = pq->phead = newnode;
  }
  else
  {
  pq->ptail->next = newnode;
  pq->ptail = newnode;
  }
  pq->size++;
}
// 17:10
void QueuePop(Queue* pq)
{
  assert(pq);
  // 
  assert(pq->phead);
  QNode* del = pq->phead;
  pq->phead = pq->phead->next;
  free(del);
  del = NULL;
  if (pq->phead == NULL)
  pq->ptail = NULL;
  pq->size--;
}
QDataType QueueFront(Queue* pq)
{
  assert(pq);
  // 
  assert(pq->phead);
  return pq->phead->val;
}
QDataType QueueBack(Queue* pq)
{
  assert(pq);
  // 
  assert(pq->ptail);
  return pq->ptail->val;
}
bool QueueEmpty(Queue* pq)
{
  assert(pq);
  return pq->phead == NULL;
}
int QueueSize(Queue* pq)
{
  assert(pq);
  return pq->size;
}

💔3.Test.c

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