11.16栈

目录

一.栈

1 概念

2.虚拟机栈

二.栈的实现(数组-顺序表)

三.栈的实现(单向链表)

四.栈的实现(双向链表)

五.栈的不可能输出序列

1.选择题

2.力扣习题

六.中缀表达式和后缀表达式

1)中缀表达式:操作运算符在操作数中间

1.中缀表达式 ----> 前缀表达式:

2.后缀表达式(逆波兰式):操作运算符在操作数之前,

3.力扣习题

七.有效括号

八.设计最小栈


一.栈

1 概念

栈:一种特殊的线性表,一种数据结构.其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则

压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。

出栈:栈的删除操作叫做出栈。出数据在栈顶。

2.虚拟机栈

JVM stack 只是JVM只是JVM当中的一小块内存,该内存一般用来存放数据,如:局部变量

调用函数的时候,我们会为这个函数开辟一块内存,叫做栈帧,在JVM虚拟栈开辟

二.栈的实现(数组-顺序表)

public class MyStack {
    public int[] elem;
    public int  usedSize;

    public MyStack(){//实现构造方法
        this.elem=new int[10];
    }

    public void push(int val){//入栈
        if(isFull()){//判读是否满了.没满扩容
            this.elem= Arrays.copyOf(this.elem,2*this.elem.length);
        }
        this.elem[usedSize]=val;
        this.usedSize++;
    }
    public boolean isFull(){
        return this.usedSize==this.elem.length;
    }
    public int pop() {
        if(isEmpty()) {
            throw new RuntimeException("栈为空!");
        }
        int oldVal = this.elem[usedSize-1];
        this.usedSize--;
        return oldVal;
    }
    public boolean isEmpty(){
        return this.usedSize==0;
    }
    public int peek(){//查看第一个
        if(isEmpty()){//判断是否是空的
            throw new RuntimeException("栈为空!");
        }
        return  this.elem[usedSize-1];
    }

}

为简便先用int

java底层也是数组,只要把加上泛型即可,他继承与Vector.无参的构造方法是空的,

追根溯源发现,他给数组的初始化也是10,因为'

我们改变int变成泛型发现泛型是不能直接实例化的,我们可以仿造源码的做法

public class MyStack<E> {
    public E[] elem;
    public int  usedSize;

    public MyStack(){//实现构造方法
        this.elem=new E[10];
    }

    public void push(E val){//入栈
        if(isFull()){//判读是否满了.没满扩容
            this.elem= Arrays.copyOf(this.elem,2*this.elem.length);
        }
        this.elem[usedSize]=val;
        this.usedSize++;
    }
    public boolean isFull(){
        return this.usedSize==this.elem.length;
    }
    public E pop() {
        if(isEmpty()) {
            throw new RuntimeException("栈为空!");
        }
        E oldVal = this.elem[usedSize-1];
        this.usedSize--;
        return oldVal;
    }
    public boolean isEmpty(){
        return this.usedSize==0;
    }
    public E peek(){//查看第一个
        if(isEmpty()){//判断是否是空的
            throw new RuntimeException("栈为空!");
        }
        return  this.elem[usedSize-1];
    }

}

三.栈的实现(单向链表)

源码里的栈要求是先进后出,并且入栈和出栈的时间复杂度是O(1)

因为链表可以头插也可以尾插,那么我们用头插法来实现入栈出栈还是尾插法呢

假设入栈是尾插法,那么时间复杂度是O(n),因为尾插法每次都要找尾巴.

这时候我们可以试着在链表尾部定义一个尾结点.插入的时候直接找尾结点即可

这样时间复杂度就是O(1)

但是单向链表的增删查改都需要找到前驱结点.所以需要遍历找这个节点的前驱.这样时间复杂度又是

o(1)

所以我们的解决方案就是双向链表或者是头插法.出栈入栈删除和添加头结点即可.

class MyNode{
    public int val;
    public MyNode next;

    public MyNode(int val) {
        this.val = val;
    }
}

public class MyStackLisk {
    public MyNode head;
    public MyStackLisk(){
        this.head=null;
    }

    public void push(int val){
        MyNode node=new MyNode(val);
        if(isEmpty()){
            this.head=node;
        }
        node.next=this.head;
        this.head=node;

    }
    public boolean isEmpty(){
        return this.head==null;
    }
    public int pop(){
        if(isEmpty()){
            throw new RuntimeException("kong");
        }
        MyNode node=this.head;
        this.head=node.next;
        return node.val;
    }
    public int peep(){
        if(isEmpty()){
            throw new RuntimeException("kong");
        }
        return this.head.val;
    }

}

四.栈的实现(双向链表)

class Node{
    public int val;
    public Node next;
    public Node pre;

    public Node(int val) {
        this.val = val;
    }
}

public class MyStacklisks {
    public Node head;
    public MyStacklisks(){
        this.head=null;
    }

    public void push(int val){
        Node node=new Node(val);
        if(isEmpty()){
            this.head=node;
        }
        node.next=this.head;
        this.head.pre=node;
        this.head=node;
        this.head.pre=null;

    }
    public boolean isEmpty(){
        return this.head==null;
    }
    public int pop(){
        if(isEmpty()){
            throw new RuntimeException("kong");
        }
        Node node=this.head;
        this.head=node.next;
        this.head.pre=null;
        return node.val;
    }
    public int peep(){
        if(isEmpty()){
            throw new RuntimeException("kong");
        }
        return this.head.val;
    }

}

五.栈的不可能输出序列

1.选择题

画图可知,不再赘述

2.力扣习题

思路:构建两个循环,先让第一个序列按数组顺序压入,再进入第二个序列,看栈顶与第二个序列的第一个元素是否相同,如果相同,直接pop.让第二个序列往后走一个位置,继续比较,如果不同,或者栈是空的,直接跳出循环,进入大循环,继续.循环结束的情况就是第一个序列全部PUSH了

最后看栈是否是空的,如果不是,就说明不是,

因为如果不是空的,就说明第二个序列遍历完了,但是第一个还有元素push在里面,没有与他相同的

class Solution {
    public boolean validateStackSequences(int[] pushed, int[] popped) {
            Stack<Integer> stack=new Stack<>();
            int j=0;
            for(int i =0;i<pushed.length;i++){
                stack.push(pushed[i]);
                while(stack.peek()==popped[j]){
                    stack.pop();
                    j++;
                    if(stack.empty()){
                        break;
                    }
                }
            }
            if(!stack.empty()){
                return false;
            }
            return true;

    }
}

六.中缀表达式和后缀表达式

中缀表达式、前缀表达式、后缀表达式

1)中缀表达式:操作运算符在操作数中间

(3+2)4-62)前缀表达式:波兰式,操作运算符在操作数之前,如-+3246。

从右至左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素 op 次顶元素),并将结果入栈;重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果

1.中缀表达式 ----> 前缀表达式:

- 初始化两个栈,符号栈s1、存放中间结果栈s2 - **从右至左扫描**中缀表达式 - 遇到操作数,将其压入栈s2 - 遇到操作运算符,则需要比较其与s1栈顶操作符的优先级 1.若s1为空,或者栈顶运算符为**右括号)**,则直接入栈 2.优先级高于栈顶运算符,也直接入栈 3.否则,将s1栈顶运算符弹出压入s2,再转到步骤1进行判断 - 遇到括号: 1.如果遇到**右括号**,直接压入栈 2.如果遇到**左括号**,则依次弹出s1栈顶运算符,压入s2,直到遇到右括号为止,并将这一对括号丢弃 - 重复2-5,直至表达式左边 - 将s1中剩余运算符依次弹出并压入s2 - 依次弹出s2的元素,即为中缀表达式

2.后缀表达式(逆波兰式):操作运算符在操作数之前,

如3 4 + 5 × 6 -。与前缀表达式类似,区别是从左至右扫描。

从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素 op 栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果

中缀表达式 ----> 后缀表达式:

      - 初始化两个栈,符号栈s1、存放中间结果栈s2
      - **从左至右**扫描中缀表达式
      - 遇到操作数,将其压入栈s2
      - 遇到操作运算符,则需要比较其与s1栈顶操作符的优先级
      1.若s1为空,或者栈顶运算符为**左括号(**,则直接入栈
      2.优先级高于栈顶运算符,也直接入栈
      3.否则,将s1栈顶运算符弹出压入s2,再转到步骤1进行判断
      - 遇到括号:
                 1.如果遇到**左括号**,直接压入栈
                 2.如果遇到**右括号**,则依次弹出s1栈顶运算符,压入s2,直到遇到右括号为止,并将这一对括号丢弃
  - 重复2-5,直至表达式右边
  - 将s1中剩余运算符依次弹出并压入s2
  - 依次弹出s2的元素,即为中缀表达式

 表达式:3 4 + 5 × 6 -
   - 从左至右扫描后缀表达式,依次将3、4依次压入堆栈
 - 遇到运算符 + ,弹出栈顶和次栈顶的两个数4、3,计算4+3的值为7,压入栈
 - 遇到5,入栈
 - 遇到x,弹出栈顶和次栈顶的两个数5、7,计算7*5=35,入栈
 - 遇到6,入栈
 - 遇到-,弹出栈顶和次栈顶的两个数6、36,计算35-6=29

3.力扣习题

思路:怎样判断是否是运算符还是数字

遍历数组

就建立一个栈把数字放在栈里

如果是数字就先压入栈,碰到运算符就弹出两个元素,先弹出的是运算符右边的操作数

因为按照中缀表达式的压入顺序

class Solution {
    public int evalRPN(String[] tokens) {
        Stack<Integer> stack=new Stack();
        int num1=0;int num2=0;
        for(int i=0;i<tokens.length;i++){
            String s=tokens[i];
            if(!isOperation(s)){
                stack.push(Integer.parseInt(s));
            }else{
                num2=stack.pop();
                num1=stack.pop(); 
                switch(s){
                    case "+":
                    stack.push(num1+num2);
                    break;
                     case "-":
                    stack.push(num1-num2);
                    break;
                    case "*":
                    stack.push(num1*num2);
                    break;
                     case "/":
                    stack.push(num1/num2);
                    break;
                }
            }
        }
        return stack.pop();

    }
    public boolean isOperation(String s){
        if(s.equals("+")||s.equals("-")||s.equals("*")||s.equals("/")){
            return true;
        }
        return false;
    }
}

这里要注意,就是避免内存冗余,可以在进入Switch语句的时候之前就把pop值放在num里不要忘记break

还有还要把字符转为整数包装类:Integer.parseInt(s)

七.有效括号

首先要判断字符串长度是否是偶数,不是偶数一定不匹配

有效字符串

1.左括号必须有相同类型的右括号闭合

2.左括号必须以正确的顺序闭合

不匹配的情况

如果遇到左括号,就入栈

如果遇到有括号,就跟栈顶匹配

如果匹配.就pop

如果不匹配就直接输出false

当字符串遍历完

栈还有元素,就说明又不匹配的元素.就说明左括号多

如果遇到右括号多的情况,也就是在循环的时候.栈已经空了.这种情况直接false

class Solution {
    public boolean isValid(String s) {
        if(s.length()%2==1) return false;//如果是奇数直接返回假
        Stack<Character> stack=new Stack();
        for(int i = 0 ;i<s.length();i++){
            char ch = s.charAt(i);
            if(isLeft(ch)){
                stack.push(ch);//如果是左括号就入栈
            }else{//这是右括号的情况
                if(stack.empty()){
                   return false;//如果是右括号的时候,但是栈为空,就说明右括号多,
                }
                char top=stack.peek();
                if(top == '{' && ch == '}' || top == '[' && ch == ']' || top == '(' && ch == ')'){//如果匹配
                    stack.pop();就排出
                }else{
                    return false;//不匹配就不对
                }
            }
        }
        if(stack.empty()){
                   return true;
                }else{
                    return false;
                }

    }
    public boolean isLeft(char top){
        if(top == '{' || top == '['  || top == '(' ){
            return true;
        }
        return false;

    }

八.设计最小栈

设计两个栈,一个是正常放入,一个只放最小元素的栈,

第一次两个栈都正常放

第二次的时候放入普通栈,并且两个栈顶元素比较,如果最小元素大.那么就把那个元素也同样放入

这里有个问题,如果相同,放不放

答案是放的

因为出栈的时候,如果不是最小元素就只要拿走普通栈的

但是如果是最小元素那就两个都要拿走

但是如果两个相同的最小,第一次拿走了,最小元素也拿走了,第二次就没得拿了


class MinStack {
       private Stack<Integer> nomalStack;
        private Stack<Integer> miniStack;//要放在外面不然其他位置永不了/
    public MinStack() {
         nomalStack=new Stack<>();
         miniStack=new Stack<>();
    }
    
    public void push(int val) {
          nomalStack.push(val);
          if(miniStack.empty()){
             miniStack.push(val);
          }else{
              if(val<=getMin()){//等于也要成立
               miniStack.push(val);
              }
          }

    }
    
    public void pop() {
        if(top()==getMin()){
            miniStack.pop();
            nomalStack.pop();
        }else{
            nomalStack.pop();
        }

    }
    
    public int top() {
        return nomalStack.peek();

    }
    
    public int getMin() {
        return miniStack.peek();

    }
}
我这里出现的问题就是我在构造方

我这里出现的问题就是我在构造方法里定义stack

这样导致其他的方法都用不了,因为在方法里,那他的使用范围就只在方法里.

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值