数据结构 - 栈

概述

计算机科学中,stack是一种线性的数据结构,只能在其一段添加和移除数据.

习惯来说,这一端称之为栈顶,另一端不能操作数据的称之为栈底,就如同生活中的一摞书

先提供一个接口:

java 复制代码
public interface Stack <E>{
    /**
     * 向栈顶压入元素
     * @param value -- 待压入值
     * @returns:压入成功返回true,否则返回false
     *
     */
    boolean push(E value);

    /**
     * 从栈顶弹出元素
     * @Returns:栈非空返回栈顶元素,栈为空返回null
     *
     */
    E pop();

    /**
     * 返回栈顶元素,不弹出
     * @Returns:栈非空返回栈顶元素,栈为空返回null
     */
    E peek();

    /**
     * 判断栈是否为空
     * @Returns:空返回true,否则返回false
     *
     */
    boolean isEmpty();

    /**
     * 判断栈已满
     * @Returns:满返回true,否则返回false
     *
     */
    boolean isFull();
}

链表实现:

java 复制代码
import java.util.Iterator;

public class LinkedListStack <E> implements Stack<E>,Iterable<E>{
    private int capacity;
    private int size;
    private Node<E>head = new Node<>(null,null);

    public LinkedListStack(int capacity){
        this.capacity = capacity;
    }
    @Override
    public Iterator<E> iterator() {
        return new Iterator<E>() {
            Node<E> p = head.next;
            @Override
            public boolean hasNext() {
                return p!=null;
            }

            @Override
            public E next() {
                E value = p.value;
                p=p.next;
                return value;
            }
        };
    }
/*
       2->
    head -> 2 -> 1 ->null
 */
    @Override
    public boolean push(E value) {
        if(isFull()){
            return false;
        }
        head.next = new Node<>(value,head.next);
        size++;
        return true;
    }

    /*
        head -> 2 ->1 ->null
     */
    @Override
    public E pop() {
        if(isEmpty()){
            return null;
        }
        Node<E> first = head.next;
        head.next = first.next;
        size--;
        return first.value;
    }

    @Override
    public E peek() {
        if(isEmpty()){
            return null;
        }
        return head.next.value;
    }

    @Override
    public boolean isEmpty() {
        return head.next ==null;//也可以size==0
    }

    @Override
    public boolean isFull() {
        return size==capacity;
    }

    //单向链表实现
    static class Node<E> {
        E value;
        Node<E>next;

        public Node(E value,Node<E>next){
            this.value = value;
            this.next = next;

        }
    }

}

数组实现:

java 复制代码
import java.util.Iterator;

public class ArrayStack<E> implements Stack<E>,Iterable<E> {

    private E[] array;
    private int top;//栈顶指针

    /*
       底           顶
        0   1   2   3
        a   b   c   d
                        t
        数组实现跟链表实现的顶部是相反的
        为什么链表的栈顶在首节点?因为增删查改方便,如果放在链表尾部那每次增删查改都需要遍历
        为什么数组实现的栈顶在尾部,因为数组是顺序结构
     */

    @SuppressWarnings("all")
    public ArrayStack(int capacity) {
        this.array = (E[])new Object[capacity];
    }

    @Override
    public Iterator<E> iterator() {
        return new Iterator<E>() {
            int p = top;
            @Override
            public boolean hasNext() {
                return p>0;
            }

            @Override
            public E next() {
                E value = array[p - 1];
                p--;
                //array[--p];
                return value;
            }
        };
    }

    @Override
    public boolean push(E value) {
        if(isFull()) {
            return false;
        }
        array[top] = value;
        top++;
        //array[top++] = value;
        return true;
    }

    @Override
    public E pop() {
        if(isEmpty()){
            return null;
        }
        E value = array[top-1];
       
        top --;
        //array[--top];
        return value;
    }

    @Override
    public E peek() {
        if(isEmpty()){
            return null;
        }

        return array[top-1];
    }

    @Override
    public boolean isEmpty() {
        return top == 0;
    }

    @Override
    public boolean isFull() {
        return top == array.length;
    }
}

应用

模拟如下方法调用:

java 复制代码
public class Main {
    public static void main(String[] args) {
        System.out.println("main1");
        System.out.println("main2");
        method1();
        method2();
        System.out.println("main3");
    }

    public static void method1() {
        System.out.println("method1");
        method3();
    }

    public static void method2() {
        System.out.println("method2");
    }

    public static void method3() {
        System.out.println("method3");
    }
}
/**
 * main1
 * main2
 * method1
 * method3
 * method2
 * main3
 */

模拟实现:

java 复制代码
public class CPU {
    static class Frame {
        int exit;

        public Frame(int exit) {
            this.exit = exit;
        }
    }
    static int pc = 1; // 模拟程序计数器 Program counter
    static ArrayStack<Frame> stack = new ArrayStack<>(100); // 模拟方法调用栈

    public static void main(String[] args) {
        stack.push(new Frame(-1));
        while (!stack.isEmpty()) {
            switch (pc) {
                case 1 -> {
                    System.out.println("main1");
                    pc++;
                }
                case 2 -> {
                    System.out.println("main2");
                    pc++;
                }
                case 3 -> {
                    stack.push(new Frame(pc + 1));
                    pc = 100;
                }
                case 4 -> {
                    stack.push(new Frame(pc + 1));
                    pc = 200;
                }
                case 5 -> {
                    System.out.println("main3");
                    pc = stack.pop().exit;
                }
                case 100 -> {
                    System.out.println("method1");
                    stack.push(new Frame(pc + 1));
                    pc = 300;
                }
                case 101 -> {
                    pc = stack.pop().exit;
                }
                case 200 -> {
                    System.out.println("method2");
                    pc = stack.pop().exit;
                }
                case 300 -> {
                    System.out.println("method3");
                    pc = stack.pop().exit;
                }
            }
        }
    }
}
/**
 * main1
 * main2
 * method1
 * method3
 * method2
 * main3
 */

练习一下:

20. 有效的括号 - 力扣(LeetCode)

E01. 有效的括号-Leetcode 20

一个字符串中可能出现 [] (){} 三种括号,判断该括号是否有效

有效的例子

复制代码
()[]{}
​
([{}])
​
()

无效的例子

复制代码
[)
​
([)]
​
([]

思路

  • 遇到左括号, 把要配对的右括号放入栈顶

  • 遇到右括号, 若此时栈为空, 返回 false,否则把它与栈顶元素对比

    • 若相等, 栈顶元素弹出, 继续对比下一组

    • 若不等, 无效括号直接返回 false

  • 循环结束

    • 若栈为空, 表示所有括号都配上对, 返回 true

    • 若栈不为空, 表示右没配对的括号, 应返回 false

答案(用到了上面案例中的 ArrayStack 类)

java 复制代码
public boolean isValid(String s) {
        ArrayStack<Character> stack = new ArrayStack<>(s.length() / 2 + 1);
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c == '(') {
                stack.push(')');
            } else if (c == '[') {
                stack.push(']');
            } else if (c == '{') {
                stack.push('}');
            } else {
                if (!stack.isEmpty() && stack.peek() == c) {
                    stack.pop();
                } else {
                    return false;
                }
            }
        }
        return stack.isEmpty();
    }
java 复制代码
class Solution {
    public boolean isValid(String s) {
        if(s.isEmpty())
            return true;
        Stack<Character> stack=new Stack<Character>();
        for(char c:s.toCharArray()){
            if(c=='(')
                stack.push(')');
            else if(c=='{')
                stack.push('}');
            else if(c=='[')
                stack.push(']');
            else if(stack.empty()||c!=stack.pop())
                return false;
        }
        if(stack.empty())
            return true;
        return false;
    }
}
cpp 复制代码
class Solution {
public:
   bool isValid(string s) {
       if(s.size() == 0){
           return 0;
       }
       stack<char> st;
       for(auto &ch: s){
           if(ch == '(' || ch == '{' || ch == '['){
               st.push(ch);//入栈
           }else{
               if(!st.empty()){
                   if(ch == ')'){
                       if(st.top() != '('){
                           return false;
                       }
                       st.pop();
                   }else if(ch == '}'){
                       if(st.top() != '{'){
                           return false;
                       }
                       st.pop();
                   }else if(ch == ']'){
                       if(st.top() != '['){
                           return false;
                       }
                       st.pop();
                   }
               }else{
                   return false;
               }
           }
       }
       return st.empty();
   }
};
E02-后缀表达式求值

LCR 036. 逆波兰表达式求值 - 力扣(LeetCode)

java 复制代码
import java.util.LinkedList;

public class Main {
   /*

   逆波兰表达式也称为后缀表达式,即把运算符写在后面
        从左向右进行计算
        不必考虑运算优先级,即不用包含括号
        /     /
        /     /
        /     /
        /     /
        /     /
        /  9  /
        ------
        "2"  1 + 3 + *
   1+2  中缀表达式
   1 2 + 后缀表达式
    LinkedList里面也实现了栈
    */
   public int evalRPN(String[] tokens) {
       LinkedList<Integer> numbers = new LinkedList<>();
       for (String t : tokens) {
           switch (t) {
               case "+" -> {
                   Integer b = numbers.pop();
                   Integer a = numbers.pop();
                   numbers.push(a + b);
               }
               case "-" -> {
                   Integer b = numbers.pop();
                   Integer a = numbers.pop();
                   numbers.push(a - b);
               }
               case "*" -> {
                   Integer b = numbers.pop();
                   Integer a = numbers.pop();
                   numbers.push(a * b);
               }
               case "/" -> {
                   Integer b = numbers.pop();
                   Integer a = numbers.pop();
                   numbers.push(a / b);
               }
               default -> numbers.push(Integer.parseInt(t));
           }
       }
       return numbers.pop();
   }

}
E-03中缀表达式转后缀

反编译

c = a + b

看不懂也没有关系,反正编译器会讲中缀表达式转换为后缀表达式

java 复制代码
import java.util.LinkedList;

/**
 * 中缀表达式转后缀
 */
public class E03InfixToSuffix {

//    public static void test(){
//        int a = 1;
//        int b =2;
//        int c = a+ b;
//    //在编译成class文件的时候就是把中缀表达式转换为了后缀表达式
//        //先让程序运行一次,目的是让程序生成字节码文件
//    }
    /*
        思路
    这是一个栈
        /   /
        /   /
        /   /
        /   /
        ----
        a+b      ab+
        a+b-c   ab+c-
        a+b*c   abc*+
        (a+b)*c  ab+c*
        (a+b*c-d)*e   abc*+d-e*
        a*(b+c)       abc+*

       1.遇到非运算符 直接拼串
       2.遇到 + - *  /
            - 它的优先级比栈顶运算符高,入栈
            - 否则把栈里优先级>=它 的都出栈, 它再入栈
       3.遍历完成,栈里剩余运算符依次出栈
       4.带()
            - 左括号直接入栈,左括号优先设置为0
            - 右括号就把栈里到左括号为止的所有运算符出栈
    */

    public static void main(String[] args) {
        System.out.println(infixToSuffix("a+b"));
        System.out.println(infixToSuffix("a+b-c"));
        System.out.println(infixToSuffix("a+b*c"));
        System.out.println(infixToSuffix("a*b-c"));
        System.out.println(infixToSuffix("(a+b)*c"));
        System.out.println(infixToSuffix("a*(b+c)"));
        System.out.println(infixToSuffix("a+b*c+(d*e*f)*g"));


    }

    /**
     * 计算运算符优先级
     * @param c
     * @return
     */
    static int priority(char c){
        return switch (c) {
            case '*','/'->2;
            case '+','-'->1;
            case '(' ->0;
            default->throw new IllegalArgumentException("不合法的运算符:"+c);
        };
    }
    static String infixToSuffix(String exp){
        LinkedList<Character>stack = new LinkedList<>();//栈
        StringBuilder sb = new StringBuilder(exp.length());//拼接
        for (int i = 0; i < exp.length(); i++) {
            char c = exp.charAt(i);
            switch (c) {
                case '+' ,'-','*','/'->{
                    if(stack.isEmpty()){
                        stack.push(c);
                    }else{
                        if (priority(c) >priority(stack.peek())) {
                            stack.push(c);
                        }else{
                            while(!stack.isEmpty()&&priority(stack.peek())>=priority(c)){
                                sb.append(stack.pop());
                            }
                            stack.push(c);
                        }
                    }
                }
                case '('->{
                    stack.push(c);
                }
                case ')'->{
                    while(!stack.isEmpty()&&stack.peek()!='('){
                        sb.append(stack.pop());
                    }
                    stack.pop();
                }
                default ->{
                    sb.append(c);
                }
            }
        }
        while(!stack.isEmpty()){
            sb.append(stack.pop());

        }
        return sb.toString();
    }
}
E04Leetcode232

232. 用栈实现队列 - 力扣(LeetCode)

java 复制代码
class MyQueue {

  ArrayStack<Integer>s1 =new ArrayStack<>(100);
    ArrayStack<Integer>s2 =new ArrayStack<>(100);

    public void push(int x){//向队列尾添加
        s2.push(x);
    }
    public int pop(){
        if(s1.isEmpty()){
            while(!s2.isEmpty()){
                s1.push(s2.pop());
            }
        }
        return s1.pop();
    }
    public int peek(){

        if(s1.isEmpty()){
            while(!s2.isEmpty()){
                s1.push(s2.pop());
            }
        }
        return s1.peek();

    }
    public boolean empty(){
        return s1.isEmpty()&&s2.isEmpty();
    }
    static class ArrayStack<E>{
        private E[] array;
        private int top;//栈顶指针
      
        public ArrayStack(int capacity) {
            this.array = (E[])new Object[capacity];
        }

      
        public boolean push(E value) {
            if(isFull()){
                return false;
            }
            array[top++]=value;
            return true;
        }

        
        public E pop() {
            if(isEmpty()){
                return null;
            }
            E value = array[top - 1];
            top--;
            return value;
        }

       
        public E peek() {
            if(isEmpty()){
                return null;
            }
            E value = array[top - 1];
            return value;
        }

        
        public boolean isEmpty() {
            return top==0;
        }

     
        public boolean isFull() {
            return top==array.length;
        }
    }
}

/**
 * Your MyQueue object will be instantiated and called as such:
 * MyQueue obj = new MyQueue();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.peek();
 * boolean param_4 = obj.empty();
 */
相关推荐
Dong雨1 小时前
六大排序算法:插入排序、希尔排序、选择排序、冒泡排序、堆排序、快速排序
数据结构·算法·排序算法
茶猫_1 小时前
力扣面试题 39 - 三步问题 C语言解法
c语言·数据结构·算法·leetcode·职场和发展
初学者丶一起加油2 小时前
C语言基础:指针(数组指针与指针数组)
linux·c语言·开发语言·数据结构·c++·算法·visual studio
半盏茶香3 小时前
C语言勘破之路-最终篇 —— 预处理(上)
c语言·开发语言·数据结构·c++·算法
2401_858286113 小时前
118.【C语言】数据结构之排序(堆排序和冒泡排序)
c语言·数据结构·算法
没事就去码3 小时前
RBTree(红黑树)
数据结构·c++
就爱学编程4 小时前
重生之我在异世界学编程之数据结构与算法:单链表篇
数据结构·算法·链表
CSCN新手听安9 小时前
list的常用操作
数据结构·list
梅茜Mercy11 小时前
数据结构:链表(经典算法例题)详解
数据结构·链表
青春男大11 小时前
java栈--数据结构
java·开发语言·数据结构·学习·eclipse