栈
在使用一个东西之前,得清楚它是什么,才能知道它的用途以及该如何使用。
栈的简介
栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶(top),相对地,把另一端称为栈底(bottom)。向一个栈插入新元素又称作进栈、入栈或压栈(push),它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈(pop),它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。栈具有记忆作用,对栈的插入与删除操作中,不需要改变栈底指针。
进栈与出栈操作如下图:
应用
通过上述定义可以知道,栈结构具有后进先出的固有特性,所以可以利用其独特性进行实现应用,如:解决逆波兰表达式问题,括号匹配问题。
逆波兰表达式
表达式一般由操作数(Operand)、运算符(Operator)组成,例如算术表达式中,通常把运算符放在两个操作数的中间,这称为中缀表达式(Infix Expression),如A+B。
波兰数学家Jan Lukasiewicz提出了另一种数学表示法,它有两种表示形式:
把运算符写在操作数之前,称为波兰表达式(Polish Expression)或前缀表达式(Prefix Expression),如+AB;把运算符写在操作数之后,称为逆波兰表达式(Reverse Polish Expression)或后缀表达式(Suffix Expression),如AB+;前后缀表达式的出现是为了方便计算机处理,它的运算符是按照一定的顺序出现,所以求值过程中并不需要使用括号来指定运算顺序,也不需要考虑运算符号(比如加减乘除)的优先级。
实现逆波兰式的算法,难度并不大,但为什么要将看似简单的中缀表达式转换为复杂的逆波兰式?原因就在于这个简单是相对人类的思维结构来说的,对计算机而言中序表达式是非常复杂的结构。相对的,逆波兰式在计算机看来却是比较简单易懂的结构。因为计算机普遍采用的内存结构是栈式结构,它执行先进后出的顺序。
本题来自力扣第150题:150. 逆波兰表达式求值 - 力扣(LeetCode)
题目概要:
乍一看题目给人一种很蒙的感觉,可以结合样例来看
代码实现
Java
注意,Java自带有栈,所以这里直接可以使用。
class Solution {
public int evalRPN(String[] tokens) {
//初始化一个栈
Stack<Integer> stack = new Stack<Integer>();
int n = tokens.length;
for(String token:tokens){
if(isNumber(token)){
stack.push(Integer.parseInt(token));
}else{
int num2 = stack.pop();
int num1 = stack.pop();
switch(token){
case "+":
stack.push(num1+num2);
break;
case "-":
stack.push(num1-num2);
break;
case "*":
stack.push(num1*num2);
break;
case "/":
stack.push(num1/num2);
}
}
}
//此时通过一些列的计算,答案便是栈顶元素
return stack.pop();
}
public boolean isNumber(String s){
return !(s.equals("+")||s.equals("-")||s.equals("*")||s.equals("/"));
}
}
完整逻辑代码
public class Main {
public static void main (String[] args) {
Scanner sc = new Scanner(System.in);
//读取字符串
String s = sc.next();
//将读取到的字符串分割成字符串数组,方便后续操作
String[] tokens = s.split("");
// 创建Solution对象
Solution solution = new Solution();
//调用该对象中的方法,并得到运算结果
int ret = solution.evalRPN(tokens);
//输出运算结果
System.out.println(ret);
}
}
class Solution {
public int evalRPN(String[] tokens) {
Stack<Integer> stack = new Stack<Integer>();
int n = tokens.length;
for(String token:tokens){
if(isNumber(token)){
stack.push(Integer.parseInt(token)); //将字符串转换为数字对象
}else{
int num2 = stack.pop();
int num1 = stack.pop();
switch(token){
case "+":
stack.push(num1+num2);
break;
case "-":
stack.push(num1-num2);
break;
case "*":
stack.push(num1*num2);
break;
case "/":
stack.push(num1/num2);
}
}
}
return stack.pop();
}
public boolean isNumber(String s){ //判断该字符串是否为数字
return !(s.equals("+")||s.equals("-")||s.equals("*")||s.equals("/"));
}
}
运行结果
括号匹配
括号匹配,顾名思义,对应的左括号与之对应的右括号相匹配(如,'(‘和’)‘匹配,’[‘和’]‘匹配,’{‘和’}'匹配)。
代码实现思想:
用一个栈进行存储左括号,如果遇到右括号则将其对应的左括号弹出,如果右括号在左括号前面或者两个括号数量不相等,则匹配失败,否则匹配成功。
代码实现
public class Main {
public static void main (String[] args) {
Scanner sc = new Scanner(System.in);
//创建字符串
String s = sc.next();
// 创建栈
Stack stack = new Stack<Character>();
// 判断括号是否匹配
boolean flag = true;
for(int i = 0;i<s.length();i++){
char ch = s.charAt(i);
if(ch=='('||ch=='{'||ch=='['){
stack.push(ch);
}else{
if(stack.isEmpty()||(char)stack.peek()!=getLeft(ch)){ //如果此时栈为空或栈顶元素右括号不相等,则说明不匹配
flag = false;
}
// 如果匹配则弹出
stack.pop();
}
}
if(flag&&stack.isEmpty()){ // 如果括号匹配并且栈为空
System.out.println("括号匹配!");
}else{
System.out.println("括号不匹配!");
}
}
public static char getLeft(char ch){ //通过右括号返回其匹配的左括号
if(ch==')'){
return '(';
}else if(ch==']'){
return '[';
}
return '{';
}
}
运行结果
总结
栈是一种后进先出的数据结构,合理的利用其特性可以很方便的解决一些问题。