「 堆栈 」一文读懂

电玩女神 2024-04-19 11:50 221阅读 0赞

一、「 堆栈 」是什么?

堆栈(stack)是一种先进后出的、操作受限的线性表,也可以直接称为

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NpbmF0XzI2ODExMzc3_size_16_color_FFFFFF_t_70

可以把栈想象成一个桶一样,往这个桶里面一层一层的放东西,先放进去的在里面,后放进去的东西依次在外面。但取东西的时候就是先取靠近外面的,再依次一层层取里面的。这就是 后进先出( Last In-First Out )的原则。

因此「 栈 」虽然是线性的,有2个端:顶端和底端,但它只允许从一端进行插入和删除数据,这就是为啥前面说「 栈 」是操作受限的了。

只有两种操作:Push 和 Pop 。我们用Push(压入)来表示往栈中插入数据,也叫入栈,用Pop(弹出)来表示从栈中删除数据,也叫出栈。我们可以既可以用 「 数组 」 来实现一个栈,也可以用 「 链表 」 来实现一个栈。

  • 用数组实现的栈,叫做 顺序栈

    顺序栈的实现非常简单,这里就不写代码了,写一下思路。先初始化一个数组,然后再用一个变量给这个数组里的元素进行计数,当有新元素需要入栈的时候,将这个新元素写入到数组的最后一个元素的后面,然后计数器加一。当需要做出栈操作时,将数组中最后一个元素返回,计数器减一。

    当然在入栈前需要判断数组是否已经满了,如果数组大小等于计数器大小,则表明数组是满的。

    出栈的时候也需要判断数组是不是空数组,如果计数器是0,则表明数组是空的。

    从上面的实现流程可以看出,通过数组实现的栈,其入栈和出栈都是对单个元素进行操作,因此其入栈和出栈的时间复杂度都是O(1),并且其入栈和出栈操作并没有额外开销更多空间,因此其空间复杂度也是O(1)的。

  • 用链表实现的栈,叫做 链式栈

    实现思路是先定义一个链表节点的类,基于这个类去定义一个头节点Head。当有新元素需要入栈的时候,将这个新元素的Next指针指向头结点Head的Next节点,然后再将Head的Next指向这个新节点。当需要做出栈操作时,直接将Head所指向的节点返回,同时让Head指向下一个节点。

    当然,在入栈和出栈时都需要判断链表是否为空的情况。

    链式栈的入栈和出栈都是在处理头部节点,所以操作很简单,其时间和空间复杂度均为O(1)。

二、「 堆栈 」的算法实践

我们来看一个基于用 来完成的 算法题(来源leetcode)

算法题:给定一个只包括 ‘(‘,’)’,’{‘,’}‘,’[‘,’]‘ 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。

举例:字符串 “()”有效、”()[]{}“有效、”(]“无效、”([)]“无效、”{[]}“有效。

解题思路:
使用1个堆栈即可解决,依次遍历这个字符串,如果遇到是左括号就入栈到堆栈中,如果遇到的是右括号,则从堆栈中取出栈顶的第一个左括号,比对一下这个左括号和当前遇到的右括号是否匹配,如果不匹配这认为这整个字符串无效。如果能匹配,则OK,删除这个左括号和右括号,继续往后走,继续遍历字符串中剩下的字符,只要遇到左括号就入栈,只要遇到右括号就与将栈顶的左括号出栈与之比较。一直走到字符串结束,再来检查堆栈中是否还有元素,如果还有元素,则这个字符串同样无效,如果堆栈为空,则字符串有效。

就以这个思路实现一个初版代码,这个代码的时间复杂度o(n),空间复杂度o(n)搞定:

  1. class Solution {
  2. public boolean isValid(String s) {
  3. Stack<Character> satck = new Stack<Character>();
  4. for(int i=0; i<s.length();i++){
  5. char c = s.charAt(i);
  6. if(c=='(' || c=='{' || c=='['){
  7. satck.push(c);
  8. }else{
  9. if(satck.isEmpty()) return false;
  10. char temp = satck.pop();
  11. if( (temp=='('&&c==')') || (temp=='{'&&c=='}') || (temp=='['&&c==']') ){
  12. continue;
  13. }else{
  14. return false;
  15. }
  16. }
  17. }
  18. return satck.isEmpty();
  19. }
  20. }

但是想了想,好像代码不是很优雅,写了一个优化版,提前将左右括号放入到MAP中,这个方法的时间和空间复杂度跟上面的一样:

  1. class Solution {
  2. public boolean isValid(String s) {
  3. Stack<Character> stack = new Stack<Character>();
  4. HashMap<Character,Character> map = new HashMap<Character,Character>();
  5. map.put('(', ')');
  6. map.put('{','}' );
  7. map.put('[', ']');
  8. for(int i=0;i<s.length();i++){
  9. char c = s.charAt(i);
  10. if(map.containsKey(c)){
  11. stack.push(c);
  12. }else{
  13. if(stack.isEmpty()) return false;
  14. char temp = stack.pop();
  15. if(map.get(temp)!=c) return false;
  16. }
  17. }
  18. return stack.isEmpty();
  19. }
  20. }

继续思考有没有更简洁的方法,竟然在leetcode上找到了一个。
但是这个方法并没有用到堆栈哦,它的思路是不断的遍历这个字符串,将字符串中的(){}[]全部调换成空字符串,如果最后全部替换完成了,并且字符串为空了,就说明字符串是有效的,否者就是无效的字符串。 这个方法的时间复杂度要高一些。

  1. class Solution {
  2. public boolean isValid(String s) {
  3. int length = s.length();
  4. do{
  5. length = s.length();
  6. s = s.replaceAll("\\(\\)","").replaceAll("\\{\\}","").replaceAll("\\[\\]","");
  7. }while(s.length()!=length);
  8. return s.length()==0;
  9. }
  10. }

以上,就是对数据结构中「 堆栈 」的一些思考。

发表评论

表情:
评论列表 (有 0 条评论,221人围观)

还没有评论,来说两句吧...

相关阅读

    相关 DDD

    什么是DDD? ddd不是一种架构风格,而是一种方法论,什么是方法论,每个人按照自己的想法来设计就是一套方法论;ddd是一种业务比较认可,对于微服务拆分的一种方法论。

    相关 Kafka

    学习之前,先来了解一下网络通信模型,网络通信模型的发展如下 > 单线程 => 多线程 => 线程池 => Reactor模型 Kafka所采用的Reactor模型如下

    相关 DDD

    [一文读懂DDD][DDD] 原文: [一文读懂DDD][DDD 1] 何为DDD DDD不是架构设计方法,不能把每个设计细节具象化,DDD是一套体系,决定了其开放性

    相关 CDN

    前言 最近在了解边缘计算,发现我们经常听说的CDN也是边缘计算里的一部分。那么说到CDN,好像只知道它中文叫做内容分发网络。那么具体CDN的原理是什么?能够为用户在浏览网站时