【数据结构】栈

简介: 【数据结构】栈

😺前言


  • 👻前面我们学习了链表,总算是跨过一个台阶了,本章带大家轻松一波,领悟一下栈的魅力。
  • 🤡栈是一种较为简单的数据结构,它的主要性质,就是数据后进先出(LIFO),我们可以利用这一性质,在做某些算法题时,以此为切入点。因此,栈还是挺不错的。


栈初始化


  • 栈的性质是后进先出(不能任意插入删除),并且只能访问栈顶元素,因此,对数据的入栈和出栈是特别方便的。


081ff4f87a5a4578883a5a26fd6658ce.png


根据栈的这样一个后进先出的性质,我们该如何选择它的底层结构呢?是顺序表的数组还是链表呢?由此我们分析一下。


如果是数组,很容易想到,完全符合对栈的性质的操作,如果是入栈,直接在数组最后一个位置插入即可,空间不够扩容就好,如果是出栈,直接将统计栈顶的变量减一即可,想要获取栈顶的数据,那不也很简单嘛。由此看来,数组还真是不错呢。


如果是链表,入栈只要将数据头插即可,出栈就是头删,获取栈顶数据就是头节点的数据,这样看来,链表也挺不错呢。那么到底是选数组还是链表呢?


我们来看一下数据对比:


d76ca8f8dd234b3b854d86b1e065bf8e.png



可以看到,对于栈的性质,该表的前面五个特点都没有明显的优与劣(就扩容会有差别),但是最后一个缓存利用率却能分出高下,很明显,实现一个栈,使用数组会更好。(对于什么是缓存利用率,这里就不讲解了,大家自行查找资料噢)

选取了实现栈的底层结构,接下来就是对栈的初始化操作了。


首先需要三个文件(与前面的单链表一样哈哈哈)stack.h,stack.c,test.c,stack.h用来存放一些所需头文件,函数声明以及栈的结构体。stack.c用来实现函数接口,test.c用来测试。


所需头文件:

#include <stdio.h>
// assert断言
#include <assert.h>
// 判空需用到
#include <stdbool.h>
// 动态空间需用
#include <stdlib.h>
  • 接下来就对控制栈的结构体进行创建:
typedef int STDataType;
typedef struct stack
{
  // 底层数组
  STDataType* a;
  // 容量
  int capacity;
  // 标识栈顶
  int top;
}stack;


  • 接下来是函数声明:
// 初始化
void STInit(stack* pt);
// 入栈
void STPush(stack* pt, STDataType x);
// 出栈
void STPop(stack* pt);
// 判空
bool STEmpty(stack* pt);
// 取栈顶元素
STDataType STTop(stack* pt);
// 栈的元素个数
int STSize(stack* pt);
// 销毁栈
void STDestroy(stack* pt);


怎么样,是不是看了函数接口就很简单呢,只有这些接口噢。

栈的初始化:

  • 对于栈的初始化,我们可以直接将topcapacity置为0,指向存放数据的数组的指针置为NULL。如下:
void STInit(stack* pt)
{
  // 这里断言一下pt是为了防止传递一个NULL指针,正确的应该传递一个结构体的地址
  assert(pt);
  pt->a = NULL;
  pt->capacity = 0;
  pt->top = 0;
}

关于top实际上还可以初始化为-1。但-1与0实际差别不大,只是获取栈顶元素,判空和数据入栈这些代码会有所不同。如果top初始化为-1,那么top就是栈顶元素,如果top初始化为0,top就是栈顶元素的下一个位置。所以我们在实现的时候,要注意这个top。


而本章采用的top为0,也就是top是栈顶元素的下一个位置。


top = 0 ,实际上也间接体现了此时栈的数据个数。


栈顶入栈


栈顶入栈就是在数组的最后一个位置添加一个数据即可,操作简单,不过这里要考虑一个扩容的问题。


如果此时空间容量不够(判断条件:top == capacity),就需要扩容,这里使用realloc扩容,如果开始没有空间,该函数的功能就跟malloc一样。


插入只要在top位置插入即可,最后top要加加一下噢。


下面是相关函数接口的代码实现:

void STPush(stack* pt, STDataType x)
{
  // 防止传递一个NULL
  assert(pt);
  // 检查容量看是否已满,满了就需要扩容
  if (pt->top == pt->capacity)
  {
    int newcapacity = pt->capacity == 0 ? 4 : pt->capacity * 2;
    STDataType* tmp = realloc(pt->a, sizeof(STDataType) * newcapacity);
    if (tmp == NULL)
    {
      perror("realloc fail");
      exit(-1);
    }
    pt->a = tmp;
    pt->capacity = newcapacity;
  }
  // 在top位置放新的的数据,同时top要加一
  pt->a[pt->top++] = x;
}


栈顶出栈


  • 栈顶出栈就是抹去数组最后一个数据,只要top自减一即可。
  • 注意,当栈为空的时候,就不能出栈了,如何判断是否为空?在下面呢,别急哈哈哈。


下面是相关函数接口的代码实现:

void STPop(stack* pt)
{
  // 如果为空就不能删了。判空在后面写到。
  assert(pt && !STEmpty(pt));
  // top减一即可
  pt->top--;
}



栈体判空


  • 栈体判空,只需要判断一下top是否为0即可。如果是0,说明此时栈为空,返回true;如果不等于0,说明此时栈不为空,返回false

下面是相关函数接口的代码实现:

bool STEmpty(stack* pt)
{
  assert(pt);
  return pt->top == 0;
}


栈的数据个数


  • 前面说了,top就相当于是数据的个数,所以这里直接返回top的值即可。

下面是相关函数接口的代码实现:


int STSize(stack* pt)
{
  assert(pt);
  return pt->top;
}


获取栈顶元素


  • 由于top是最后一个元素的下一个位置,因此获取栈顶元素时,它的位置为top - 1

下面是相关函数接口的代码实现:

STDataType STTop(stack* pt)
{
  assert(pt && !STEmpty(pt));
  return pt->a[pt->top - 1];
}


栈的销毁


  • 有空间的开辟,那就要有空间的释放,这里称为销毁。
  • 销毁也很简单,只需要将指向栈空间的那个指针free即可。
  • 当然,最后可以将top置为0capacity也置为0

下面是相关函数接口的代码实现:

void STDestroy(stack* pt)
{
  assert(pt);
  free(pt->a);
  pt->capacity = 0;
  pt->top = 0;
}


整体代码


stack.h


#include <stdio.h>
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
typedef int STDataType;
typedef struct stack
{
  STDataType* a;
  int capacity;
  int top;
}stack;
// 初始化
void STInit(stack* pt);
// 入栈
void STPush(stack* pt, STDataType x);
// 出栈
void STPop(stack* pt);
// 判空
bool STEmpty(stack* pt);
// 取栈顶元素
STDataType STTop(stack* pt);
// 栈的元素个数
int STSize(stack* pt);
// 销毁栈
void STDestroy(stack* pt);



stack.c

#include "stack.h"
void STInit(stack* pt)
{
  assert(pt);
  pt->a = NULL;
  pt->capacity = 0;
  pt->top = 0;
}
void STPush(stack* pt, STDataType x)
{
  assert(pt);
  if (pt->top == pt->capacity)
  {
    int newcapacity = pt->capacity == 0 ? 4 : pt->capacity * 2;
    STDataType* tmp = realloc(pt->a, sizeof(STDataType) * newcapacity);
    if (tmp == NULL)
    {
      perror("realloc fail");
      exit(-1);
    }
    pt->a = tmp;
    pt->capacity = newcapacity;
  }
  pt->a[pt->top++] = x;
}
void STPop(stack* pt)
{
  assert(pt && !STEmpty(pt));
  pt->top--;
}
bool STEmpty(stack* pt)
{
  assert(pt);
  return pt->top == 0;
}
STDataType STTop(stack* pt)
{
  assert(pt && !STEmpty(pt));
  return pt->a[pt->top - 1];
}
int STSize(stack* pt)
{
  assert(pt);
  return pt->top;
}
void STDestroy(stack* pt)
{
  assert(pt);
  free(pt->a);
  pt->capacity = 0;
  pt->top = 0;
}


test.c

#include "stack.h"
void test()
{
  stack st;
  STInit(&st);
  STPush(&st, 1);
  STPush(&st, 2);
  STPush(&st, 3);
  STPush(&st, 4);
  STPush(&st, 5);
  while (!STEmpty(&st))
  {
    printf("%d ", STTop(&st));
    STPop(&st);
  }
  printf("\n");
  STPush(&st, 5);
  STPush(&st, 55);
  STPush(&st, 555);
  STPush(&st, 5555);
  STPush(&st, 55555);
  STPush(&st, 555555);
  printf("%d\n", STTop(&st));
  while (!STEmpty(&st))
  {
    printf("%d ", STTop(&st));
    STPop(&st);
  }
  printf("\n");
  STDestroy(&st);
}
int main()
{
  test();
  return 0;
}


😼写在最后


💝到这里,一个简简单单的栈就完成了,是不是很简单呢?不过也别得意哈,后面难的数据结构还没来呢,栈就是让你处于一个平静期,为后来的难度做准备呢哈哈哈哈哈。

❤️‍🔥后续将会持续输出有关数据结构的文章,你们的支持就是我写作的最大动力!


感谢阅读本小白的博客,错误的地方请严厉指出噢~

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