【初阶数据结构篇】栈的实现(附源码)

简介: 在每一个方法的第一排都使用assert宏来判断ps是否为空(避免使用时传入空指针,后续解引用都会报错)。


1 代码位置


[gitee](Stack/Stack · petrichor/2024-summer-c-language - 码云 - 开源中国 (gitee.com))


2 概念与结构


1.1概念


⼀种特殊的线性表,其只允许在固定的⼀端进⾏插⼊和删除元素操作进⾏数据插⼊和删除操作的⼀端称为栈顶,另⼀端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out的原则。


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


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



1.2结构


栈的实现⼀般可以使⽤数组或者链表实现,相对⽽⾔数组的结构实现更优⼀些。因为数组在尾上插⼊数据的代价⽐较⼩。


数组尾插时间复杂度:O(1) 链表尾插时间复杂度:O(N)


2 栈的实现


因为栈的底层是数组,所以栈的实现方法和动态顺序表大致相同,且因为栈只能在栈顶出和入数据,所以栈还要更简单一些


Stack.h


#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include<stdbool.h>
typedef int STDataType;
typedef struct Stack
{
  STDataType* arr;
  STDataType capacity;
  STDataType top;
}ST;

//初始化和销毁
void STInit(ST*);
void STDestroy(ST*);


//栈顶---入数据,出数据
void StackPush(ST*, STDataType);
void StackPop(ST*);

//判空
bool StackEmpty(ST*);

//取栈顶元素
STDataType StackTop(ST*);


//获取栈中有效元素个数
int STSize(ST*);

test.c


  • 用来测试我们写的函数(函数的调用)
  • 这一部分就是自己写的时候用的测试用例,随便什么都行


最好是写一个方法测试一次,不然找错误的时候会很痛苦😜

#define _CRT_SECURE_NO_WARNINGS 1
#include "Stack.h"
void STTest01()
{
  ST st;
  STInit(&st);
  
  StackPush(&st, 1);
  StackPush(&st, 2);
  StackPush(&st, 3);
  StackPush(&st, 4);
  StackPush(&st, 5);

  
  //StackPop(&st);
  //StackPop(&st);
  //StackPop(&st);
  //StackPop(&st);
  //StackPop(&st);
  // 
  
  printf("size: %d\n", STSize(&st));
  
  while (!StackEmpty(&st))
  {
    STDataType data = StackTop(&st);
    printf("%d ", data);
    StackPop(&st);
  }
  printf("\n");
  
  printf("size: %d\n", STSize(&st));

}
int main()
{
  STTest01();
  return 0;
}

Stack.c


函数方法的实现,重点重点!!!


在每一个方法的第一排都使用assert宏来判断ps是否为空(避免使用时传入空指针,后续解引用都会报错)


2.1 栈的初始化和销毁


2.1.1 初始化
void STInit(ST* ps)
{
  assert(ps);
  ps->arr = NULL;
  ps->capacity = ps->top = 0;
}
2.1.2 销毁
void STDestroy(ST*ps)
{
  assert(ps);
  if (ps->arr)
    free(ps->arr);
  ps->arr = NULL;
  ps->top = ps->capacity = 0;
}

注:栈的特性决定了它无法被遍历和随机访问和插入数据,只能在栈顶操作!!!所以打印方法不能和顺序表一样遍历,具体方法会在后面讲到


2.2 栈顶插入和删除数据


2.2.1 栈顶插入数据(压栈)


插入数据的时候一定要判断空间是否足够,不足要增容,一般2倍或3倍增容!!!


养成好习惯,不要用arr直接接收动态开辟空间的地址,否则开辟失败arr变为NULL,连原来的内存块都找不到了,这就造成了内存泄漏!!!


void StackPush(ST* ps, STDataType x)
{
  assert(ps);

  //空间是否足够
  if (ps->top == ps->capacity)
  {
    int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
    STDataType* tmp = (STDataType*)realloc(ps->arr, newcapacity * sizeof(STDataType));
    if (tmp == NULL)
    {
      perror("realloc fail!");
      exit(1);
    }
    ps->arr = tmp;
    ps->capacity = 2 * newcapacity;
  }
  ps->arr[ps->top++] = x;
}
2.2.2 栈顶删除数据(出栈)


删除数据的时候一定要判断栈是否为空,即top不能为0!!!


bool StackEmpty(ST* ps)
{
  assert(ps);
  return ps->top == 0;
}

void StackPop(ST* ps)
{
  assert(ps);
  assert(!StackEmpty(ps));
  --ps->top;
}
  • 只要让top–即可,不影响后来的插入(数据会被覆盖)

2.3 返回栈顶数据

STDataType StackTop(ST* ps)
{
  assert(ps);
  assert(!StackEmpty(ps));
  return ps->arr[ps->top-1];
}

2.4 返回栈的有效数据个数

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

2.5 打印栈中数据



  • 之前说到栈不能通过遍历来打印,所以只有通过循环取栈顶元素后再让其出栈的方式来依次打印,打印完了栈也为空了!

while (!StackEmpty(&st))
  {
    STDataType data = StackTop(&st);
    printf("%d ", data);
    StackPop(&st);
  }
  printf("\n");

Stack.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "Stack.h"
void STInit(ST* ps)
{
  assert(ps);
  ps->arr = NULL;
  ps->capacity = ps->top = 0;
}

void STDestroy(ST*ps)
{
  assert(ps);
  if (ps->arr)
    free(ps->arr);
  ps->arr = NULL;
  ps->top = ps->capacity = 0;
}

void StackPush(ST* ps, STDataType x)
{
  assert(ps);

  if (ps->top == ps->capacity)
  {
    int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
    STDataType* tmp = (STDataType*)realloc(ps->arr, newcapacity * sizeof(STDataType));
    if (tmp == NULL)
    {
      perror("realloc fail!");
      exit(1);
    }
    ps->arr = tmp;
    ps->capacity = 2 * newcapacity;
  }
  ps->arr[ps->top++] = x;
}


bool StackEmpty(ST* ps)
{
  assert(ps);
  return ps->top == 0;
}
void StackPop(ST* ps)
{
  assert(ps);
  assert(!StackEmpty(ps));
  --ps->top;
}



STDataType StackTop(ST* ps)
{
  assert(ps);
  assert(!StackEmpty(ps));
  return ps->arr[ps->top-1];
}



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


对于栈这一种结构的实现,因为和顺序表差别不大,更细节的内容就没有过多赘述,对以数组作为底层结构的线性表的实现方法有什么疑问的话,推荐先去看这一篇哦->顺序表的实现方法

目录
相关文章
|
5月前
|
存储 安全 Java
Java 集合面试题从数据结构到 HashMap 源码剖析详解及长尾考点梳理
本文深入解析Java集合框架,涵盖基础概念、常见集合类型及HashMap的底层数据结构与源码实现。从Collection、Map到Iterator接口,逐一剖析其特性与应用场景。重点解读HashMap在JDK1.7与1.8中的数据结构演变,包括数组+链表+红黑树优化,以及put方法和扩容机制的实现细节。结合订单管理与用户权限管理等实际案例,展示集合框架的应用价值,助你全面掌握相关知识,轻松应对面试与开发需求。
295 3
|
5月前
|
编译器 C语言 C++
栈区的非法访问导致的死循环(x64)
这段内容主要分析了一段C语言代码在VS2022中形成死循环的原因,涉及栈区内存布局和数组越界问题。代码中`arr[15]`越界访问,修改了变量`i`的值,导致`for`循环条件始终为真,形成死循环。原因是VS2022栈区从低地址到高地址分配内存,`arr`数组与`i`相邻,`arr[15]`恰好覆盖`i`的地址。而在VS2019中,栈区先分配高地址再分配低地址,因此相同代码表现不同。这说明编译器对栈区内存分配顺序的实现差异会导致程序行为不一致,需避免数组越界以确保代码健壮性。
118 0
栈区的非法访问导致的死循环(x64)
232.用栈实现队列,225. 用队列实现栈
在232题中,通过两个栈(`stIn`和`stOut`)模拟队列的先入先出(FIFO)行为。`push`操作将元素压入`stIn`,`pop`和`peek`操作则通过将`stIn`的元素转移到`stOut`来实现队列的顺序访问。 225题则是利用单个队列(`que`)模拟栈的后入先出(LIFO)特性。通过多次调整队列头部元素的位置,确保弹出顺序符合栈的要求。`top`操作直接返回队列尾部元素,`empty`判断队列是否为空。 两题均仅使用基础数据结构操作,展示了栈与队列之间的转换逻辑。
|
10月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
500 77
|
8月前
|
存储 自然语言处理 数据库
【数据结构进阶】AVL树深度剖析 + 实现(附源码)
在深入探讨了AVL树的原理和实现后,我们不难发现,这种数据结构不仅优雅地解决了传统二叉搜索树可能面临的性能退化问题,还通过其独特的平衡机制,确保了在任何情况下都能提供稳定且高效的查找、插入和删除操作。
676 19
|
8月前
|
数据库 C++
【数据结构进阶】红黑树超详解 + 实现(附源码)
本文深入探讨了红黑树的实现原理与特性。红黑树是一种自平衡二叉搜索树,通过节点着色(红/黑)和特定规则,确保树的高度接近平衡,从而实现高效的插入、删除和查找操作。相比AVL树,红黑树允许一定程度的不平衡,减少了旋转调整次数,提升了动态操作性能。文章详细解析了红黑树的性质、插入时的平衡调整(变色与旋转)、查找逻辑以及合法性检查,并提供了完整的C++代码实现。红黑树在操作系统和数据库中广泛应用,其设计兼顾效率与复杂性的平衡。
1596 3
|
9月前
|
算法 调度 C++
STL——栈和队列和优先队列
通过以上对栈、队列和优先队列的详细解释和示例,希望能帮助读者更好地理解和应用这些重要的数据结构。
229 11
|
9月前
|
DataX
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
10月前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
411 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
10月前
|
存储 C语言 C++
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
218 9

热门文章

最新文章