当前位置:首页 > > 小麦大叔
[导读]栈(stack)是限定仅在表的一端进行操作的数据结构,且栈是一种先进后出的数据结构,允许操作的一端称为栈顶,不允许操作的称为栈底。




关注、星标公众号 ,直达精彩内容

ID:技术让梦想更伟大

作者:李肖遥

栈的概念

栈(stack)是限定仅在表的一端进行操作的数据结构,且栈是一种先进后出的数据结构,允许操作的一端称为栈顶,不允许操作的称为栈底,如下图所示:

之前我们讲到了链表,我们只能够对其链表的表尾结点进行操作,并且只能进行插入一个新的结点与删除最末尾的这个结点两个操作,而这样强限制性的‘链表’,就是我们所说的

就像是一个死胡同一样,只有一个出口,如图所示,有个概念:

栈的结点设计

栈分为数组栈链表栈,其区别如下:

  • 数组栈使用数组进行功能的模拟,实现较为快速和便利;

  • 链表栈使用链表的思路去设计,实现相比较说较为麻烦,但是其稳定,且不易出错;

在链表栈中又分为静态链表栈和动态链表栈,其区别如下:

  • 静态链表栈给定栈的空间大小,不允许超过存储超过给定数据大小的元素;

  • 动态栈使用的是自动创建空间的方法进行创建,只要符合机器的硬件要求以及编译器的控制,其理论上是极大的。

数组栈

其实际就是用一段连续的存储空间来存储栈中的数据元素,有以下特点:

  1. 元素所占的存储空间必须连续,这里的连续是指的逻辑连续,而不是物理连续。

  2. 元素在存储空间的位置是按逻辑顺序存放的

我们来举例说明,鉴于C语言数组下标都是0开始,并且栈的使用需要的空间大小难以估计,所以初始化空栈的时候,不应该设定栈的最大容量。

我们先为栈设定一个基本容量,在应用过STACK_程当中,当栈的空间不够用时,再逐渐扩大。

设定2个常量,STACK_INIT_SIZE(存储空间初始化分配量)和STACK_INCREMENT(存储空间分配增量),宏定义如下

#define STACK_INIT_SIZE 1000 //数值可以根据实际情况确定
#define STACK_INCREMENT 10   //数值可以根据实际情况确定

栈的定义如下

typedef struct      
{
    void *base; 
    void *top;
    int stackSize;
} SqSTACK;
  • base 表示栈底指针

  • top 表示栈顶指针

  • stackSize 表示栈当前可以使用的最大容量

若base的值是NULL,表示栈结构不存在;top初始值指向栈底,即top = base;

每当插入新的元素时,指针top就增1,反之删除就减1,非空栈中的栈顶指针始终在栈顶元素的下一个指针上面。

数据元素和栈顶指针的关系如下图所示:

链表栈

我们以链表栈的动态链表栈为例子,进行栈的设计。

首先是栈的结点,设计出两个结构体,一个结构体Node表示结点,其中包含有一个data域和next指针,如图所示:

Node

其中data表示数据,next指针表示下一个的指针,其指向下一个结点,通过next指针将各个结点链接。

接下来是我们设计的重点,为这个进行限制性的设计,我们需要额外添加一个结构体,其包括了一个永远指向栈头的指针top一个计数器count记录元素个数。

其主要功效就是设定允许操作元素的指针以及确定栈何时为空,如图所示:

Stack

这里我采用的是top和count组合的方法。其代码可以表示为:

//栈的结点设计
//单个结点设计,数据和下一个指针
typedef struct node     
{

    int data; 
    struct node *next;
} Node;

利用上面的结点创建栈,分为指向头结点的top指针和计数用的count

typedef struct stack    
{
    Node *top;
    int count;
} Link_Stack;

栈的基本操作—入栈(压栈)

入栈的基本顺序可以用以下图所示:

入栈(push)操作时,我们只需要找到top所指向的空间,创建一个新的结点,将新的结点的next指针指向top指针指向的空间,再将top指针转移,并且指向新的结点,这就是是入栈操作

其代码可以表示为:


//入栈 push
Link_Stack *Push_stack(Link_Stack *p, int elem)
{
    if (p == NULL)
        return NULL;
    Node *temp;
    temp=(Node*)malloc(sizeof(Node));
    //temp = new Node;
    temp->data = elem;
    temp->next = p->top;
    p->top = temp;
    p->count++;
    return p;
}

栈的基本操作—出栈

出栈(pop)操作,是在栈不为空的情况下,重复说一次,一定要进行判空操作,将栈顶的元素删除,同时top指针,next向下进行移动即可的操作。

其代码可以表示为:

//出栈 pop
Link_Stack *Pop_stack(Link_Stack *p)
{
    Node *temp;
    temp = p->top;
    if (p->top == NULL)
    {
        printf("错误:栈为空");
        return p;
    }
    else
    {
        p->top = p->top->next;
        free(temp);
        //delete temp;
        p->count--;
        return p;
    }
}

栈的基本操作—遍历

这个就很常见了,也是我们调试必须的手段。

栈的遍历相对而言比较复杂,由于栈的特殊性质,其只允许在一端进行操作,所以遍历操作操作永远都是逆序的

简单一点描述,其过程为,在栈不为空的情况下,一次从栈顶元素向下访问,直到指针指向空(即到栈尾)为结束。

其代码可以表示为:

//遍历栈:输出栈中所有元素
int show_stack(Link_Stack *p)
{
    Node *temp;
    temp = p->top;
    if (p->top == NULL)
    {
        printf("");
        printf("错误:栈为空");
        return 0;
    }
    while (temp != NULL)
    {
        printf("%d\t", temp->data);
        temp = temp->next;
    }
    printf("\n");
    return 0;
}

栈数组与栈链表的代码实现

最后呢,我们使用代码来帮助我们了解一下:

栈数组

数组栈是一种更为快速的模拟实现栈的方法,这里我们不多说。

模拟,就是不采用真实的链表设计,转而采用数组的方式进行模拟操作

也就是说这是一种仿真类型的操作,其可以快速的帮助我们构建代码,分析过程,相应的实现起来也更加的便捷。

其代码如下:

#include
#include
#include
#define maxn 10000
 
//结点设计
typedef struct stack{
    int data[maxn];
    int top;
}stack;
 
//创建
stack *init(){
    stack *s=(stack *)malloc(sizeof(stack));
    if(s==NULL){
        printf("分配内存空间失败");
        exit(0);
    }
    memset(s->data,0,sizeof(s->data));
    //memset操作来自于库文件string.h,其表示将整个空间进行初始化
    //不理解可以查阅百度百科https://baike.baidu.com/item/memset/4747579?fr=aladdin
    s->top=0;     //栈的top和bottom均为0(表示为空)
    return s;
}
 
//入栈push
void push(stack *s,int data){
    s->data[s->top]=data;
    s->top++;
}
 
//出栈pop
void pop(stack *s){
    if(s->top!=0){
        s->data[s->top]=0;  //让其回归0模拟表示未初始化即可
        s->top--;
    }
}
 
//模拟打印栈中元素
void print_stack(stack *s){
    for(int n=s->top-1;n>=0;n--){
        printf("%d\t",s->data[n]);
    }
    printf("\n");   //习惯性换行
}
 
int main(){
    stack *s=init();
    int input[5]={11,22,33,44,55};  //模拟五个输入数据
    for(int i=0;i<5;i++){
        push(s,input[i]);
    }
    print_stack(s);
    /////////////
    pop(s);
    print_stack(s);
    return 0;
}

其编译结果如下:

栈链表

#include 
#include 
//栈的结点设计
//单个结点设计,数据和下一个指针
typedef struct node     
{

    int data; 
    struct node *next;
} Node;
//利用上面的结点创建栈,分为指向头结点的top指针和计数用的count
typedef struct stack    
{

    Node *top;
    int count;
} Link_Stack;
 
//创建栈
Link_Stack *Creat_stack()
{
    Link_Stack *p;
    //p = new Link_Stack;
    p=(Link_Stack*)malloc(sizeof(Link_Stack));
    if(p==NULL){
        printf("创建失败,即将退出程序");
        exit(0);
    }
 else
 {printf("创建成功\n");
 }
    p->count = 0;
    p->top = NULL;
    return p;
}
 
//入栈 push
Link_Stack *Push_stack(Link_Stack *p, int elem)
{
    if (p == NULL)
        return NULL;
    Node *temp;
    temp=(Node*)malloc(sizeof(Node));
    //temp = new Node;
    temp->data = elem;
    temp->next = p->top;
    p->top = temp;
    p->count++;
    return p;
}
 
//出栈 pop
Link_Stack *Pop_stack(Link_Stack *p)
{
    Node *temp;
    temp = p->top;
    if (p->top == NULL)
    {
        printf("错误:栈为空");
        return p;
    }
    else
    {
   printf("\npop success");
        p->top = p->top->next;
        free(temp);
        //delete temp;
        p->count--;
        return p;
    }
}
 
//遍历栈:输出栈中所有元素
int show_stack(Link_Stack *p)
{
    Node *temp;
    temp = p->top;
    if (p->top == NULL)
    {
        printf("");
        printf("错误:栈为空");
        return 0;
    }
    while (temp != NULL)
    {
        printf("%d\t", temp->data);
        temp = temp->next;
    }
    printf("\n");
    return 0;
}
 
int main()
//用主函数测试一下功能
 int i;
    Link_Stack *p;
    p = Creat_stack();
    int n = 5;
    int input[6] = {10,20,30,40,50,60};
    /////////////以依次入栈的方式创建整个栈//////////////
    for(i=0;i        Push_stack(p, input[i]);
    }
    show_stack(p);
    ////////////////////出栈///////////////////////
    Pop_stack(p);
    show_stack(p);
    return 0;
}

编译结果如下:

关于栈的总结

栈-它是一种运算受限的线性表,在数制转换,括号匹配的检验,表达式求值等方面都可以使用,并且较为简便的解决问题。

今天栈基础就讲到这里,下一期,我们再见!


推荐阅读:


    
             
嵌入式编程专辑
Linux 学习专辑
C/C++编程专辑

关注 微信公众号『技术让梦想更伟大』,后台回复“ m ”查看更多内容,回复“ 加群 ”加入技术交流群。

长按前往图中包含的公众号关注

免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
换一批
延伸阅读

当我们谈起C语言,很多人第一印象是面向底层、面向系统的编译型语言,写出来的程序一般都是从头到尾跑一遍就结束,很少和用户交互。但实际上,C语言从诞生开始就支持交互式的程序设计,通过标准输入输出和用户实时交互,接收用户输入、...

关键字: C语言 编程

在STM32嵌入式开发中,精确延时是非常基础但又极其关键的功能。无论是驱动单总线传感器(比如DS18B20)、控制LCD屏幕时序、还是生成精确的脉冲信号,都需要用到微秒级甚至纳秒级精度的延时。很多新手刚开始使用STM32...

关键字: STM32 嵌入式

在C语言开发中,位操作符是最容易被新手忽略,却能在嵌入式开发、底层驱动、算法优化中发挥巨大作用的工具。和常规的算术操作、逻辑操作相比,位操作直接操作二进制位,执行效率更高,占用代码空间更小,能轻松实现很多用常规方法很难实...

关键字: C语言 位操作符

在C语言开发中,原生字符串的使用一直存在诸多不便。传统C语言中,字符串本质是以'\0'结尾的固定字符数组,开发人员必须提前预估字符串的最大长度:如果预估过小,拼接或插入字符时会出现缓冲区溢出,引发内存越界错误;如果预估过...

关键字: C语言 字符串

随着半导体测试向更高复杂性与并行度演进,多工位自动测试设备(ATE)和SiC/GaN测试对电感、电容和电阻(LCR)测量的需求不断提升。然而,传统的外接台式LCR仪表和基于线缆的设置难以扩展,而且会降低可重复性。本文介绍...

关键字: 半导体 电阻 嵌入式

智能高尔夫球追踪系统是一项创新的嵌入式电子项目,旨在展示如何将紧凑型物联网硬件集成到体育科技应用中。在体育领域,高尔夫球扮演着主要角色,但在现代时代,所有设备都变得更加智能化,高尔夫球也由此演变为智能高尔夫球。本项目结合...

关键字: 嵌入式 物联网 NRF无线技术

在工业自动化、智能传感、嵌入式组网等分布式总线系统中,设备自动地址分配是实现节点互联互通、即插即用的核心技术。传统人工配置地址方式存在操作繁琐、扩展性差、地址冲突风险高、维护成本高等诸多问题,已无法适配大规模、动态化的总...

关键字: 总线 嵌入式 组网

2026年6月8日 – 专注于引入新品的全球电子元器件和工业自动化产品授权代理商贸泽电子 (Mouser Electronics) 正式宣布,首次荣获全球嵌入式应用安全连接解决方案知名供应商NXP® Semiconduc...

关键字: 物联网 移动设备 嵌入式

城市灯火通明、生活井然运转的背后,总有人在不被注意的地方,日复一日地坚持着。他们或许没有惊天动地的故事,却在漫长岁月里,用自己的方式守护着他人的生活。近日,乡村教师班爱花、爱心厨房运营者丫丫妈,以及“扛楼女工”云姐的故事...

关键字: 西门子家电 洗碗机 嵌入式

2026年5月15日,正值“世界无幽日”,一组数据再次引发公众关注:据《中国幽门螺杆菌感染防控》白皮书显示,我国幽门螺杆菌人群感染率已接近50%,涉及超过7亿人口,且家庭内传播特征极为显著——父母若感染,子女感染风险升高...

关键字: 洗碗机 AI 嵌入式
关闭