数据结构与算法:栈

朋友们大家好啊,在链表的讲解过后,我们本节内容来介绍一个特殊的线性表:栈,在讲解后也会以例题来加深对本节内容的理解

  • 栈的介绍
    • 栈进出栈的变化形式
  • 栈的顺序存储结构的有关操作
    • 栈的结构定义与初始化
    • 压栈操作
    • 出栈操作
    • 获取栈顶元素和有效元素个数
    • 判断是否为空和栈的销毁
  • 栈的链式存储结构的有关操作
    • 链表的创建
    • 链式栈的定义
    • 初始化
    • 压栈和出栈
    • 检查栈是否为空
  • 栈的应用--有效的扩号

栈的介绍

在应用软件中,栈的应用非常普遍,比如使用浏览器上网时,会有一个后退键,点击后可以按访问顺序的逆序加载浏览过的网页
在这里插入图片描述
很多类似的软件,比如word等文档或编辑软件都有撤销的操作,也是用栈的方式来实现的

栈是一种特殊的线性数据结构,仅支持在一个位置进行添加元素(称为“入栈”或“push”操作)和移除元素(称为“出栈”或“pop”操作)的操作。这个位置就是栈顶(Top)。由于栈是后进先出(LIFO, Last In First Out)的数据结构,最后一个添加到栈中的元素将是第一个被移除。

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

在这里插入图片描述
栈首先是一个线性表,说明栈元素具有线性关系,在定义中说在线性表的表尾进行插入和删除操作,这里的表尾是指栈顶

它的特殊之处就在于它的删除和插入始终只能在栈顶进行

栈进出栈的变化形式

首先提一个问题,最先进栈的元素,是不是一定最后出栈呢?
答案是不一定的,在不是所有元素都进栈的情况下,先进去的元素也可以出栈,保证是栈顶元素出栈就可以

举例,如果我们有1、2、3三个数字一次进栈,会有哪些出栈次序呢?

  • 第一种:1、2、3进,再3、2、1出,出栈次序为321
  • 第二种:1进,1出,2进,2出,3进,3出。进一个出一个,出栈次序为123
  • 第三种,1进,2进,2出,1出,3进,3出,出栈顺序为213
  • 第四种:1进,1出,2进,3进,3出,2出,出栈顺序为132
  • 第五种:1进,2进,2出,3进,3出,1出,出栈次序为231

栈的顺序存储结构的有关操作

对于栈来讲,线性表的操作特性它都具备,由于它的特殊性,特别是插入和删除操作,我们改名为push和pop

线性表是用数组来实现的,对于栈这一种只能一头插入的线性表来说,下表为0的一段作为栈底

栈的结构定义与初始化

typedef int STDataType;


typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}ST;

对栈进行初始化,构造initial函数

void StackInit(ST* ps)
{
	assert(ps);

	ps->a = NULL;
	ps->top = -1;
	ps->capacity = 0;

}

首先assert断言ps是否为空指针,将a指向NULL,capacity置为0,而top置为-1

在栈的实现中,top变量一般用来指示栈顶元素的位置。对于一个空栈来说,不存在任何元素,因此没有一个合理的位置可以被称为栈顶。在这种情况下,需要一个特殊的值来表示栈是空的
在进行入栈和出栈操作时,top的更新逻辑变得简单直接。例如,每当添加一个新元素到栈中时,先将top加1(这将把top从-1改为0,表示第一个元素的位置),然后在top对应的位置上存放新元素

在这里插入图片描述
保证top指向栈顶元素

压栈操作

void StackPush(ST* ps, STDataType x) {
    assert(ps != NULL); 
    // 检查栈是否已满
    if (ps->top + 1 == ps->capacity) {
        int newcapacity=ps->capacity==0?4:ps->capacity*2; 
        STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newcapacity);
        if (tmp == NULL) {
            perror("realloc fail");
            return;
        }
        ps->a = tmp;
        ps->capacity = newcapacity;
    }
     
    // 先将栈顶索引top增加1,然后在新的栈顶位置存入元素x
    ps->top++;
    ps->a[ps->top] = x;
}
  • 首先检查栈是否已满,即:if (ps->top + 1 == ps->capacity)。这是通过比较top + 1(即如果添加新元素后的栈顶索引)和capacity(栈的容量)来实现的。
  • 如果栈满,执行扩容操作。新的容量newcapacity为当前容量的两倍,但如果当前容量为0,则初始化容量为4。
  • 使用realloc尝试扩容
  • 栈顶索引top增加1,以便于在正确的位置添加新元素
  • 在新的栈顶位置存入元素x,即ps->a[ps->top] = x;

出栈操作

void StackPop(ST* ps) {
    assert(ps != NULL); 
    if (ps->top == -1) { 
        printf("栈已空,无法执行出栈操作。\n");
        return;
    }
    ps->top -= 1; 
}

两个操作没有涉及任何循环,时间复杂度均为O(1);

获取栈顶元素和有效元素个数

STDataType StackTop(ST* ps) {
    assert(ps != NULL);
    if (ps->top == -1) {
        printf("错误:试图从空栈中获取元素。\n");
        exit(EXIT_FAILURE); 
    }
    return ps->a[ps->top];
}
int StackSize(ST* ps) {
    assert(ps != NULL); 
    return ps->top + 1; 
}

判断是否为空和栈的销毁

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

在C语言中,当一个函数的返回类型被声明为bool(需要包含<stdbool.h>头文件),那么它只能返回两个值之一:true或false

  • true通常被定义为整数1。
  • false被定义为整数0。
    这意味着,当你看到一个函数的返回类型是bool,你可以期望该函数根据其执行的操作或检查的条件,返回表示“真”或者“假”的结果。这样的函数通常用于进行某种条件检测或确认某事是否成立。

这行代码核心地检查栈是否为空。在这里,ps->top是栈顶元素的索引。通常情况下,当栈为空时,栈顶索引top被设置为-1来表示栈内没有元素。如果ps->top等于-1,函数返回true,表示栈为空;否则返回false,表示栈中有元素。

void StackDestroy(ST* ps) {
    assert(ps != NULL); // 确保栈指针ps非空
    free(ps->a);        // 释放动态数组
    ps->a = NULL;       // 将指针设为NULL,防止悬挂指针
    ps->top = -1;       // 重置栈顶指标
    ps->capacity = 0;   // 重置栈容量
}

栈的链式存储结构的有关操作

讲完了栈的顺序存储,我们接着来看栈的链式存储

思考一下,栈只在栈顶进行删除和插入,那么栈顶是放在链表的头端还是尾端呢?

当使用链表实现链式栈时,通常选择链表的头部作为栈顶,因为这种方法更高效、实现也更简单:

  • 在链表头部插入或删除节点只需要O(1)的时间复杂度,因为这些操作不需要遍历整个链表。这对于栈操作(即push和pop操作)非常理想,因为它们也应该是O(1)的时间复杂度
  • 链表有头指针,栈有顶部指针,可以做到合二为一

链表的创建

typedef int STDataType;

typedef struct StackNode {
    STDataType data;                     
    struct StackNode* next;       
} StackNode;

链式栈的定义

typedef struct LinkedStack{
    StackNode* top;               
    int size;                     
} LinkedStack;

初始化

初始化一个空栈,只需要将栈顶指针设置为NULL,栈的大小设置为0

void Initialize(LinkedStack* stack) {
    stack->top = NULL;
    stack->size = 0;
}

压栈和出栈

void Push(LinkedStack* stack, STDataType x) {
    StackNode* newNode = (StackNode*)malloc(sizeof(StackNode));
    if (newNode == NULL) {
        printf("Memory allocation failed\n");
        return;
    }
    newNode->data = x ;
    newNode->next = stack->top;    // 新节点的下一个节点就是当前的栈顶
    stack->top = newNode;          // 更新栈顶为新节点
    stack->size++;
}

推入新元素需要创建一个新的节点,并将其插入到链表的头部。

int Pop(LinkedStack* stack) {
    if (stack->top == NULL) {      // 检查栈是否为空
        printf("Stack is empty\n");
        return -1;                 // 使用-1表示错误情况,实际使用中应考虑其他错误处理方式
    }
    StackNode* temp = stack->top;  // 临时保存栈顶节点
    int data = temp->data;         // 获取栈顶数据
    stack->top = temp->next;       // 更新栈顶指针为下一个节点
    free(temp);                    // 释放原栈顶节点的内存
    stack->size--;
    return data;                   // 返回栈顶数据
}

弹出栈顶元素先要检查栈是否为空。如果不为空,将栈顶节点从链表中移除,并释放它所占用的内存。

检查栈是否为空

检查链式栈是否为空也很简单,只需检查栈顶指针是否为NULL。

int IsEmpty(LinkedStack* stack) {
    return stack->top == NULL;
}

栈的应用–有效的扩号

给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  • 左括号必须用相同类型的右括号闭合。
  • 左括号必须以正确的顺序闭合。
  • 每个右括号都有一个对应的相同类型的左括号。

这个问题可以通过使用栈来轻松解决。基本思想是遍历字符串中的每个字符,对于每个开放括号((, {, [),我们将其推入栈中。对于每个关闭括号(), }, ]),我们检查它是否与栈顶的开放括号匹配如果匹配,则弹出栈顶元素并继续处理字符串的下一个字符。如果在任何时候遇到不匹配的情况,或者在遍历完字符串后栈不为空,则字符串不是有效的

typedef char STDataType;


typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}ST; 

void StackInit(ST* ps)
{
	assert(ps);

	ps->a = NULL;
	ps->top = -1;
	ps->capacity = 0;

}

void StackPush(ST* ps, STDataType x) {
    assert(ps != NULL); 
    if (ps->top + 1 == ps->capacity) {
        int newcapacity=ps->capacity==0?4:ps->capacity*2;
        STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newcapacity);
        if (tmp == NULL) {
            perror("realloc fail");
            return;
        }
        ps->a = tmp;
        ps->capacity = newcapacity;
    }
    ps->top += 1;
    ps->a[ps->top] = x;
}
void StackPop(ST* ps) {
    assert(ps != NULL); 
    if (ps->top == -1) {
        printf("栈已空,无法执行出栈操作。\n");
        return;
    }
    ps->top -= 1; 
}
STDataType StackTop(ST* ps) {
    assert(ps != NULL); 
    if (ps->top == -1) {
        printf("错误:试图从空栈中获取元素。\n");
        exit(EXIT_FAILURE); 
    }
    return ps->a[ps->top];
}
int StackSize(ST* ps) {
    assert(ps != NULL); 
    return ps->top + 1; 
}
bool StackEmpty(ST* ps) {
    assert(ps != NULL); 
    return ps->top == -1; 
}
void StackDestroy(ST* ps) {
    assert(ps != NULL); 
    free(ps->a);        
    ps->a = NULL;       
    ps->top = -1;       
    ps->capacity = 0;  
}

我们首先列出准备好的函数,这里的数据类型为字符类型,只需要将typedef int STDataType;改为typedef char STDataType;

bool isValid(char* s) 
{
    ST sa;
    StackInit(&sa);
    while(*s)
    {
        if(*s=='['||*s=='{'||*s=='(')
        {
            StackPush(&sa,*s);
        }
        else
        {
            if(StackEmpty(&sa))return false;
            char top=StackTop(&sa);
            StackPop(&sa);

            if(*s==']'&& top!='['||*s=='}'&&top!='{'||*s==')'&&top!='(')
            {
                 return false;
            }

        }
        ++s;
    }

    bool ret =StackEmpty(&sa);
    StackDestroy(&sa);
    return ret;
}

使用while(*s)循环遍历字符串s中的每个字符。对于每个字符有两种情况:

  • 左括号([, {, ():如果字符是左括号之一,使用StackPush(&sa,*s);将其推入栈中。
  • 右括号(], }, )):如果字符是右括号,首先检查栈是否为空,如果空,则立即返回false,表示没有对应的左括号与当前右括号匹配。如果栈不为空,则获取栈顶元素top=StackTop(&sa);并使用StackPop(&sa);将其从栈中弹出。然后检查栈顶元素是否与当前的右括号匹配,如果不匹配,则返回false
  • 结束条件:遍历结束后,使用bool ret =StackEmpty(&sa);检查栈是否为空。如果栈为空,意味着所有的左括号都已被正确匹配,返回true;否则,返回false。最后,StackDestroy(&sa);销毁栈以释放可能分配的资源

本节内存到此结束!感谢大家的阅读!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Fluter学习3 - Dart 空安全

Dart 空安全&#xff1a; 空类型操作符 (?)空值合并操作符 (??)空值断言操作符 (!)延迟初始化 (late) 1、空类型操作符 (?) 当你想要根据一个表达式是否为 null 来执行某个操作时&#xff0c;你可以使用 (?)语法&#xff1a;expression1?.expression2如果 expression1…

关于开放系统互联的一些笔记

最近几天就发几篇计算机方面的基础知识 属于个人归纳整理&#xff0c;便于理解希望对大家有帮助 原文地址&#xff1a;关于开放系统互联的一些笔记 - Pleasure的博客 下面是正文内容&#xff1a; 前言 最近在恶补一些计算机方面的基础知识…… 正文 首先为了能够更透彻的理…

c语言结构体与共用体

前面我们介绍了基本的数据类型 在c语言中 有一种特殊的数据类型 由程序员来定义类型 目录 一结构体 1.1概述 1.2定义结构体 1.3 结构体变量的初始化 1.4 访问结构体的成员 1.5结构体作为函数的参数 1.6指向结构的指针 1.7结构体大小的计算 二共用体 2.1概述 2.2 访…

智慧安防/视频监控汇聚平台EasyCVR如何通过接口调用获取设备录像回看的流地址?

视频云存储/视频融合/安防监控EasyCVR视频汇聚系统可兼容各品牌的IPC、NVR、移动单兵、智能手持终端、移动执法仪、无人机、布控球等设备的接入&#xff0c;支持的接入协议包括&#xff1a;国标GB28181、RTSP/Onvif、RTMP&#xff0c;以及厂家的私有协议与SDK&#xff0c;如&am…

探索在GIS中使用ChatGPT

在创建了一个简单的点击询问 ChatGPT GIS 应用程序之后&#xff0c;我一直在努力想出关于如何在 GIS 应用程序中使用 ChatGPT 和 OpenAI 的更好的主意。后来想到只需要要问问 ChatGPT “如何使用它”&#xff0c;下面是对其中的几个实例。 简单的点击询问应用程序 地理输入和输…

最大划水收益

解法&#xff1a; 双指针、贪心 #include <iostream> #include <vector> using namespace std; #define endl \nint main() {ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);int n, k;cin >> n >> k;vector<int> vec(n, 0);for (int i …

你所在的行业,有必要做小程序么?

引言 在当今数字化飞速发展的时代&#xff0c;企业和行业正面临着不断变化的市场环境。随着移动互联网的崛起&#xff0c;小程序作为一种轻量级、便捷的应用形式&#xff0c;逐渐成为各行各业提升服务效率、拓展市场份额的重要工具。对于你所在的行业&#xff0c;究竟是否有必…

【深度优先搜索】【树】【状态压缩】2791. 树中可以形成回文的路径数

作者推荐 【深度优先搜索】【树】【有向图】【推荐】685. 冗余连接 II 本文涉及知识点 深度优先搜索 树 图论 状态压缩 LeetCode:2791. 树中可以形成回文的路径数 给你一棵 树&#xff08;即&#xff0c;一个连通、无向且无环的图&#xff09;&#xff0c;根 节点为 0 &am…

Javascript怎么输出内容?两种常见方式以及控制台介绍

javascript是一种非常重要的编程语言&#xff0c;在许多网页中它被广泛使用&#xff0c;可以实现许多交互效果和动态效果。输出是javascript中最基本的操作之一&#xff0c;下面将介绍两种常见的输出方式。 一、使用console.log()函数输出 console.log()函数是常用的输出函数…

【牛客面试必刷TOP101】Day24.BM34 判断是不是二叉搜索树和BM35 判断是不是完全二叉树

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;牛客面试必刷TOP101 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01;&…

3d模型导出fbx面出错是什么原因?怎么解决---模大狮模型网

3D模型在导出为FBX格式时出错可能有多种原因&#xff0c;以下是一些常见的原因和解决方法&#xff1a; 一&#xff1a;模型包含不支持的元素 某些特定的元素或功能在导出为FBX格式时可能会导致错误。例如&#xff0c;某些软件中使用了特殊的插件、约束或动画效果&#xff0c;这…

【学习笔记】数据结构与算法04:哈希表、哈希冲突、哈希算法

知识出处&#xff1a;Hello算法&#xff1a;https://www.hello-algo.com/ 文章目录 2.3 哈希表2.3.1 哈希表「 hash table」2.3.1.1 哈希表常见操作2.3.1.2 哈希表的简单实现2.3.1.3 哈希冲突与扩容 2.3.2 哈希冲突2.3.2.1 「链式地址 separate chaining」2.3.2.2 「开放寻址 o…

数字革命的先锋:探索Web3的无限可能性

随着科技的不断进步&#xff0c;我们正在迎来数字革命的新时代。在这个时代中&#xff0c;Web3技术作为数字革命的先锋&#xff0c;正以其独特的特点和无限的可能性引领着未来的发展方向。本文将深入探索Web3技术的核心原理、应用场景以及对未来的影响&#xff0c;揭示数字革命…

32看门狗

目录 一.看门狗简介 二&#xff0e;代码实现 一.看门狗简介 IWDG的专用时钟是LSI&#xff0c;内部低速时钟 WWDG使用的是APB&#xff11;的时钟&#xff0c;并没有专门的时钟&#xff0c;所以并不独立 如果独立看门狗已经由硬件选项或软件启动&#xff0c; LSI振荡器将被强制…

Aster实现一台电脑当两台使——副屏搭配键鼠

前言&#xff1a;笔者每年回家&#xff0c;都面临着想要和小伙伴一起玩游戏&#xff0c;但小伙伴没有电脑/只有低配电脑的问题。与此同时&#xff0c;笔者自身的电脑是高配置的电脑&#xff0c;因此笔者想到&#xff0c;能否在自己的电脑上运行游戏&#xff0c;在小伙伴的电脑上…

C++从入门到精通 第十二章(C++流)

一、C流的概念 1、C流的体系结构 &#xff08;1&#xff09;C为实现数据的输入输出定义了一系列的流类&#xff0c;这些类之间的派生、继承关系如下图所示&#xff0c;它们之中一部分是用模板实现的&#xff0c;图中用细线框表示&#xff0c;另外图中的虚线表示模板类与模板实…

计算机二级C语言的注意事项及相应真题-5-程序修改

目录 41.累加链表结点数据域中的数据作为函数值返回42.根据整型形参m&#xff0c;计算如下公式的值43.删除数列中值为x的元素44.从N个字符串中找出最长的那个串&#xff0c;并将其地址作为函数值返回45.将两个长度相等的纯数字字符串当作两个加数&#xff0c;求其代表的数值之和…

【Docker】Linux主机部署Docker

Docker部署 1.二进制文件部署 到如下地址&#xff0c;下载二进制包。 Docker官网&#xff1a;https://docs.docker.com/engine/install/binaries/ 网易镜像源&#xff1a;https://mirrors.163.com/docker-ce/linux/static/stable/x86_64/ 下载好的二进制包上传到主机&#xf…

C++项目 -- 高并发内存池(七)性能瓶颈与优化

C项目 – 高并发内存池&#xff08;七&#xff09;性能瓶颈与优化 文章目录 C项目 -- 高并发内存池&#xff08;七&#xff09;性能瓶颈与优化一、检测性能瓶颈二、使用基数树来优化项目1.基数树2.不加锁的原理3.性能对比 三、最终代码实现 一、检测性能瓶颈 DeBug下运行代码&…

015—pandas 标记按月连续变化趋势

前言 在业务数据分析中&#xff0c;特别是和时间相关的数据&#xff0c;会经常要判断数据的变化情况&#xff0c;比如是否是增长还是降低&#xff0c;或是持平。 需求 以数据中最后的月份为基础&#xff0c;来看它最近的数据变化&#xff0c;并将变化情况标记在本行的最后一…