【数据结构】栈
栈是常见的数据结构之一,我将在这篇博客中简要介绍 栈、栈的使用、单调栈、栈和DFS、栈和递归。
栈#
栈(stack) 又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。
栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。
栈的使用#
- stl库实现
在C++中的stl库中存在已经封装好的栈已经栈相关的函数,我们可以直接使用。
头文件:#include <stack> 定义在std空间中: using namespace std; 创建栈: stack<数据类型> stk(自定义栈名); 栈的基本操作函数: stk.push(X);将元素X放入栈中 stk.pop();删除栈尾元素 stk.size();返回栈中元素的个数 stk.empty();如果栈为空返回true,不空返回false stk.top();访问栈顶元素
- 手写栈
手写栈比使用stl库的效率更高,更节约时间,所以一般在算法竞赛中会选择用数组模拟实现栈。
int stk[n];创建栈 int cnt = 0;指向栈顶的指针 stk[++cnt] = x //x入栈,++cnt是因为通常会空出下标为0的作为判断栈是否为空,其次++cnt使cnt所指是栈顶元素 cnt--; //弹出栈顶元素 cnt >0 //cnt > 0则栈不为空,cnt == 0则栈为空 //cnt的值表示栈里有多少个元素 stk[cnt];// 栈顶元素
好了,你已经会使用栈了()。
单调栈#
单调栈是栈的一个常见的使用方法。
顾名思义,单调栈是保证栈中元素时刻为单调序列的栈。
单调栈的实现:
新的元素入栈时从栈顶开始判断,如果不满足单调条件就弹出栈顶元素,循环直到满足单调条件或者栈为空,再将该元素入栈。
while(!stk.empty() && stk.top() < / > target) stk.pop(); stk.push(target) 涉及数组的问题时,通常我们是用栈存储数组下标,而不是数组元素。
单调栈的应用范围我知道的不全面,只能例举几道单调栈道题目。
503. 下一个更大元素 II
由题意可知,这道题使用的是一个单调减的栈。
弹出栈的元素的下一个更大元素就是正要入栈的元素。
我们只需要对[原数组 + 前n - 1个元素]的数组用单调栈遍历一边即可,最后留在栈中的便是没有下一个更大元素的元素对此,我们只需要初始化答案容器(c++里是vector,c里为数组)为-1即可。
class Solution { public: vector<int> nextGreaterElements(vector<int>& nums) { int n = nums.size(); vector<int> ans(n,-1);//初始化为-1 int stk[2*n - 1];//栈存储数组下标,便于填充答案数组 int cnt = 0; for(int i = 0; i < 2*n - 1; i++) { int tmp = i % n; while(cnt > 0 && nums[stk[cnt]] < nums[tmp]) { ans[stk[cnt]] = nums[tmp]; cnt--; } stk[++cnt] = tmp; } return ans; } };
402. 移掉 K 位数字
这道题是贪心+单调栈可以解决。
对数字143XXXX,删掉第一个数字时,显然14XXX > 13XXX,所以选择删掉4依次类推,原理是位数越高,影响越大。也就是贪心,我总是要去掉高位的数字。
char * removeKdigits(char * num, int k){ if(strlen(num) == k) return "0"; int len = strlen(num); char stk[100010]={}; int head = 0 , i = 0; while(i < len) { while(head > 0 && stk[head] > num[i] && k) { head--; k--; }//每弹出一个,去除一位数 stk[++head] = num[i]; i++; }//单调栈走完后,栈中留下去除一定位数的数据 head -= k;//由于可能还没有去除k位数字就已经结束了,由于栈中此时是单调增的,我们只需要从后往前去除剩下几位数 char* ans = (char*)malloc(sizeof(char)*(len+1)); int q = 1 , j = 0; while(q <= head && stk[q]=='0') q++;//除去可能存在的前导零 for(; q <= head; q++) { ans[j++] = stk[q]; }//复制处理后的数据 if(j == 0)//如果没有可以复制的数据,则答案为0,并添加结束的0 { ans[0] = '0'; ans[1] = 0; } else ans[j] = 0;//字符串末位添加0结束 return ans; }
栈与DFS#
由于栈先进后出的性质,通常会和DFS(深度优先搜索)练习在一起。
深度优先搜索可以用递归、迭代等方式实现,比较常见的是在二叉树的遍历中(不过二叉树遍历还有一个比较难的算法,问就是不会)。DFS,通俗讲就是一条路走到黑,撞到南墙才回头走另一条岔道。
这和栈先进后出的性质很符合,比如说
二叉树的前序遍历:对深度第二的一个节点 node -> node、node->left -> node -> node、node->right
我们访问到node ,下一步让左子节点进栈,经过条件判断后左子节点没有子节点了,左子节点出栈,右子节点进栈、出栈,然后node出栈,栈顶元素来到了node的父节点。
例题:
733. 图像渲染
由于我写的是BFS,就直接用了官方的代码
class Solution { public: string removeKdigits(string num, int k) { vector<char> stk; for (auto& digit: num) { while (stk.size() > 0 && stk.back() > digit && k) { stk.pop_back(); k -= 1; } stk.push_back(digit); } for (; k > 0; --k) { stk.pop_back(); } string ans = ""; bool isLeadingZero = true; for (auto& digit: stk) { if (isLeadingZero && digit == '0') { continue; } isLeadingZero = false; ans += digit; } return ans == "" ? "0" : ans; } }; 作者:力扣官方题解 链接:https://leetcode.cn/problems/remove-k-digits/solutions/484940/yi-diao-kwei-shu-zi-by-leetcode-solution/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
栈与递归#
由于c语言中调用函数时分配给函数的是内存中栈的空间。
因此,函数的递归调用,可以看作是一个栈。
第一层函数在栈底,随着不断递归调用,新的函数不断向栈顶添加,如果这时递归结束条件没有设置好,就会导致栈的内存溢出,程序异常终止。
用栈的方式理解函数的递归调用,或许会更好理解一点。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术