数据结构-----栈、顺序栈、链栈

news2024/10/7 18:31:29

        在软件应用中,栈这种后进先出数据结构的应用是非常普遍的。比如用浏览器上网时,不管什么浏览器都有一个“后退”键,你点击后可以按访问顺序的逆序加载浏览过的网页。即使从一个网页开始,连续点了几十个链接跳转,你点“后退”时,还是可以像历史倒退一样,回到之前浏览过的某个页面。在很多类似的软件,都有撤销的的操作,也是使用栈这种方式来实现的。

1、定义

栈是限定仅在表尾进行插入和删除操作的线性表。

特点:数据是先进后出、后进先出

2、结构及类型

2.1 栈的类型?

  1. 满减栈

  2. 满增栈

  3. 空减酸

  4. 空增栈

满栈,top指示的是,最后一次入栈的元素

空栈,不含任何元素的栈。top指示的是,新元素待插入的位置

增栈,随着元素的push,top地址变大

减栈,随着元素的push,top地址变小

2.2 结构

栈顶:允许插入和删除的一端

栈底:另一端

 2.3 说明

        栈又称为后进先出的线性表(LIFO结构),首先它是一个线性表,也就是说,栈元素具有线性关系,即前驱后继关系。只不过它是一种特殊的线性表而已。定义中说是在线性表的表尾进行插入和删除操作,这里表尾是指栈顶,而不是栈底。

        它的特殊之处就在于限制了这个线性表的插入和删除位置,它始终只在栈顶进行。这也就使得:栈底是固定的,最先进栈的只能在栈底。

栈的插入操作,叫作进栈,也称压栈、入栈。类似子弹入弹夹

栈的删除操作,叫作出栈,也有的叫作弹栈。如同弹夹中的子弹出夹

3、栈的抽象数据类型(ADT栈)

两个流程:入栈、出栈

两种常见的栈:顺序栈、链式栈

4、顺序栈

4.1 什么是顺序栈?

栈的顺序存储其实也是线性表顺序存储的简化,简称为顺序栈。可以简单理解为将数据存在一个数组中,只不过释放数据和存储数据有一些独特的功能。通常,下标为0的一端作为栈底比较好,因为首元素都存在栈底,变化最小。

4.2 图示理解顺序栈

在进行进栈和出栈时,栈顶会进行++或--处理,目的是为了调整栈的大小,++代表写入了一个节点,--代表出去了一个节点。但是栈顶++实际上指的是一个待插入的数据位置,栈顶表示插入最新的数据位置(大小)。同样的,栈顶--代表即将出去的一个节点的位置

4.3 执行代码

顺序栈这里我直接放代码,不分开说明,每个功能需要去理解一下。

typedef struct  person {
        char name[32];
        char sex;
        int age;
        int score;
}DATATYPE;
typedef struct list {
        DATATYPE *head;
        int tlen;
        int top;
}SeqStack;

SeqStack* CreateSeqStack(int len);
int DestroySeqStack(SeqStack* ss);
int PushSeqStack(SeqStack* ss, DATATYPE* data);
int PopSeqStack(SeqStack*ss);
DATATYPE* GetTopSeqStack(SeqStack* ss);
int IsEmptySeqStack(SeqStack* ss);
int IsFullSeqStack(SeqStack* ss);
int GetSizeSeqStack(SeqStack*ss);
// 创建一个顺序栈,并初始化
SeqStack* CreateSeqStack(int len)
{
    // 分配内存给SeqStack结构体
    SeqStack* ss = (SeqStack*)malloc(sizeof(SeqStack));
    if(NULL == ss)
    {
        perror("CreateSeqStack malloc 1"); // 打印错误信息
        return NULL;
    }

    // 分配内存给存储数据的数组
    ss->head = (DATATYPE*)malloc(sizeof(DATATYPE) * len);
    if(NULL == ss->head)
    {
        perror("CreateSeqStack malloc 2"); // 打印错误信息
        free(ss); // 释放已分配的SeqStack结构体内存
        return NULL;
    }
    
    ss->tlen = len; // 初始化栈的总长度
    ss->top = 0;    // 初始化栈顶指针
    return ss;      // 返回创建好的顺序栈指针
}

// 入栈操作
int PushSeqStack(SeqStack* ss, DATATYPE* data)
{
    // 判断栈是否已满
    if(IsFullSeqStack(ss))
    {
        return 1; // 栈已满,无法入栈
    }
    
    // 将数据复制到栈顶,并更新栈顶指针
    memcpy(&ss->head[ss->top++], data, sizeof(DATATYPE));
    return 0; // 入栈成功
}

// 出栈操作
int PopSeqStack(SeqStack* ss)
{
    // 判断栈是否为空
    if(IsEmptySeqStack(ss))
    {
        return 1; // 栈为空,无法出栈
    }
    
    // 出栈操作,更新栈顶指针
    ss->top--;
    return 0; // 出栈成功
}

// 判断栈是否已满
int IsFullSeqStack(SeqStack* ss)
{
    return ss->top == ss->tlen; // 当栈顶指针等于总长度时,表示栈已满
}

// 判断栈是否为空
int IsEmptySeqStack(SeqStack* ss)
{
    return 0 == ss->top; // 当栈顶指针为0时,表示栈为空
}

// 获取栈顶元素的指针
DATATYPE* GetTopSeqStack(SeqStack* ss)
{
    if(IsEmptySeqStack(ss)) // 如果栈为空,则返回NULL
    {
        return NULL;
    }
    // 返回栈顶元素的指针
    return &ss->head[ss->top - 1];
}

// 获取栈的大小(元素个数)
int GetSizeSeqStack(SeqStack* ss)
{
    return ss->top; // 栈的大小即为栈顶指针的值
}

// 销毁顺序栈,释放内存
void DestroySeqStack(SeqStack* ss)
{
    if(ss != NULL)
    {
        if(ss->head != NULL)
        {
            free(ss->head); // 释放存储数据的数组内存
        }
        free(ss); // 释放SeqStack结构体内存
    }
}

5、链栈

栈的链式存储结构,与顺序栈不同的是链栈是由一个个节点拼接而成,并非连续的数组。

5.1 用到的结构体

1.数据结构体

typedef struct {
    //公共数据
}DATATYPE;

这个结构体是链栈中存储的数据类型,即每个节点的数据部分。在这个结构体中可以定义链栈节点存储的具体数据内容。

2.节点结构体

typedef struct node{
    DATATYPE data;//数据体
    struct node *next;//指向下一个节点的指针
}LinkStackNode

LinkStackNode:这个结构体定义了链栈的节点类型。每个节点包含两部分信息:

  1. DATATYPE类型的变量,用于存储节点的数据;

  2. 指向下一个节点的指针。

解释struct node *next而非其他类型的指针:

struct node *nex表示指针指向的是另一个struct node类型的节点。这是因为链栈中每个节点都是由struct node定义的,因此指向下一个节点的指针应该是指向struct node类型的指针。

int *: 这表示指针指向的是一个整数。在链栈中,不是在每个节点中存储整数数据,而是在data字段中存储整数数据。

因此next指针应该指向另一个节点,而不是一个整数。

3.链栈结构体

typedef struct  {
    LinkStackNode* top;//指向链栈顶部节点的指针,通过这个指针可以访问链栈中的最顶部节点,也就是最后一个入栈的节点。
    int clen;          //栈当前的长度:其中包含的节点数量
}LinkStackList;

这个结构体定义了链栈本身的结构。

4.下面从图片解释上面结构体之间的关系

 

        因为栈是一个线性表,这里LinkStackList结构体用来确定栈的头在哪里,clen记录了这个栈所包含节点的个数,就是所谓的长度。LinkStackNode结构体确定每个节点的内容,其包括下一个节点的位置指针以及本节点的data数据,DATATYPE是存放数据的结构体。需要注意的是,无论是List中的*top还是节点Node*next,都是指向下一个整体节点的位置,而非节点中某个成员

5. 那么为什么要用三个结构体?

        首先 DATATYPE结构体是公共数据,用来存放每个节点所包含的数据,可以灵活定义节点中存储的数据内容。如果未来需要修改节点数据的结构或添加额外的数据字段,只需修改这个结构体,而不必改动其他部分。所以不能省略;(必要的)

  LinkStackNode结构体用来存放数据和指向下一个节点的指针,这样的设计使得节点本身具有了独立的意义,可以单独操作节点,而不必担心节点的具体数据类型或链栈的实现细节。(必要的)

  LinkStackList结构体的作用是可以明确找到栈的顶部指针和长度信息,如果将长度信息clen放在Node节点中,也可以实现知道具体的长度,如果想要知道clen,这就要求要在Node每个节点中++进行计算,使得计算流程更加复杂,并且每次想要获取数据的时候就要从头开始重新遍历一遍。而放在List结构体中,只要将最开始创建栈的时候将创建节点的个数存在clen中就可以了,将链栈的结构信息独立出来,更方便调用。(非必要但建议有)

5.2 执行代码

.h文件

typedef struct {
        char name[32];
        char sex;
        int age;
        int score;
}DATATYPE;

typedef struct stack_node {
        DATATYPE data;
        struct stack_node *next;
}LinkStackNode;

typedef struct  {

    LinkStackNode* top;
        int clen;
}LinkStackList;

LinkStackList* CreateLinkStack();
int DestroyLinkStackList(LinkStackList**ls);
int PushLinkStack(LinkStackList* ls, DATATYPE*data);
int PopLinkStack(LinkStackList*ls);
DATATYPE* GetTopLinkStack(LinkStackList*ls);
int IsEmtpyLinkStack(LinkStackList* ls);
int GetSizeLinkStack(LinkStackList*ls);

.c文件

#include "linkstack.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 创建一个新的链式栈并返回指针
LinkStackList* CreateLinkStack()
{
    // 分配内存空间给链式栈
    LinkStackList* ls = (LinkStackList*)malloc(sizeof(LinkStackList));
    if (NULL == ls)
    {
        perror("CreateLinkStack malloc");
        return NULL;
    }
    // 初始化链式栈的顶部指针为空,长度为0
    ls->top = NULL;
    ls->clen = 0;
    return ls;
}

// 销毁链式栈
int DestroyLinkStackList(LinkStackList** ls)
{
    // 获取链式栈的长度
    int len = GetSizeLinkStack(*ls);
    int i = 0;
    // 逐个弹出链式栈中的元素
    for (i = 0; i < len; i++)
    {
        PopLinkStack(*ls);
    }

    // 释放链式栈的内存空间
    free(*ls);
    *ls = NULL;
    return 0;
}

// 将数据压入链式栈
int PushLinkStack(LinkStackList* ls, DATATYPE* data)
{
    // 分配内存空间给新的节点
    LinkStackNode* newnode = (LinkStackNode*)malloc(sizeof(LinkStackNode));
    if (NULL == newnode)
    {
        perror("PushLinkStack malloc");
        return 1;
    }
    // 将数据拷贝到新节点中
    memcpy(&newnode->data, data, sizeof(DATATYPE));
    newnode->next = NULL;
    
    // 如果链式栈为空,直接将新节点设为顶部
    if (IsEmtpyLinkStack(ls))
    {
        ls->top = newnode;
    }
    else
    {
        // 否则,将新节点插入到链式栈顶部并更新顶部指针
        newnode->next = ls->top;
        ls->top = newnode;
    }
    // 链式栈长度加一
    ls->clen++;
    return 0;
}

// 弹出链式栈顶部的元素
int PopLinkStack(LinkStackList* ls)
{
    // 如果链式栈为空,返回1表示失败
    if (IsEmtpyLinkStack(ls))
    {
        return 1;
    }
    // 保存顶部节点的指针
    LinkStackNode* tmp = ls->top;
    // 更新顶部指针为下一个节点
    ls->top = ls->top->next;
    // 释放原顶部节点的内存空间
    free(tmp);
    // 链式栈长度减一
    ls->clen--;
    return 0;
}

// 获取链式栈顶部元素的指针
DATATYPE* GetTopLinkStack(LinkStackList* ls)
{
    // 如果链式栈为空,返回NULL
    if (IsEmtpyLinkStack(ls))
    {
        return NULL;
    }
    // 否则,返回顶部节点的数据指针
    return &ls->top->data;
}

// 判断链式栈是否为空
int IsEmtpyLinkStack(LinkStackList* ls)
{
    return 0 == ls->clen;
}

// 获取链式栈的长度
int GetSizeLinkStack(LinkStackList* ls)
{
    return ls->clen;
}
//调用
#include "linkstack.h"
#include <stdio.h>

int main(int argc, char *argv[])
{
     DATATYPE data[]={
        {"zhansan",'m',20,90},
        {"lisi",'f',22,87},
        {"wangmazi",'m',21,93},
        {"guanerge",'m',40,60},
        {"liuei",'m',42,83},
    };

    LinkStackList* ls = CreateLinkStack();

    int i = 0 ;
    for(i = 0 ;i<5;i++)
    {
        PushLinkStack(ls,&data[i]);
    }

    int len = GetSizeLinkStack(ls);
    for(i = 0 ;i<len;i++)
    {
    
        DATATYPE* tmp = GetTopLinkStack(ls);
        printf("%s %d\n",tmp->name ,tmp->score );

        PopLinkStack(ls);
    }

    DestroyLinkStackList(&ls);
    return 0;
}

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

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

相关文章

纳斯达克大屏媒体尺寸与投放费用:一次投放需要多少钱?

纳斯达克大屏媒体尺寸与投放费用&#xff1a;一次投放需要多少钱&#xff1f; 1. 纳斯达克图片要求 1.1 像素要求 高度&#xff1a;2336 像素宽度&#xff1a;1832 像素 1.2 分辨率要求 像素比率&#xff1a;1.0 px 72 dpi 1.3 文件格式要求 静态图片格式&#xff1a;.…

vue2 export default写法,computed、methods的使用

<template><div><h2>{{nameAll}}</h2><h2>{{method}}</h2><h2>{{tt()}}</h2><h2>{{firstName}}</h2><h2>更新后赋值数据&#xff1a;{{lastName}}</h2><h2>赋值数据:{{writeValue}}</h2>…

第十三届蓝桥杯省赛C++ A组 Java A组/研究生组《推导部分和》(C++)

【题目描述】 【输入格式】 【输出格式】 【数据范围】 【输入样例】 5 3 3 1 5 15 4 5 9 2 3 5 1 5 1 3 1 2 【输出样例】 15 6 UNKNOWN 【思路】 题解来源&#xff1a;AcWing 4651. $\Huge\color{gold}{推导部分和}$ - AcWing 【代码】 #include<bits/stdc.h> #define…

基于ssm的线上旅行信息管理系统论文

摘 要 随着旅游业的迅速发展&#xff0c;传统的旅行信息查询管理方式&#xff0c;已经无法满足用户需求&#xff0c;因此&#xff0c;结合计算机技术的优势和普及&#xff0c;特开发了本线上旅行信息管理系统。 本论文首先对线上旅行信息管理系统进行需求分析&#xff0c;从系…

git提交和回退

目录 一. git 提交二. git commit 后准备回退&#xff0c;尚未 git push三. git add 添加多余文件 撤销操作四. 更改 Git commit 的默认编辑器五. 撤销某个commit的变更六. 回退到之前的commit状态总结&#xff1a; 一. git 提交 git pull # 更新代码 git status # 查看代码状…

2G-3G-4G-5G 语音方案

1.2G、3G时代&#xff0c;语音业务采用CS&#xff08;Circuited Switched&#xff0c;电路交换&#xff09;技术&#xff0c;即手机在通话前需在网络中建立一条独占资源的线路&#xff0c;直到通话结束才拆除。这种古老的技术存在耗资源、组网复杂、效率低等缺点。 2. 进入4…

Codeup_1132:问题 A: 最长公共子序列

目录 Problem DescriptionInputOutputSample InputSample Output原题链接解题思路代码实现&#xff08;C&#xff09; Problem Description 给你一个序列X和另一个序列Z&#xff0c;当Z中的所有元素都在X中存在&#xff0c;并且在X中的下标顺序是严格递增的&#xff0c;那么就…

如何使用CHAT-AI?

伴随着CHAT-GPT的出现&#xff0c;人们都喜欢上了CHAT-AI。嗯&#xff1f;你还不会用&#xff1f;&#xff01; 教程来喽&#xff01; 首先点这里的 … 点击扩展 接着选择“管理扩展” 点击之后搜索“wetab” 最后你需要注册一个号&#xff0c;然后就可以使用CHAT-AI啦&#x…

《无名之辈》天涯镖局攻略:高效拉镖窍门!

《无名之辈》天涯镖局开启要注意什么&#xff0c;在这里&#xff0c;每一次运镖都是一次刺激的冒险&#xff0c;而掌握合适的策略将让你事半功倍。以下是天涯镖局的开启攻略&#xff0c;助你在危机四伏的路途上赢得胜利。 ① 拉取适当级别的包子和加速卡 在天涯镖局中&#xf…

SpringBoot中处理校验逻辑的两种方式:Hibernate Validator+全局异常处理

最近正在开发一个校园管理系统&#xff0c;需要对请求参数进行校验&#xff0c;比如说非空啊、长度限制啊等等&#xff0c;可选的解决方案有两种&#xff1a; 一种是用 Hibernate Validator 来处理一种是用全局异常来处理 两种方式&#xff0c;我们一一来实践体验一下。 一、…

Kimi 200万字爆火,通义加码1000万,阿里笑而不语

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 我怎么感觉Kimi是一个“网红”产品呢?在没有任何预兆情况下&#xff0c;国产AI大模型Kimi突然爆火&#xff0c;最近我在很多平台上看到了Kimi的广告&#xff0c;感觉到处都在吹这个产品。 看见上面的新闻了吧&a…

全网最详细的 Ubuntu 18.04 安装Livox mid-360驱动,测试 fast_lio2

目录 一、前言 二、依赖的环境 三、 安装Livox-SDK2&#xff0c;fast_lio2 和 livox_ros_driver2 (1) 安装Livox-SDK2 (2) 安装 fast_lio2 和 livox_ros_driver2 四、mid-360 设备硬件设置 五、运行设备 六、topic信息查看 一、前言 Livox mid-360需要使用Livox-SDK2…

推特社交机器人分类

机器人有不同的种类。 cresci-17数据集中的三种不同的机器人类:传统垃圾机器人、社交垃圾机器人和假追随者。 传统的垃圾邮件机器人会生成大量推广产品的内容&#xff0c;并且可以通过频繁使用的形容词来检测; 社交垃圾邮件倾向于攻击或支持政治候选人&#xff0c;因此情绪是一…

基于单片机防酒驾酒精检测报警系统装置设计

**单片机设计介绍&#xff0c;基于单片机防酒驾酒精检测报警系统装置设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机防酒驾酒精检测报警系统装置的设计旨在通过单片机技术和酒精传感器&#xff0c;实现对驾驶员酒…

Maya 2024 for Mac/Win:重塑三维创意世界的利器

在数字化浪潮汹涌的当下&#xff0c;三维图形软件早已成为创意产业不可或缺的重要工具。而在这其中&#xff0c;Maya 2024以其卓越的性能和丰富的功能&#xff0c;赢得了无数设计师的青睐。无论是Mac还是Win平台&#xff0c;Maya 2024都能为您的三维创作提供强大的支持。 Maya…

操作系统原理-模拟动态分区首次适应分配和回收算法——沐雨先生

一、实验题目&#xff1a; 模拟动态分区首次适应分配和回收算法 二、实验目的&#xff1a; 通过本实验&#xff0c;可加深理解动态分区分配、回收程序的功能和具体实现&#xff0c;特别是对回收分区的合并的理解。 三、实验环境&#xff1a; 1、硬件&#xff1a;PC机及其兼容…

【WiFi】WiFi QoS映射关系及抓包分析

WiFi Aliance认证测试对应图 RFC8325 ​​​​​​RFC 8325https://datatracker.ietf.org/doc/html/rfc8325 RFC 8325 – WiFi QoS Mappings | mrn-cciew (mrncciew.com)https://mrncciew.com/2021/09/14/rfc-8325-wifi-qos-mappings/ 802.11 UP和DSCP映射关系 802.11 UP …

万兆车载以太网转换器 10G/2.5G多速车载以太网转换器-MC10GM

MC10GM转换器 一、产品简要分析 2.5G,5G,10G可切换万兆/多速车载以太网转换器。采用罗森博格H-MTD标准接口类型。实现将车载以太网标准2.5/5/10G BASE-T1转换为工业级2.5/5/10G 标准以太网&#xff0c;进而接入电脑或工控机. 产品实现2.5/5/10G Base-T1 和2.5/5/10G Base-R之间…

ubuntu卸载Anaconda

1. 删除配置的环境变量 sudo gedit ~/.bashrc # >>> conda initialize >>> # !! Contents within this block are managed by conda init !! __conda_setup"$(/work3/ai_tool/anaconda3/bin/conda shell.bash hook 2> /dev/null)" if [ $? -…

CI860K01 3BSE032444R1 参数说明书

ABB CI860K01 3BSE032444R1是一款ABB公司生产的通信接口模块。 这款模块是专为工业自动化环境设计的&#xff0c;能够在各种设备之间提供稳定和可靠的数据传输接口。它采用了先进的通信技术和严格的生产工艺&#xff0c;确保了产品的高质量和性能。此外&#xff0c;它的设计合…