数据结构与算法-栈

引言

在计算机科学和软件工程领域,数据结构是构建算法与程序设计的基础。栈(Stack)作为一种基础且实用的数据结构,遵循“后进先出”(Last In First Out, LIFO)原则,它的操作简单直观,广泛应用于各种编程场景。本文将深入剖析栈的基本概念、操作原理、实现方式以及实际应用场景。

一、栈的基本概念与原理

栈是一种线性数据结构,它仅允许在一端进行插入和删除操作,这一端称为栈顶(Top)。新元素总是被添加到栈顶,并且只有栈顶的元素才能被移除或访问,这种特性赋予了栈独特的逻辑特性——后进先出。

栈的主要操作包括:

  1. Push(入栈):将一个元素添加到栈顶。
  2. Pop(出栈):从栈顶移除并返回一个元素。
  3. Peek(查看栈顶元素):返回栈顶元素但不移除。

二、栈的实现方式

在Java等高级语言中,栈可以通过数组或链表来实现。下面分别简述两种实现方式:

1.基于数组的栈

  • 基于数组的栈:使用固定大小的数组来存储元素,通过一个变量记录当前栈顶位置。当栈顶达到数组末尾时,若需要继续添加元素,则可能需要动态扩容,这会带来一定的性能开销。
class ArrayStack {
    private int maxSize;   //栈的大小
    private int[] stack;   //数组,数组模拟栈,数据就放在该数组
    private int top = -1;  //栈顶,初始化为-1

    public ArrayStack(int maxSize) {
        this.maxSize = maxSize;
        stack = new int[maxSize];
    }

    //    栈满
    public boolean isFull() {
        return top == maxSize - 1;
    }

    //    栈空
    public boolean isEmpty() {
        return top == -1;
    }

    //    入栈
    public void push(int value) {
        if (isFull()) {
            System.out.println("栈满");
            return;
        }
        top++;
        stack[top] = value;
    }

    //    出栈
    public int pop() {
        if (isEmpty()) {
            throw new RuntimeException("栈空");
        }
        int value = stack[top];
        top--;
        return value;
    }

    //    得到栈顶值不弹出
    public int peek() {
        if (isEmpty()) {
            throw new RuntimeException("栈空");
        }
        return stack[top];
    }

    //    显示栈
    public void show() {
        if (isEmpty()) {
            System.out.println("栈空");
            return;
        }
        for (int i = top; i >= 0; i--) {
            System.out.printf("stack[%d]=%d\n", i, stack[i]);
        }
    }
}

2.基于链表的栈 

  • 基于链表的栈: 可以使用单向链表的头节点作为栈顶,新增元素直接加在链表头部,删除元素则只需改变头节点引用。这样可以避免数组扩容的问题,但每个节点需要额外的空间存储指针信息。
class LinkedListStack {
    private Hero head = new Hero(0, "");

    //    栈空
    public boolean isEmpty() {
        return head.getNext() == null;
    }

    //    入栈
    public void push(Hero heroNode) {
        Hero temp = head;
        while (true) {
            if (temp.getNext() == null) {
                break;
            }
            temp = temp.getNext();
        }
        temp.setNext(heroNode);
    }

    //    出栈
    public Hero pop() {
        Hero temp = head;
        Hero value = null;
        if (temp.getNext() == null) {
            throw new RuntimeException("栈空");
        }
        while (true) {
            if (temp.getNext().getNext() == null) {
                break;
            }
            temp = temp.getNext();
        }
        value = temp.getNext();
        temp.setNext(null);
        return value;
    }

    //    得到栈顶值不弹出
    public Hero peek() {
        Hero temp = head;
        if (temp.getNext() == null) {
            throw new RuntimeException("栈空");
        }
        while (true) {
            if (temp.getNext() == null) {
                break;
            }
            temp = temp.getNext();
        }
        return temp;
    }

    //    显示栈
    public void show() {
        if (isEmpty()) {
            System.out.println("栈空");
            return;
        }
        Hero reverseHead = new Hero(-1, "");
        Hero next = null;
        Hero cur = head.getNext();
        while (cur != null) {
            next = cur.getNext();
            cur.setNext(reverseHead.getNext());
            reverseHead.setNext(cur);
            cur = next;
        }
        head.setNext(reverseHead.getNext());
        Hero temp = head.getNext();
        while (true) {
            if (temp == null) {
                break;
            }
            System.out.println(temp);
            temp = temp.getNext();
        }
    }
}

class Hero {
    private int no;
    private String name;
    private Hero next;

    public Hero(int no, String name) {
        this.no = no;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Hero{" +
                "no=" + no +
                ", name='" + name + '\'' +
                '}';
    }

    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Hero getNext() {
        return next;
    }

    public void setNext(Hero next) {
        this.next = next;
    }
}

三、栈的应用场景

栈由于其简洁高效的特性,在众多编程问题和系统设计中发挥着关键作用,如:

  1. 函数调用堆栈:在大多数编程语言中,函数调用过程都会形成一个隐式的调用栈,用于保存函数参数、局部变量及返回地址等信息。

  2. 表达式求值和计算:比如计算逆波兰表示法(Reverse Polish Notation, RPN)表达式时,可以利用栈存储运算数,并在遇到运算符时弹出相应数量的运算数进行计算。

  3. 深度优先搜索(DFS):在图论算法中,遍历图或树时常使用栈来辅助完成深度优先搜索。

  4. 浏览器的历史记录:浏览器的前进后退功能就是栈的一个典型应用实例,用户访问的网页顺序按照栈的规则进行存储和回溯。

  5. 括号匹配问题:在处理代码或者文本中的括号匹配问题时,可以用栈来检测括号是否正确配对。

四、引出栈的应用(计算器)

1. 使用栈完成表达式的计算

思路: 

  • 通过一个index值,遍历我们的表达式
  • 发现是数字,就直接入数字栈
  • 发现是字符符号,就如下二种情况
  • 如果符号栈中有操作符,就将其进行比较,如果当前操作符的优先级小于等于栈中操作符的优先级,就从数字栈中pop出两个值,从符号栈中pop出一个值,进行运算,将运算结果入栈数字栈,再讲该符号入栈到符号栈
  • 如果当前操作符的优先级大于栈中操作符的优先级,就直接入符号栈
  • 当表达式扫描完毕后,就顺序从数字栈和符号栈中pop出对应值进行运算
  • 数字栈中最后只剩一个值,就是该表达式结果

验证: 

  3 + 2 * 6 - 2 ?= 13

2.代码展示

便于进行计算和判断扫表的是数字还是符号这里增加对应解决方法

1.比较运算符优先级

    //    返回运算符的优先级 优先级是程序员来定的,优先级使用数字表示,数字越大优先级越高
    public int priority(int oper) {
        if (oper == '*' || oper == '/') {
            return 1;
        } else if (oper == '+' || oper == '-') {
            return 0;
        } else {
            return -1;   //假定运算符只有+-*/
        }
    }

2.判断数字字符 

   //    判断是否为运算符
    public boolean isOper(int val) {
        return val == '+' || val == '-' || val == '*' || val == '/';
    }

3.两数计算 

    //    计算方法
    public int cal(int num1, int num2, int oper) {
        int res = 0;  //用于存放计算的结果
        switch (oper) {
            case '+':
                res = num1 + num2;
                break;
            case '-':
                res = num2 - num1;    //注意顺序
                break;
            case '*':
                res = num1 * num2;
                break;
            case '/':
                res = num2 / num1;
                break;
            default:
                break;
        }
        return res;
    }

 4.计算器逻辑 

public class Calculator {
    public static void main(String[] args) {
        //计算表达式
        String expression = "71+2*6-4";
        //创建存放数字栈
        ArrayStack2 numStack = new ArrayStack2(10);
        //创建存放符号栈
        ArrayStack2 operStack = new ArrayStack2(10);
        //定义需要的相关变量
        int index = 0;   //用于扫描栈的
        int num1 = 0;
        int num2 = 0;
        int oper = 0;
        int res = 0;
        char ch = ' ';    //将每次扫描的car保存到此
        String keepNum = "";  //拼接多为数的
        while (true) {
            ch = expression.substring(index, index + 1).charAt(0);
//            判断ch是数字还是符号
            if (numStack.isOper(ch)) {   //符号
//                判断当前符号栈是否为空
                if (!operStack.isEmpty()) {
//                    不为空 处理
//                    如果符号栈有操作符,就进行比较,如果当前的操作符的优先级小于或者等于栈中的操作符,
//                    就从符号栈中pop出一个符号,进行计算,将得到结果入数字栈,然后将当前的操作符入符号栈
                    if (operStack.priority(ch) <= operStack.priority(operStack.peek())) {
                        num1 = numStack.pop();
                        num2 = numStack.pop();
                        oper = operStack.pop();
                        res = numStack.cal(num1, num2, oper);
                        numStack.push(res);
                        operStack.push(ch);
                    } else {
//                      如果当前的符号优先级大于栈中顶部符号的优先级那么直接入栈
                        operStack.push(ch);
                    }
                } else {
//                    为空  直接入栈操作
                    operStack.push(ch);
                }
            } else {   //数字
//                这里扫描的数字是字符类型
//                例如 '1' = 49  '2' = 50  所以需要 ch - 48
//                分析思路
//                1.当处理多位数时,不能发现是一个数就直接入栈,因为可能是多位数
//                2.在处理数时,需要向expression的表达式的index后看一位是不是数字还是字符
//                如果是数字就继续扫描,是符号就入栈
//                3.需要定义一个变量拼接

//                处理多位数
                keepNum += ch;
//                如果ch已经是最后一位就无须往后看
                if (index == expression.length() - 1) {
                    numStack.push(Integer.parseInt(keepNum));
                } else {
//                往后看一位,不影响index值
                    if (operStack.isOper(expression.substring(index + 1, index + 2).charAt(0))) {
                        numStack.push(Integer.parseInt(keepNum));
//                    重要!!!!!KeepNum清空
                        keepNum = "";
                    }
                }
            }
//            让index+1并判断是否扫描到最后
            index++;
            if (index >= expression.length()) {
                break;
            }
        }
//            扫描完毕,就顺序从数组栈和符号栈pop相应的数和符号进行运算,
//            数字栈将会有最后一个数组,就是该表达式的结构
        while (true) {
//                如果符号栈为空就结束
            if (operStack.isEmpty()) {
                break;
            }
            num1 = numStack.pop();
            num2 = numStack.pop();
            oper = operStack.pop();
            res = numStack.cal(num1, num2, oper);
            numStack.push(res);
        }
        System.out.printf("表达式%s的结果为%d", expression, numStack.pop());
    }

}

五、总结

        总之,栈作为最基础的数据结构之一,不仅在理论层面为我们提供了理解复杂问题的新视角,而且在实践中为许多算法和系统提供了高效可靠的解决方案。掌握栈的概念和运用技巧,对于提升编程能力有着不可忽视的价值。

 

 

  • 21
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值