栈的讲解

栈的概念及结构

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

进行数据插入和删除操作的一端称为栈顶,另一端称为栈底(因为先进后出)。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。

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

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

后进先出,后进去的先出来(就像羽毛球桶)

图片实例(这里是我画了一个图,找了两个图)

实现栈的方式

栈的实现可以是顺序表,也可以是链表

入数据 1 2 3 4

出数据 4 3 2 1

顺序表(数组)实现

链表实现的话最好是双链表

当然单链表也是可以的

不过一般情况下我们都是采取单链表的方式进行实现

因为

C语言中栈的实现通常采用顺序表的方式而不是链表的方式,原因包括但不限于以下几点:

1. **简单性**:顺序表的实现相对简单,它是基于数组的,而数组是C语言的基本数据结构。链表的实现需要额外的指针操作,对于栈的基本操作(入栈和出栈)来说,这种复杂性通常是不必要的。

2. **性能**:顺序表(数组)在内存中是连续存储的,这意味着存取速度快,CPU缓存的效率也更高。而链表由于其非连续性,访问速度相对较慢,且不利于缓存优化。

3. **空间效率**:顺序表可以预先分配固定大小的内存空间,而链表则需要为每个元素分配独立的内存空间,并且每个节点都需要额外的指针空间。在栈的应用场景中,通常可以预估栈的最大深度,因此顺序表可以更有效地利用内存。

4. **栈特性**:栈是后进先出(LIFO)的数据结构,其操作主要集中在栈顶进行。顺序表可以通过索引直接访问栈顶元素,而链表则需要从头开始遍历,直到找到最后一个元素。

5. **边界检查**:顺序表可以方便地进行上溢检查,只需比较栈的当前元素数量和预分配的大小即可。链表则需要维护一个额外的计数器来记录元素数量,以进行上溢检查。

6. **缓存局部性**:由于顺序表的数据存储是连续的,它具有较好的时间局部性和空间局部性,这使得它更适合现代计算机系统的缓存机制。

7. **编译器优化**:现代编译器对数组和循环有很好的优化,可以生成高效的代码。而链表的优化则更为复杂,且效果可能不如顺序表。

8. **编程习惯**:C语言程序员通常习惯于使用数组来实现栈,因为数组在C语言中使用非常普遍,且容易理解。

然而,这并不意味着链表不能用于实现栈。在一些特定场景下,链表实现的栈也有其优势,例如当栈的大小频繁变化或者对内存的使用有更严格的要求时。链表可以根据需要动态分配内存,而不需要像顺序表那样预先分配一块较大的内存空间。

选择顺序表还是链表来实现栈,最终取决于具体的应用场景和性能要求。

存在CPU缓存的问题 所以数组其实还是比较好用的

计算机的存储体系-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/Jason_from_China/article/details/138589959

栈的实现

 创建文件

创建栈的结构

typedef int STDataType;
typedef struct Stack
{STDataType* _a; // 首元素的地址int _top;		// 栈顶,初始化为0,也就是等同于size,初始化为-1,等同于下标int _capacity;  // 容量 
}Stack;

这段代码定义了一个名为 Stack 的结构体,用于实现一个栈数据结构:

  1. STDataType:这是一个类型定义,它将 int 类型别名为 STDataType。这意味着在定义栈结构体时,可以使用 STDataType 来表示栈中存储的数据类型为整数。

  2. Stack 结构体:

    • STDataType* _a:这是一个指向 STDataType 类型数据的指针,用于指向栈的内存空间。在栈的上下文中,这个数组通常用来存储栈中的元素。_a 通常指向一个预先分配的数组,该数组的大小由 _capacity 成员指定。
    • int _top:这个整数用来表示栈顶的位置。在大多数栈的实现中,栈顶是一个非常重要的概念,因为它指向了最后一个入栈的元素的位置。在这段代码中,_top 初始化为0,意味着栈顶位于数组的第一个元素(在C语言中数组索引从0开始)。如果使用 _top 初始化为-1,则表示栈是空的,因为此时没有元素入栈,栈顶的下标为-1。
    • int _capacity:这个整数用来存储栈的最大容量,即栈能存储的最大元素数量。在栈操作中,这个值用来检查栈是否已满,以避免栈溢出。

这个 Stack 结构体的定义为栈的实现提供了基础框架。通常,还需要实现一些函数来操作这个栈,比如 Push(入栈)、Pop(出栈)、Top(获取栈顶元素)、IsEmpty(检查栈是否为空)等。接下来我们会逐个实现。

初始化栈

#include"Stack.h"
// 初始化栈 
void StackInit(Stack* ps)
{ps->_a = NULL;// 这里栈顶初始化为-1和初始化为0是不一样的//    0 0 0 0 0 0 0 0 数组// -1 0 0 0 0 0 0 0 0 初始化为-1//    0 0 0 0 0 0 0 0 初始化为0// 初始化为0,也就是等同于size,初始化为-1,等同于下标ps->_capacity = ps->_top = 0;
}

代码解释

  1. `void StackInit(Stack* ps)`:这是 `StackInit` 函数的定义开始,它接收一个指向 `Stack` 结构体的指针 `ps` 作为参数。
  2.  `ps->_a = NULL;`:这行代码将栈的数组指针 `_a` 初始化为 `NULL`。这意味着初始化时栈没有分配任何内存空间,数组为空。
  3. `ps->_capacity = ps->_top = 0;`:这里初始化了两个成员变量:
  4.    - `_capacity`:栈的容量,这里初始化为0,表示栈的最大容量为0,即栈没有任何空间可以存储元素。
  5.  - `_top`:栈顶的索引,这里初始化为0,表示栈顶位于数组的第一个位置。由于数组指针 `_a` 已经初始化为 `NULL`,此时栈是空的。

_top 初始化的不同方式:

  1. - **初始化为-1**:在一些栈的实现中,`_top` 被初始化为-1,用来表示栈为空。这种方式下,数组的索引从0开始,但 `_top` 的初始值-1意味着没有元素在栈中。当第一个元素被推入栈时,`_top` 会增加到0,表示数组的第一个元素现在包含数据。
  2. - **初始化为0**:在这段代码中,`_top` 被初始化为0,这同样表示栈为空。由于 `_a` 是 `NULL`,即使 `_top` 是0,栈实际上也是空的。当第一个元素被推入栈时,数组 `_a` 会被分配内存,且 `_top` 保持不变,直到有元素入栈。
  3. 选择哪种初始化方式取决于你的栈操作的具体实现。如果你的栈操作允许在 `_top` 为0时推入元素,那么初始化 `_top` 为0是合适的。如果你希望 `_top` 总是指向栈顶元素的下一个位置,那么初始化 `_top` 为-1可能更合适。
  4. 在这段代码中,由于 `_a` 被初始化为 `NULL`,栈的状态(空或非空)主要由 `_a` 的值决定。
  5. `_top` 的初始化值更多地是为将来元素入栈时的索引管理做准备。

这里我采取的是初始化为0,也就可以简介表示实际的个数

销毁栈

// 销毁栈 
void StackDestroy(Stack* ps)
{// 判断为不为空,判断里面有没有数值assert(ps && ps->_top);free(ps->_a);ps->_capacity = ps->_top = 0;
}
  1. free(ps->_a);:这行代码调用 free 函数来释放栈的数组 _a 所占用的内存。这是销毁栈的关键步骤,因为它避免了内存泄漏。

  2. ps->_capacity = ps->_top = 0;:在释放了栈的内存之后,将 _capacity_top 成员变量重置为0。_capacity 表示栈的容量,重置为0意味着栈不再具有存储任何元素的能力。_top 表示栈顶的位置,重置为0可以表示栈现在是空的(取决于你的栈实现中 _top 的使用方式)。

入栈

// 入栈 
void StackPush(Stack* ps, STDataType data)
{//首先判断容量是多少,然后进行扩容if (ps->_capacity == ps->_top){//扩容int newapacity = ps->_capacity == 0 ? 4 : 2 * ps->_capacity;STDataType* tmp = (STDataType*)realloc(ps->_a, newapacity * sizeof(STDataType));if (tmp == NULL){perror("StackPush:tmp:error:");exit(1);}//改变容量大小,指向新空间的头第一个元素位置的地址ps->_capacity = newapacity;ps->_a = tmp;}//插入数值ps->_a[ps->_top] = data;ps->_top++;
}

C语言-realloc函数的使用-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/Jason_from_China/article/details/137074963注意,realloc涉及一个原地扩容和异地扩容的情况,所以才会出现这一行代码

ps->_a = tmp;

这段代码定义了一个名为 StackPush 的函数,用于向栈中添加一个新元素。

  1. void StackPush(Stack* ps, STDataType data):函数定义开始,StackPush 接收一个指向 Stack 结构体的指针 ps 和一个要入栈的数据 data

  2. if (ps->_capacity == ps->_top):这行代码检查栈是否已满。_capacity 是栈的最大容量,_top 是栈顶的索引。如果两者相等,表示栈已经达到最大容量。

  3. int newapacity = ps->_capacity == 0 ? 4 : 2 * ps->_capacity;:如果栈已满,则需要扩容。新的容量 newcapacity 是当前容量的两倍,如果当前容量为0(即栈是空的或尚未分配内存),则新容量设置为4。

  4. STDataType* tmp = (STDataType*)realloc(ps->_a, newapacity * sizeof(STDataType));:使用 realloc 函数为栈的数组 _a 分配新的内存空间。realloc 会处理原有内存块的复制,并返回指向新内存块的指针。

  5. if (tmp == NULL):检查 realloc 是否成功。如果 tmpNULL,表示内存分配失败。

  6. perror("StackPush:tmp:error:");:如果内存分配失败,使用 perror 函数输出错误信息。perror 会将参数字符串作为前缀,后跟一个冒号和空格,然后输出当前设定的 errno 描述的错误信息。

  7. exit(1);:如果内存分配失败,调用 exit 函数以非零状态码退出程序。

  8. ps->_capacity = newapacity;:如果内存分配成功,更新栈的容量为新的容量。

  9. ps->_a = tmp;:将栈的数组指针 _a 更新为指向新分配的内存空间。

  10. ps->_a[ps->_top] = data;:将传入的数据 data 存储到栈顶位置。由于栈是顺序存储的,这一步是直接通过数组索引来完成的。

  11. ps->_top++;:更新栈顶索引 _top,将其增加1,以指向数组中的下一个位置,为下一次入栈做准备。

这段代码实现了一个动态栈,它可以根据需要自动扩容。当栈满时,它会增加栈的容量并重新分配内存。需要注意的是,realloc 的使用可能会导致原有内存块被移动到新的内存位置,因此所有指向原内存块的指针都需要更新。此外,realloc 失败时的错误处理是必要的,以避免程序因内存分配问题而崩溃。

出栈

// 出栈 
void StackPop(Stack* ps)
{// 判断为不为空,判断里面有没有数值assert(ps && ps->_top);ps->_top--;
}
  1. void StackPop(Stack* ps):函数定义开始,StackPop 接收一个指向 Stack 结构体的指针 ps 作为参数。

  2. assert(ps && ps->_top);assert 是一个宏,用于在运行时测试一个条件是否为真。如果条件为假,则 assert 将产生一个断言失败,通常会导致程序异常终止。在这里,它用于确保传入的栈指针 ps 不是 NULL,并且 ps->_top 也是非空的,从而确保栈已经初始化过并且 _top 成员变量是有效的。

  3. ps->_top--;:这行代码将栈顶索引 _top 减一。由于栈是后进先出(LIFO)的数据结构,减少 _top 的值就相当于从栈顶部移除了一个元素。在栈的顺序存储实现中,通常不需要实际移动数组中的元素,只需要更新栈顶的索引即可。

出栈其实就很简答,只需要删除栈顶就可以

删除

获取栈顶元素 

// 获取栈顶元素 
STDataType StackTop(Stack* ps)
{assert(ps && ps->_top);return ps->_a[ps->_top - 1];
}
  1. STDataType StackTop(Stack* ps):函数定义开始,StackTop 接收一个指向 Stack 结构体的指针 ps 作为参数,并返回栈顶的元素。

  2. assert(ps && ps->_top);assert 是一个宏,用于在运行时测试一个条件是否为真。如果条件为假,则 assert 将产生一个断言失败,通常会导致程序异常终止。在这里,它用于确保传入的栈指针 ps 不是 NULL,并且 ps->_top 也是非空的,从而确保栈已经初始化过并且 _top 成员变量是有效的。

  3. return ps->_a[ps->_top - 1];:这行代码返回栈顶元素的值。由于栈是后进先出(LIFO)的数据结构,栈顶元素就是数组 _a 中索引为 _top - 1 的元素。这里假设 _top 初始化为0并且数组索引从0开始,所以栈顶元素的索引是 _top - 1

获取栈中有效元素个数 

int StackSize(Stack* ps)
{assert(ps && ps->_top);return ps->_top - 1;
}

检测栈是否为空,如果为空返回非零结果(1),如果不为空返回0 

int StackEmpty(Stack* ps)
{assert(ps);return ps->_top == 0;// 所以,ps->_top == 0 这个表达式的作用是比较栈顶位置 ps->_top 是否等于 0。// 如果相等,说明栈为空,表达式结果为 true(在C语言中用 1 表示);// 如果不相等,说明栈不为空,表达式结果为 false(在C语言中用 0 表示)
}

栈代码的总结

Stack.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int STDataType;
typedef struct Stack
{STDataType* _a; // 首元素的地址int _top;		// 栈顶,初始化为0,也就是等同于size,初始化为-1,等同于下标int _capacity;  // 容量 
}Stack;
// 初始化栈 
void StackInit(Stack* ps);
// 销毁栈 
void StackDestroy(Stack* ps);
// 入栈 
void StackPush(Stack* ps, STDataType data);
// 出栈 
void StackPop(Stack* ps);
// 获取栈顶元素 
STDataType StackTop(Stack* ps);
// 获取栈中有效元素个数 
int StackSize(Stack* ps);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 
int StackEmpty(Stack* ps);

Stack.c

#include"Stack.h"
// 初始化栈 
void StackInit(Stack* ps)
{ps->_a = NULL;// 这里栈顶初始化为-1和初始化为0是不一样的//    0 0 0 0 0 0 0 0 数组// -1 0 0 0 0 0 0 0 0 初始化为-1//    0 0 0 0 0 0 0 0 初始化为0// 初始化为0,也就是等同于size,初始化为-1,等同于下标ps->_capacity = ps->_top = 0;
}
// 销毁栈 
void StackDestroy(Stack* ps)
{// 判断为不为空,判断里面有没有数值assert(ps && ps->_top);free(ps->_a);ps->_capacity = ps->_top = 0;
}
// 入栈 
void StackPush(Stack* ps, STDataType data)
{//首先判断容量是多少,然后进行扩容if (ps->_capacity == ps->_top){//扩容int newapacity = ps->_capacity == 0 ? 4 : 2 * ps->_capacity;STDataType* tmp = (STDataType*)realloc(ps->_a, newapacity * sizeof(STDataType));if (tmp == NULL){perror("StackPush:tmp:error:");exit(1);}//改变容量大小,指向新空间的头第一个元素位置的地址ps->_capacity = newapacity;ps->_a = tmp;}//插入数值ps->_a[ps->_top] = data;ps->_top++;
}
// 出栈 
void StackPop(Stack* ps)
{// 判断为不为空,判断里面有没有数值assert(ps && ps->_top);ps->_top--;
}
// 获取栈顶元素 
STDataType StackTop(Stack* ps)
{assert(ps && ps->_top);return ps->_a[ps->_top - 1];
}
// 获取栈中有效元素个数 
int StackSize(Stack* ps)
{assert(ps && ps->_top);return ps->_top - 1;
}
// 检测栈是否为空,如果为空返回非零结果(1),如果不为空返回0 
int StackEmpty(Stack* ps)
{assert(ps);return ps->_top == 0;// 所以,ps->_top == 0 这个表达式的作用是比较栈顶位置 ps->_top 是否等于 0。// 如果相等,说明栈为空,表达式结果为 true(在C语言中用 1 表示);// 如果不相等,说明栈不为空,表达式结果为 false(在C语言中用 0 表示)
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/836485.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构与算法===回溯法

文章目录 原理使用场景括号生成代码 小结 原理 回溯法是采用试错的思想&#xff0c;它尝试分步骤的去解决一个问题。在分步骤解决问题的过程中&#xff0c;当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候&#xff0c;它将取消上一步甚至是上几步的计算&#x…

函数模板底层本质

#include<iostream> using namespace std;template<typename T1> T1 ave(T1 a, T1 b) { return a b; }int main() {ave(100, 200);ave(short(100), short(200));return 0; }反汇编代码 模板本质是编译器帮忙生成了不同的函数 就算非类型参数值不一样编译器也重新…

HCIP(BGP综合实验)--8

一&#xff1a;实验要求 二&#xff1a;实现过程 &#xff08;一&#xff09;配置IP地址&#xff1a; AR1: [AR1]int g0/0/0 [AR1-GigabitEthernet0/0/0]ip add 12.1.1.1 24 [AR1-GigabitEthernet0/0/0]int l0 [AR1-LoopBack0]ip add 172.16.0.1 32 [AR1-LoopBack0]int l1 […

2024OD机试卷-按身高和体重排队 (java\python\c++)

题目:按身高和体重排队 题目描述 某学校举行 运动会 ,学生们按编号(1、2、3…n)进行标识,现需要按照身高由低到高排列,对身高相同的人,按体重由轻到重排列;对于身高体重都相同的人,维持原有的 编号顺序关系。请输出排列后的学生编号。 输入描述 两个序列,每个序列由…

TM1650 并联在I2C 信号线的处理方法

目的是可以并联多个TM1650 在标准I2C 总线上&#xff0c;并且不影响其他标准I2C 器件。思路就是拿个额外的开关控制每一片TM1650 的使能&#xff0c;就像SPI 的CS 信号那样。 协议 TM1650 的通信协议虽说不是标准I2C&#xff0c;但也算是比较兼容的&#xff0c;比方说&#x…

springboot实现Aop

一、原理 AOP&#xff08;Aspect Oriented Programming&#xff09;的意思是&#xff1a;面向切面编程&#xff0c;通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。利用AOP可对业务逻辑进行增强&#xff0c;在不改变原有逻辑的基础上&#xff0c;在其前后进…

JVM调优:JVM常用调优命令和参数

JVM调优是调整和配置Java虚拟机&#xff08;JVM&#xff09;的过程&#xff0c;以便最大限度地提高应用程序的性能和效率。这涉及到调整内存设置、选择合适的垃圾收集器&#xff0c;以及配置各种性能参数。 今天我们来了解一下参数如何设置 JVM调优的重要性 1.通过调整JVM参数…

YOLOv8独家原创改进: AKConv(可改变核卷积)

1.AKConv原理介绍 地址:2311.11587 (arxiv.org) 摘要:基于卷积运算的神经网络在深度学习领域取得了令人瞩目的成果,但标准卷积运算存在两个固有的缺陷。一方面,卷积运算仅限于局部窗口,无法捕获其他位置的信息, 并且它的采样形状是固定的。 另一方面,卷积核的大小固定为…

合肥知识付费系统解决方案,培训机构试听课不理想要怎么转化成学员?

现在到处都是培训机构&#xff0c;机构之间的竞争也是越发激烈的&#xff0c;很多机构为了获得生源&#xff0c;都会利用试听课来让家长或孩子&#xff0c;感受机构教学以及服务&#xff0c;那培训机构试听课不理想要怎么转化学员&#xff1f; 因为机构的试听课课程并不是同一种…

基于yolov8+gradio目标检测演示系统设计

YOLOv8与Gradio&#xff1a;开启目标检测的可视化新篇章 随着人工智能技术的飞速发展&#xff0c;目标检测作为计算机视觉领域的重要分支&#xff0c;已经广泛应用于安防监控、自动驾驶、医疗影像等多个领域。而YOLO&#xff08;You Only Look Once&#xff09;系列算法作为目…

光线追踪的渲染流程

光线追踪是一种基于物理的渲染技术&#xff0c;它模拟了光线在场景中的传播和相互作用过程。以下是光线追踪的基本渲染流程&#xff1a; 场景描述&#xff1a; 首先&#xff0c;需要描述场景&#xff0c;包括物体的几何形状、材质、光源等信息。每个物体通常由其几何形状&#…

23种设计模式学习导航

23种设计模式在Java中是一种重要的软件开发原则&#xff0c;旨在提高代码的可维护性、可扩展性和重用性。这些设计模式分为创建型模式、结构型模式和行为型模式三类。以下是这23种设计模式的Java完整版学习导航&#xff1a; 一、创建型模式&#xff08;5种&#xff09; 单例模…

Maven多环境与SpringBoot多环境配置

1. Maven多环境配置与应用 1.1 多环境开发 我们平常都是在自己的开发环境进行开发&#xff0c; 当开发完成后&#xff0c;需要把开发的功能部署到测试环境供测试人员进行测试使用&#xff0c; 等测试人员测试通过后&#xff0c;我们会将项目部署到生成环境上线使用。 这个时…

Spring中用到的设计模式有哪些

工厂模式,BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得Bean对象。 单例模式,Spring依赖注入Bean实例默认是单例的。Spring依赖注入(包括lazy-init方式)都是发生在AbstractBeanFactory的getBean里。getBean的doGetBean方法调用getSingleton进行bean的创…

手写8大排序算法Java版

如何手写8大排序算法&#xff1a;从入门到精通 排序算法是计算机科学中最基本的算法之一&#xff0c;几乎所有编程语言的面试都会涉及到排序算法的问题。掌握排序算法不仅能够帮助你更好地理解数据结构和算法&#xff0c;还能提升你解决实际问题的能力。本文将带你从零开始&am…

VSCode调试Typescript

目录 背景介绍安装运行问题提示没有npm或者tsc命令提示tsc : 无法加载文件ts-debugObject.defineProperty(exports, __esModule, { value: true })调试时自动断点 背景介绍 由于近期要涉及鸿蒙相关的功能&#xff0c;之前没有接触过TS这个语言&#xff0c;所以想要熟悉一下。 …

Unity曲线插件Dreamteck Splines生成曲线Mesh

一、需求 脱离编辑器&#xff0c;运行时添加点&#xff0c;动态生成管道、线缆等曲线Mesh。 二、Dreamteck Splines简单运用 这方面资料不多&#xff0c;只有官方文档全英参考&#xff0c;而且又介绍得不详细。 2个重要组件介绍&#xff1a; SplineComputer&#xff1a; 最…

yum、rpm相关命令-工具手册

1.rpm文件: 1.1安装rpm文件: rpm -ivh kde-select.rpm23 #--nodeps强制安装,无视环境缺少依赖的检查 rpm -ivh --nodeps kde-select.rpm #--force --replacefiles可以无视rpm的冲突去强制替换(如两个rpm的安装路径相同了会有冲突) rpm -ivh --nodeps --force --replacef…

设计模式——备忘录模式(Memento)

备忘录模式&#xff08;Memento Pattern&#xff09;&#xff0c;也称为快照模式或Token模式&#xff0c;是GoF&#xff08;Gang of Four&#xff0c;四位设计模式专家&#xff09;提出的23种设计模式之一&#xff0c;属于行为模式。该模式的主要目的是在不破坏封装性的前提下&…

springboot中mybatisplus注意事项

使用代码生成工具CodeGenerator 需要修改的内容 dsc.setUsername(“root”); mysql账号dsc.setPassword(“root”); mysql密码strategy.setInclude(“crm_edu”); 表名pc.setModuleName(“eduservice”); //模块名 package com.test.demo;import com.baomidou.mybatisplus.a…