【数据结构指南】栈

【数据结构指南】栈

前言:

        厨房里叠放的盘子想必大家都见过,新洗好的只能放在最顶层,使用时也必须从最上层开始取。羽毛球桶里的羽毛球也是如此,用过的球只能放回顶层,取用时也必须从最上层拿取。这些日常场景里,其实都藏着一种重要的数据结构:栈。

        关于栈的一个笑话:从前有一家客栈,客栈有一条规矩:最早进来的人最晚上菜,最晚进来的人最早上菜。有一天,一个人早早来到客栈吃饭,发现一个比他来的还晚的人已经吃完饭走了,而店主还没上自己的菜。吃完饭后他问店主这是为什么,店主回答:“因为我们是客栈啊。

本文系统讲解栈这一数据结构,从基础概念到实际应用进行全面剖析。通过清晰的逻辑和丰富的实例,即使是数据结构初学者也能轻松掌握核心要点。

一、栈的基本概念

        

1.1栈的定义

        栈  (stack) 是一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作,其核心特性遵循后进先出原则,这种特性可以形象地类比为叠放盘子的过程:最晚放入的盘子  (后进)  会被最先取出  (先出)  ,而最早放入的盘子  (先进)  则最后才能取出  (后出)  。

                

1.2栈的基础知识     

        为了方便理解,我们先明确栈的 3 个核心概念:​

栈顶(Top):位于栈的最上层,即最后入栈的元素。所有入栈(push)和出栈(pop)操作都只能在栈顶进行。

        

栈底(Bottom):位于栈的最底层,即第一个入栈的元素。这个元素在栈中位置固定,不会轻易变动。

        

空栈(empty):没有任何元素的栈状态。此时无法执行出栈操作。

        

1.3进栈出栈的示意图

        

进栈(Push):将元素添加到栈顶

出栈(Pop):移除并返回栈顶元素

        

二、栈的实现

        

2.1栈实现的相关接口

      本文通过数组的方式实现栈 ,实现的相关接口如图所示:

        

2.2栈实现的相关文件

        

            

2.3Stack.h文件

        Stack.h头文件实现:通过结构体定义栈,声明栈的相关接口函数。

#pragma once #include<stdlib.h> #include<stdio.h> #include<stdbool.h> #include<assert.h> typedef int STDatatype; typedef struct Stack { STDatatype* a; //top表示指向栈顶元素的下一个元素下标 int top; //栈的容量大小 int capacity; }Stack; //初始化和销毁栈 void StackInit(Stack* pst); void StackDestroy(Stack* pst); //元素的入栈和出栈 void Push(Stack* pst,STDatatype x); void Pop(Stack* pst); //获取栈顶元素 STDatatype top(Stack* pst); //获取栈中有效元素个数 int Size(Stack* pst); //判断栈是否为空 bool Empty(Stack* pst);

        

对于Stack.h头文件主要关注:

typedef int STDatatype; typedef struct Stack { STDatatype* a; //top表示指向栈顶元素的下一个元素下标 int top; //栈的容量大小 int capacity; }Stack;
typedef int STDatatype;

        
把 int 重命名为 STDatatype,方便后续修改栈存储的元素类型(比如想存 char 或结构体,只需改这一行)。

        
struct Stack

        

结构体:定义栈的核心结构。

        
STDatatype* a; 

        

动态数组指针,指向栈的内存空间(存储栈的元素)。

        
int top; 

        

栈顶标记(注释说明 “指向栈顶元素的下一个元素下标”)。例如:栈空时 top=0;入栈一个元素后,top=1(此时栈顶元素是 a[0])。

        
int capacity;

        

栈的容量(当前分配的内存能存多少个元素),用于判断是否需要扩容。     
   

        

2.4Stack.c文件

①栈的初始化

void StackInit(Stack* pst) { //断言pst assert(pst); pst->a = NULL; pst->capacity = 0; //指向栈顶元素的下一个元素 pst->top = 0; }

        

②栈的销毁

//销毁栈 void StackDestroy(Stack* pst) { assert(pst); if (pst->a != NULL) { free(pst->a); pst->a = NULL; } pst->capacity = 0; pst->top = 0; } 

        

③入栈

//元素的入栈 void Push(Stack* pst, STDatatype x) { assert(pst); //判断是否容量足够 if (pst->capacity == pst->top) { //需要进行扩容 int newcapacity =pst->capacity==0? 4 : pst->capacity * 2; STDatatype* tmp = (STDatatype *)realloc(pst->a, newcapacity*sizeof(STDatatype)); if (tmp == NULL) { perror("realloc fail"); return; } pst->a = tmp; pst->capacity = newcapacity; } //进行入栈 pst->a[pst->top++] = x; }

         

④出栈

//元素的出栈 void Pop(Stack* pst) { assert(pst && pst->top > 0); pst->top--; }

      

⑤获取栈顶元素

//获取栈顶元素 STDatatype top(Stack* pst) { assert(pst && pst->top > 0); return pst->a[pst->top-1]; }

        

⑥获取栈中的有效个数

//获取栈中有效元素个数 int Size(Stack* pst) { assert(pst); return pst->top; } 

      

⑦判断栈是否为空

//判断栈是否为空 bool Empty(Stack* pst) { assert(pst); return pst->top == 0; }

        

2.5Test.c文件

#include"Stack.h" void Test() { Stack st = { 0 }; StackInit(&st); Push(&st, 1); printf("%d\n", top(&st)); Push(&st, 2); printf("%d\n", top(&st)); Push(&st, 3); printf("%d\n", top(&st)); Push(&st, 4); printf("%d\n", top(&st)); printf("当前栈中元素的总个数为:%d\n", Size(&st)); Pop(&st); printf("%d\n", top(&st)); Pop(&st); printf("%d\n", top(&st)); Pop(&st); printf("%d\n", top(&st)); Pop(&st); if (Empty(&st)) printf("当前栈为空\n"); else printf("当前栈不为空\n"); printf("当前栈中元素的总个数为:%d\n", Size(&st)); StackDestroy(&st); } int main() { Test(); return 0; }

        

三、栈的简单应用

        

Leetcode链接:有效的括号  

        

3.1题目简介:

        

3.2题目分析

        

当开始接触题目时,我们会不禁想到如果计算出左括号的数量,和右括号的数量,如果每种括号左右数量相同,会不会就是有效的括号了呢?

        

但是左括号和右括号是否匹配还和它的位置相关,例如 " ]][[ " 这样一组案例即使左右括号数量相同,但是位置不满足题目条件。

        

我们通过仔细分析这样一组测试样例:{ ( ) [ ( ) ] }  ,对于有效的括号,它的部分子表达式仍然是有效的括号。




        

故而可以使用栈来解决,对于左括号压入栈中,通过匹配右括号的方式,删除最小的括号对。

        

       

        

3.3代码示例

class Solution { public: bool isValid(string s) { //创建一个栈结构 stack<char> st; for(int i=0;i<s.length();i++) { //如果是左括号( { [ 进入栈中 if(s[i]=='('|| s[i]=='{' || s[i]=='[') { st.push(s[i]); } else //右括号 { //取出左括号的前提是栈中至少有左括号 if(st.empty()) { return false; } //取出栈顶元素 char ch=st.top(); st.pop(); //栈顶元素的左括号 与 右括号进行匹配 if( (ch=='('&&s[i]!=')') || (ch=='{'&&s[i] !='}') || (ch=='['&&s[i] !=']') ) { return false; } } } //如果遍历完字符串,栈中还有元素,说明左右括号数量不匹配 if(!st.empty()) return false; return true; } };

        

既然看到这里了,不妨点赞+收藏,感谢大家,若有问题请指正。

Read more

LeetCode 42接雨水全解:暴力超时→DP降维打击→双指针极限压缩空间→单调栈栈式凹槽定位,全景式解析算法优化路径

LeetCode 42接雨水全解:暴力超时→DP降维打击→双指针极限压缩空间→单调栈栈式凹槽定位,全景式解析算法优化路径

文章目录 * 本篇摘要 * LeetCode 42 接雨水 详解 * ① 暴力解法(多循环嵌套,卡超时,因此后续使用了两种基于暴力优化的方法) * ② 动态规划解法 * 核心思想 * 步骤(三步走) * 举例说明 * 代码实现思路 * ③ 双指针解法(优化对应的dp的空间复杂度变成O(1)) * 双指针优化思路 * ④单调栈解法 * 单调栈简介 * 核心特点 * 常见用途 * 左边最近比当前数大的数(用单调栈) * 步骤: * 示例: * 最终结果: * 单调栈一般模版 * 关键点 * 注意点 * 单调栈不同选型需求 * 优势 * 引入单调栈 * 本篇小结 本篇摘要 本篇围绕LeetCode 42“接雨水”展开,剖析四种解法:暴力法通过嵌套循环统计每柱接水量,易超时;动态规划预先记录左右最大值,将复杂度降至O(n);双指针边遍历边更新极值,空间优化至O(1

By Ne0inhk
❿⁄₁₁ ⟦ OSCP ⬖ 研记 ⟧ 密码攻击实践 ➱ NTLM哈希传递攻击

❿⁄₁₁ ⟦ OSCP ⬖ 研记 ⟧ 密码攻击实践 ➱ NTLM哈希传递攻击

郑重声明:本文所涉安全技术仅限用于合法研究与学习目的,严禁任何形式的非法利用。因不当使用所导致的一切法律与经济责任,本人概不负责。任何形式的转载均须明确标注原文出处,且不得用于商业目的。 🔋 点赞 | 能量注入 ❤️ 关注 | 信号锁定 🔔 收藏 | 数据归档 ⭐️ 评论 | 保持连接💬 🌌 立即前往 👉晖度丨安全视界🚀 ▶ 信息收集  ▶ 漏洞检测 ▶ 初始立足点  ▶ 权限提升 ▶ 横向移动 ➢ 密码攻击 ➢ NTLM哈希传递攻击🔥🔥🔥 ▶ 报告/分析 ▶ 教训/修复 目录 1.密码破解 1.1 破解Windows哈希实践 1.1.1 NTLM哈希传递攻击概述 1.1.1.1 什么是NTLM哈希传递? 1.1.1.2 攻击应用场景 1.1.

By Ne0inhk

【数学建模】(LeetCode 1227)小鸟回笼/飞机座位问题

题目 有 nnn 只小鸟,各有自己的笼子(编号 1,2,⋯ ,n1, 2, \cdots, n1,2,⋯,n)。第一天,第一只小鸟(编号 1)没有回到自己的笼子(笼 1),而是随机进了其它某个笼子。后续的小鸟每天回来时,如果自己的笼子空着就进自己的笼子,否则从剩下的空笼子中随机选一个。 问:最后一只回笼的小鸟回到自己笼子的概率是多少? 这个问题和经典的飞机座位问题等价(见下),但需要注意的时初始条件不同,下面的问题第一个人位置也是随机的(可能回到自己的位置),而上面小鸟回笼问题则是在没有回到自己的笼子情况下。 有nnn位乘客即将登机,飞机正好有nnn个座位。第一位乘客的票丢了,他随便选了一个座位坐下。剩下的乘客将会:如果他们自己的座位还空着,就坐到自己的座位上;当他们自己的座位被占用时,随机选择其他座位,问: 第nnn位乘客坐在自己的座位上的概率是多少? 解答 **解:**设当所有鸟回到笼子后鸟和笼子编号的映射为 f(n)=m

By Ne0inhk
【优选算法必刷100题】第031~32题(前缀和算法):连续数组、矩阵区域和

【优选算法必刷100题】第031~32题(前缀和算法):连续数组、矩阵区域和

🔥艾莉丝努力练剑:个人主页 ❄专栏传送门:《C语言》、《数据结构与算法》、C/C++干货分享&学习过程记录、Linux操作系统编程详解、笔试/面试常见算法:从基础到进阶 ⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平 🎬艾莉丝的简介: 🎬艾莉丝的算法专栏简介: 目录 031  连续数组 1.1  解法一:暴力解法 1.2  解法二:前缀和在哈希表中 1.3  算法实现 1.3.1  C++实现 1.3.2  Java实现 1.4  博主手记 032  矩阵区域和 2.1

By Ne0inhk