【数据结构】栈(Stack)的实现

本文介绍了栈的基本概念,包括它的线性表特性及“后进先出”原则。详细阐述了如何使用Java模拟实现栈,包括入栈、出栈、查看栈顶元素以及重写toString方法。此外,还讨论了与栈相关的在线编程题目,如有效括号的判断和栈的次序匹配问题。
摘要由CSDN通过智能技术生成

目录

1.栈的概念

2.栈的模拟实现

2.1栈的定义

2.2入栈 

2.3 出栈

2.4查看栈顶元素

2.5重写toString方法

2.6 完整代码:MyStack

2.7 测试类:MyStackTest

3.相关OJ题

3.1 有效的括号

3.2 出栈入栈次序匹配

3.3 求栈的最小值

3.4 逆波兰表达式求值


1.栈的概念

      栈是一种线性表,遵循“先进后出”的原则,简称LIFO(Last In First Out)。在栈顶进行数据的插入和删除。主要方法有三个:E push(E e): 元素e入栈; E pop():出栈;E peek():获取栈顶元素 。其他常用的方法还有int size():获取栈中有效元素的个数; boolean empty():判断栈是否为空 以及Stack():创建一个空栈。

简单使用一下上述方法:

public static void main(String[] args) {
        Stack<Integer> stack = new Stack<>();
        //入栈
        stack.push(1);
        stack.push(2);
        stack.push(3);
        stack.push(4);
        stack.push(5);
        stack.push(6);
        System.out.println("栈中的元素有"+stack);
        //出栈
        System.out.println("出栈的元素为"+stack.pop());
        System.out.println("出栈的元素为"+stack.pop());
        //查看栈顶元素
        System.out.println("栈顶元素为"+stack.peek());
    }

测试结果为:  

2.栈的模拟实现

        上面我们用java中自带的方法实现了一个栈,那么我们怎么自己模拟实现一个栈呢?这里我们首先要知道,栈是在栈顶进行元素的插入与删除的,所以我们在这里使用数组来实现栈,因为在数组的尾部进行元素的插入与删除也是非常方便的。

2.1栈的定义

    private Object[] array;//定义一个数组
    private int size;//当前栈中有效元素的个数
    //无参构造方法:默认栈的大小
    public MyStack() {
        this(10);//注意:这里的this就是下面的MyStack
    }    
    //有参构造方法
    public MyStack(int capacity) {
        this.array = new Object[capacity];
    }

2.2入栈 

我们根据下面的图理解,主要步骤也就是三步:因为入栈是在栈顶插入元素,也就是数组尾部插入元素,然后更新元素个数,最后判断数组是否需要扩容即可。

标题

代码实现:

    public void push(E element){
        array[size] = element;//尾插
        size++;//更新节点数
        if(array.length == size){//判断插入元素后数组的长度:是否需要扩容
            array = Arrays.copyOf(array,array.length<<1);
        }
    }

2.3 出栈

 代码实现:如果栈中没有元素则抛出异常。

    public E pop(){
        //边界条件:判断当前栈是否为空
        if(!isEmpty()){
            throw new NoSuchElementException("stack is empty");
        }
        //返回size-1位置上的值,也就是栈顶值
        E val = (E)array[size-1];
        size--;//更新元素个数
        return val;
    }
    /**
     * 判断当前栈是否为空
     */
    private boolean isEmpty() {
        if(size == 0){
            return false;
        }
        return true;
    }

2.4查看栈顶元素

查看栈顶元素的前提是栈不为空,否则抛出异常提示。

    public E peek(){
        //边界条件:判断当前栈是否为空
        if(!isEmpty()){
            throw new NoSuchElementException("stack is empty");
        }
        //返回size-1位置上的值,也就是栈顶值
        E val=  (E)array[size-1];
        return val;
    }

2.5重写toString方法

主要思想:遍历栈中的元素,当不为最后一个元素的时候添加逗号。

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("bottom[");
        for (int i = 0; i < size; i++) {
            sb.append(array[i]);
            if(i < size-1){
                sb.append(",");
            }
        }
        sb.append("]top");
        return sb.toString();
    }

 2.6 完整代码:MyStack

public class MyStack<E> {
    private Object[] array;//定义一个数组
    private int size;//定义一个默认的数组大小
    //构造方法:默认的数组大小
    public MyStack() {
        this(10);//注意:这里的this就是下面的MyStack
    }
    //有参数传入自定义的数组大小
    public MyStack(int capacity) {
        this.array = new Object[capacity];
    }
    /**
     * 入栈:利用数组实现入栈,相当于栈顶在数组的尾部,直接将元素值在数组中实现尾插,也就是size位置
     */
    public void push(E element){
        array[size] = element;//尾插
        size++;//更新节点数
        if(array.length == size){//判断插入元素后数组的长度:是否需要扩容
            array = Arrays.copyOf(array,array.length<<1);
        }
    }
    /**
     * 出栈:在数组尾部返回size-1位置上的值
     */
    public E pop(){
        //边界条件:判断当前数组是否为空
        if(!isEmpty()){
            throw new NoSuchElementException("stack is empty");
        }
        //返回size-1位置上的值,也就是栈顶值
        E val = (E)array[size-1];
        size--;//更新元素个数
        return val;
    }
    /**
     * 判断当前数组是否为空
     */
    private boolean isEmpty() {
        if(size == 0){
            return false;
        }
        return true;
    }
    /**
     * 查看栈顶元素:在数组尾部返回size-1位置上的值
     */
    public E peek(){
        //边界条件:判断当前数组是否为空
        if(!isEmpty()){
            throw new NoSuchElementException("stack is empty");
        }
        //返回size-1位置上的值,也就是栈顶值
        E val=  (E)array[size-1];
        return val;
    }
    /**
     * 重写toString方法
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("bottom[");
        for (int i = 0; i < size; i++) {
            sb.append(array[i]);
            if(i < size-1){
                sb.append(",");
            }
        }
        sb.append("]top");
        return sb.toString();
    }
}

2.7 测试类:MyStackTest

public class MyStackTest {
    public static void main(String[] args) {
        MyStack<Integer> stack = new MyStack<>();
        //入栈
        stack.push(1);
        stack.push(2);
        stack.push(3);
        stack.push(4);
        stack.push(5);
        stack.push(6);
        //出栈
        System.out.println(stack.pop());
        //查看此时的栈顶元素
        System.out.println(stack.peek());
        System.out.println(stack+"  ");
    }
}

3.相关OJ题

3.1 有效的括号

(1)问题描述:给定一个只包括 '(',')','{','}','[',']'的字符串s ,判断字符串是否有效。有效字符串需满足:

  • 左括号必须用相同类型的右括号闭合。
  • 左括号必须以正确的顺序闭合。
  • 每个右括号都有一个对应的相同类型的左括号。

(2)主要思路: 字符串中包含两种类型的符号,一种是左括号,另一种是右括号。我们可以不断的获取字符串中的每一个字符,判断该字符的类型:①如果第一个字符就是右括号则直接false,该字符串一定不是有效的;②如果是左括号我们就让它一直入栈,直到遇到右括号。此时我们就将栈中处于栈顶的左括号弹出,如果它们是匹配的一对括号,则返回true,否则返回false。根据这个特点,如果字符串遍历结束同时栈中的左括号全部出栈,栈为空,则此时的s一定是有效的字符串,否则不是有效字符串。

(3)代码实现

    public boolean isValid(String s) {
        //创建栈
        Stack<Character> stack = new Stack<>();
        //给出来的是字符串类型,要先转为字符
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            //让获取到的字符入栈:判断是不是左括号,如果是左括号的话才能入栈,直到遇见第一个右括号为止
            if (ch=='[' || ch=='{' || ch=='('){
                stack.push(ch);
            }else{
                //如果此时的符号是右括号,直接返回false
                if(stack.isEmpty()){
                    return false;
                }else{
                    //如果判断的是右括号:这里要写pop将栈顶的元素弹出,之前写的是peek,一直都是最上方的栈顶元素没有变化
                    char temp = stack.pop();
                    if(ch == ']' && (temp=='(' || temp == '{')){
                        return false;
                    }
                    if(ch == ')' && (temp=='[' || temp == '{')){
                        return false;
                    }
                    if(ch == '}' && (temp=='(' || temp == '[')){
                        return false;
                    }
                }
            }
        }
        //这里不能直接返回true,如果只有一个左括号的情况 [,for循环执行完走到这里是要判断栈中是否为空的。
        if(stack.isEmpty()){
            return true;
        }else{
            return false;
        }
    }

3.2 出栈入栈次序匹配

(1)问题描述:给定两个序列,判断第二个序列是不是第一个的出栈情况

(2)主要思路:序列s2使用一个指针i从第一个元素开始移动,遍历序列s1让其不断压入栈,直到s1的栈顶值和s2的指针指向的第一个值相等,此时s1弹出栈顶元素,s2的指针i向后移动。最终判断条件:如果s1的所有元素已经全部压入栈,但是栈中不为空,则s2一定不是s1的有效序列。

(3)代码实现

    public boolean validateStackSequences(int[] pushed, int[] popped) {
        //创建一个栈
        Stack<Integer> stack = new Stack<>();
        //i是s2的指针,从第一个值开始不断移动
        int i = 0;
        //遍历s1的值不断压入栈,直到s1的栈顶元素和s2的第一个值相等,则s1弹出栈顶元素
        for (int val : pushed) {
            stack.push(val);
            //注意:用的是while循环不是if,同时此时要注意前提条件:栈里面不能为空
            while (!stack.isEmpty() && popped[i] == stack.peek()){//s1的栈顶值和s2的指针指向的值相等
                stack.pop();//弹出s1的栈顶值
                i++;//s2的指针不断移动
            }
        }
        //直到for循环结束也就是s1所有的值都已经压入完栈了,但是此时栈中的值非空,则说明s2不是s1对应的出栈情况
        if(stack.isEmpty()){
            return true;
        }else{
            return false;
        }
    }

3.3 求栈的最小值

(1)问题描述:求栈中的最小值:请设计一个栈,除了常规栈支持的pop与push函数以外,还支持min函数,该函数返回栈元素中的最小值。 执行push、pop和min操作的时间复杂度必须为(1)。

(2)主要思路:一个栈无法同时获取栈中最小值和栈顶值,因此使用两个栈完成。 第一个栈进行正常的入栈,可以获取栈顶值;第二个栈则只保存入栈过程中的最小值。

 (3)代码实现

    //创建两个栈
    Stack<Integer> s1 = new Stack<>();
    Stack<Integer> s2 = new Stack<>();
    //入栈
    public void push(int x) {
        s1.push(x);//s1正常入栈
        if(s2.isEmpty()){//如果s2是空的,当前值就是最小值直接入栈s2
            s2.push(x);
        }else{//此时s2中存在值
            int temp = s2.peek();//先获取s2中的栈顶值
            if(x > temp){//如果当前的值大于s2的栈顶值
                s2.push(getMin());//将s2中的最小值入栈到时
            }
            else{
                s2.push(x);//否则该值就是最小值入栈s2
            }
        }
    }
    //出栈
    public void pop() {
        s1.pop();
        s2.pop();
    }
    //在s1里面查看栈顶元素
    public int top() {
        return (int)s1.peek();
    }
    //在s2中获取最小值
    public int getMin() {
        return (int) s2.peek();
    }

3.4 逆波兰表达式求值

(1)问题描述: 给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。  请你计算该表达式,返回一个表示表达式值的整数。比如:输入:tokens = ["2","1","+","3","*"],输出:9,解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9

(2)主要思想:感觉这个和前面的那个有效的括号思想有点类似,这个表达式里面也是包含两种不一样的符号,一个是数字,一个是操作运算符。那么我们也是不断的比那里字符串,判断该符号是数字还是运算符,如果是数字,就将该数字压入栈中;如果是运算符,就将栈中的数字弹出两次进行运算操作,计算完成之后再入栈,等待下一次的运算。最终栈中的元素就是计算得出的结果。

(3)代码实现

     public int evalRPN(String[] tokens) {
        //创建一个栈
        Stack<Integer> stack = new Stack<>();
        //遍历字符串
        for (String str : tokens) {
            //判断当前字符串是数字还是运算符号
            if(isNum(str)){
                //是数字,就将该字符串类型的数字转为整型数字压入栈
                stack.push(Integer.parseInt(str));
            }else{//说明是运算符,此时判断是哪种运算符
                //此时从栈中连续弹出两个值
                int num1 = stack.pop();
                int num2 = stack.pop();
                int result = 0;
                switch (str) {
                    case "+":
                        result = num2+num1;
                        break;
                    case "-":
                        result = num2-num1;
                        break;
                    case "*":
                        result = num2*num1;
                        break;
                    case "/":
                        result = num2/num1;
                        break;
                }
                //将计算出来的最新的值压入栈中,等待下一次计算
                stack.push(result);
            }
        }
        //当整个字符串遍历结束,栈顶的元素值就是最终计算结果
        return stack.peek();
    }
    /**
     * 判断当前字符串是数字还是运算符号
     * 尝试执行字符串转整型操作,不报错就返回true,否则catch返回false
     */
    private boolean isNum(String str) {
        try{
            Integer.parseInt(str);
            return true;
        }catch(Exception e){
            return false;
        }
    }

好啦~终于把栈的知识复习了一遍,下来要学习的是队列。终于到周五了,不会有人还在学习吧~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值