1. 反转字符串
package com.yc.huke;
import com.sun.deploy.util.StringUtils;
import javax.xml.stream.events.Characters;
import java.util.Stack;
/**
* @Description: 用栈来反转字符串
* @Author: Andy
* @Date: 2023/6/15 11:59
*/
public class UseByStack1 {
public static void main(String[] args) {
String str1 = "I Love you, hello world!";
System.out.println(getReversalStrByStack(str1));
String str2 = "我爱中国";
System.out.println(getReversalStrByStack(str2));
}
public static String getReversalStrByStack(String str) {
char[] chars = str.toCharArray();
Stack<Character> stack = new Stack<>();
for (char aChar : chars) {
stack.push(aChar);
}
StringBuffer stringBuffer = new StringBuffer();
while (!stack.isEmpty()){
stringBuffer.append(stack.pop());
}
return stringBuffer.toString();
}
}
2. 判断括号是否匹配
写过xml标签或者html标签的,我们都知道"<"必须和最近的">"进行匹配,"[" 也必须和最近的 "]" 进行匹配。
比如:<abc[123]abc>这是符号相匹配的,如果是 <abc[123>abc] 那就是不匹配的。
对于 12<a[b{c}]>,我们分析在栈中的数据:遇到匹配正确的就消除
最后栈里的内容是空的就代表匹配成功,否则就匹配失败。
package com.yc.huke;
import java.util.Stack;
/**
* @Description:
* @Author: Andy
* @Date: 2023/6/15 19:29
*/
public class BracketMatch {
public static void main(String[] args) {
String str = "12<a[b{c}]>";
System.out.println(isBracketMatch(str));
String str1 = "<<abc>]>123";
System.out.println(isBracketMatch(str1));
// 特殊情况 右括号先出现
String str2 = "a]<<abc>]>123";
System.out.println(isBracketMatch(str2));
}
/**
* 判断字符串中括号是否匹配
*/
public static boolean isBracketMatch(String str) {
char[] chars = str.toCharArray();
Stack<Character> stack = new Stack<>();
for (char aChar : chars) {
// 如果是左边的括号则入栈
if (aChar == '<' || aChar == '[' || aChar == '{') {
stack.push(aChar);
} else if (aChar == '>' || aChar == ']' || aChar == '}') {
// 如果是右边的括号,则判断是否是空栈,若是空栈则说明先出现右括号,很明显不符合匹配规则
if (stack.isEmpty()) {
return false;
} else {
// 如果是不是空栈,那就读取栈顶元素,判断是否和当前要入栈的右括号成对,成对则将栈顶元素出栈,不成对则说明不符合匹配规则
Character top = stack.peek();
if ((aChar == '>' && top == '<') || (aChar == ']' && top == '[') || (aChar == '}' && top == '{')) {
stack.pop();
} else {
return false;
}
}
}
}
// 如果最终是空栈,则说明所有括号均成对抵消了,符合匹配规则
return stack.isEmpty();
}
}
3. 进制转换
package com.yc.huke;
import java.util.Stack;
/**
* @Description: 用栈来实现进制转换
* @Author: Andy
* @Date: 2023/6/15 20:21
*/
public class BaseConversion {
public static void main(String[] args) {
// 10进制转8进制
System.out.println("159的8进制是:"+handleBaseConversion(159));
}
public static int handleBaseConversion(int i){
int quotient;
int remainder;
Stack<Integer> stack = new Stack<>();
while (true){
quotient = i / 8;
remainder = i % 8;
stack.push(remainder);
if (quotient == 0){
break;
}
i = quotient;
}
StringBuffer stringBuffer = new StringBuffer();
while (!stack.isEmpty()){
stringBuffer.append(stack.pop());
}
String string = stringBuffer.toString();
return Integer.valueOf(string);
}
}
4. 表达式解析
1、人类如何解析算术表达式
如何解析算术表达式?或者换种说法,遇到某个算术表达式,我们是如何计算的:
①、求值 3+4-5
②、求值 3+4*5
2、计算机如何解析算术表达式
对于前面的表达式 3+4-5,我们人是有思维能力的,能根据操作符的位置,以及操作符的优先级别能算出该表达式的结果。但是计算机怎么算?
先看看什么是前缀表达式,中缀表达式,后缀表达式:这三种表达式其实就是算术表达式的三种写法,以 3+4-5为例
①、前缀表达式:操作符在操作数的前面,是一种没有括号的算术表达式,例如,- 1 + 2 3,它等价于1-(2+3)
②、中缀表达式:操作符在操作数的中间,这也是人类最容易识别的算术表达式 3+4-5
③、后缀表达式:操作符在操作数的后面,是一种没有括号的算术表达式,比如 34+5-,等价于(3+4)-5
上面我们讲的人是如何解析算术表达式的,也就是解析中缀表达式,这是人最容易识别的,但是计算机不容易识别,所以我们程序员如果要写计算表达式的程序,就应该先把中缀表达式在程序内部转换为前缀或后缀表达式!怎么转呢?
3、以后缀表达式为例子
后缀表达式,指的是不包含括号,运算符放在两个运算对象的后面,所有的计算按运算符出现的顺序,严格从左向右进行(不再考虑运算符的优先规则)。
由于后缀表达式的运算符在两个操作数的后面,那么计算机在解析后缀表达式的时候,只需要从左向右扫描,也就是只需要向前扫描,而不用回头扫描,遇到运算符就将运算符放在前面两个操作符的中间(这里先不考虑乘方类似的单目运算),一直运算到最右边的运算符,那么就得出运算结果了。这样实现的逻辑就简单了,在计算机底层的硬件实现就要简单的多了!
①、如何将中缀表达式转换为后缀表达式(逆波兰表达式)
对于这个问题,转换的规则如下:
1、初始化两个栈:运算符栈s1,中间结果栈s2
2、从左到右扫描表达式
3、遇到操作数压入s2
4、遇到运算符,比较其与s1栈顶运算符的优先级
a、如果s1栈不是空的,并且栈顶元素不是“( ”, 并且栈顶元素优先级大于或等于当前元素,就弹出s1栈顶的元素压入s2栈,再把当前元素压入s1
b、否则,就把当前元素直接压入栈s1
5、遇到括号
a、遇到左括号( ,直接压入s1栈
b、遇到右括号 ),依次弹出s1栈顶的元素,压入s2栈,知道遇到左括号为止,然后将这对括号丢掉
6、重复2-5,扫描完所有表达式
7、将s1中剩余的运算符压入s2栈
package com.yc.huke;
import java.util.Stack;
/**
* @Description: 中缀表达式转后缀表达式(逆波兰表达式)
* @Author Andy
* @Date 2023/6/16 18:34
*/
public class PolandExpression {
public static void main(String[] args) {
String expression = "(34.6+56.2+58)*63-69";
middleToSuffix(expression);
}
/**
* 中缀转后缀
* @param expression
*/
public static void middleToSuffix(String expression){
// 运算符栈
Stack<Character> s1 = new Stack<>();
// 中间结果栈
Stack<String> s2 = new Stack<>();
int length = expression.length();
int i;
for (i = 0; i < length; i++) {
if (Character.isDigit(expression.charAt(i))){
// 如果是数字则压入s2
// 但是有些数字不止一位,需要截取(截取到下一个非数字的字符)
String operand = "";
int j;
for (j = i+1; j < length; j++) {
if (!Character.isDigit(expression.charAt(j)) && '.' != expression.charAt(j)){
operand = expression.substring(i, j);
// 下次从j开始遍历
i = j-1;
break;
}
}
if (j == length){
// 说明此数字后面的所有内容是一个数,从i开始截取后面全部
operand = expression.substring(i);
i = j -1;
}
s2.push(operand);
} else if (isOperator(expression.charAt(i))) {
// 如果是运算符,比较其与s1栈顶运算符的优先级
if (!s1.isEmpty() && s1.peek() != '(' && priorityCompare(s1.peek(), expression.charAt(i)) >= 0){
// 如果s1栈不是空的,并且栈顶元素不是“( ” ,并且优s1栈顶元素优先级大于等于当前元素,则将s1的栈顶元素弹出压入s2,再把当前元素压入s1
s2.push(String.valueOf(s1.pop()));
}
// 否则,就直接压入栈s1
s1.push(expression.charAt(i));
} else if (expression.charAt(i) == '(') {
// 遇到左括号( ,直接压入s1栈
s1.push(expression.charAt(i));
} else if (expression.charAt(i) == ')') {
while (true){
if (!s1.isEmpty() && s1.peek() != '('){
s2.push(String.valueOf(s1.pop()));
}else if (s1.peek() == '('){
s1.pop();
break;
}
}
}
}
// 扫描完表达式所有内容后,将s1中剩余的运算符压入s2栈
while (!s1.isEmpty()){
s2.push(String.valueOf(s1.pop()));
}
while (!s2.isEmpty()){
System.out.print(s2.pop() + " ");
}
}
/**
* 比较优先级
* @param before
* @param after
* @return
*/
private static int priorityCompare(char before, char after) {
if (before == '+' || before == '-') {
if (after == '*' || after == '/') {
return -1;
}
} else if (before == '*' || before == '/') {
if (after == '+' || after == '-') {
return 1;
}
}
return 0;
}
/**
* 是否是运算符(只支持 + - * /)
* @param character
* @return
*/
public static boolean isOperator(Character character) {
if (character == '+' || character == '-' || character == '*' || character == '/') {
return true;
}
return false;
}
}
②、在后缀表达式的基础上实现计算
只需要从左向右扫描,遇到运算符就将运算符放在前面两个操作符的中间,一直运算到最右边的运算符,那么就得出运算结果了。
package com.yc.huke;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
/**
* @Description: 中缀表达式转后缀表达式(逆波兰表达式)
* @Author Andy
* @Date 2023/6/16 18:34
*/
public class PolandExpression {
public static void main(String[] args) {
String expression = "(34.6+56.2+58)*63-69";
System.out.println(middleToSuffix(expression));
}
/**
* 中缀转后缀 再计算结果输出
* @param expression
*/
public static String middleToSuffix(String expression){
// 运算符栈
Stack<Character> s1 = new Stack<>();
// 中间结果栈
Stack<String> s2 = new Stack<>();
Double result = null;
List<Double> doubles = new ArrayList<>();
int length = expression.length();
int i;
for (i = 0; i < length; i++) {
if (Character.isDigit(expression.charAt(i))){
// 如果是数字则压入s2
// 但是有些数字不止一位,需要截取(截取到下一个非数字的字符)
String operand = "";
int j;
for (j = i+1; j < length; j++) {
if (!Character.isDigit(expression.charAt(j)) && '.' != expression.charAt(j)){
operand = expression.substring(i, j);
// 下次从j开始遍历
i = j-1;
break;
}
}
if (j == length){
// 说明此数字后面的所有内容是一个数,从i开始截取后面全部
operand = expression.substring(i);
i = j -1;
}
s2.push(operand);
} else if (isOperatorChar(expression.charAt(i))) {
// 如果是运算符,比较其与s1栈顶运算符的优先级
if (!s1.isEmpty() && s1.peek() != '(' && priorityCompare(s1.peek(), expression.charAt(i)) >= 0){
// 如果s1栈不是空的,并且栈顶元素不是“( ” ,并且优s1栈顶元素优先级大于等于当前元素,则将s1的栈顶元素弹出压入s2,再把当前元素压入s1
s2.push(String.valueOf(s1.pop()));
Double calculate = calculate(s2.pop().charAt(0), Double.valueOf(s2.pop()), Double.valueOf(s2.pop()));
s2.push(String.valueOf(calculate));
}
// 否则,就直接压入栈s1
s1.push(expression.charAt(i));
} else if (expression.charAt(i) == '(') {
// 遇到左括号( ,直接压入s1栈
s1.push(expression.charAt(i));
} else if (expression.charAt(i) == ')') {
while (true){
if (!s1.isEmpty() && s1.peek() != '('){
s2.push(String.valueOf(s1.pop()));
Double calculate = calculate(s2.pop().charAt(0), Double.valueOf(s2.pop()), Double.valueOf(s2.pop()));
s2.push(String.valueOf(calculate));
}else if (s1.peek() == '('){
s1.pop();
break;
}
}
}
}
// 扫描完表达式所有内容后,将s1中剩余的运算符压入s2栈
while (!s1.isEmpty()){
s2.push(String.valueOf(s1.pop()));
}
Double calculate = calculate(s2.pop().charAt(0), Double.valueOf(s2.pop()), Double.valueOf(s2.pop()));
s2.push(String.valueOf(calculate));
return s2.peek();
}
/**
* 比较优先级
* @param before
* @param after
* @return
*/
private static int priorityCompare(char before, char after) {
if (before == '+' || before == '-') {
if (after == '*' || after == '/') {
return -1;
}
} else if (before == '*' || before == '/') {
if (after == '+' || after == '-') {
return 1;
}
}
return 0;
}
/**
* 是否是运算符(只支持 + - * /)
* @param character
* @return
*/
public static boolean isOperatorChar(Character character) {
if (character == '+' || character == '-' || character == '*' || character == '/') {
return true;
}
return false;
}
public static boolean isOperatorStr(String str) {
if ("+".equals(str) || "-".equals(str) || "*".equals(str) || "/".equals(str)) {
return true;
}
return false;
}
public static Double calculate(Character c, Double after, Double before){
Double result = 0d;
switch (c){
case '+':
result = before + after;
break;
case '-':
result = before - after;
break;
case '*':
result = before * after;
break;
case '/':
result = before / after;
// TODO 分子不为0
break;
}
return result;
}
}
以上代码不能识别负数的情况,下面是一个适配负数计算的代码,同时也是牛客算法题 www.nowcoder.com/practice/95… 的一种解法:
import java.util.Scanner;
import java.util.Stack;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String expression = in.nextLine();
Stack<Character> s1 = new Stack<>();
Stack<String> s2 = new Stack<>();
String number = "";
// 遍历表达式的每个字符,将中缀转化成后缀表达式
for (int i = 0; i < expression.length(); i++) {
if (isOperator(expression.charAt(i), i, expression)) {
while (!s1.isEmpty() && s1.peek() != '(' &&
priorityCompare(s1.peek(), expression.charAt(i)) >= 0) {
s2.add(String.valueOf(s1.pop()));
}
s1.add(expression.charAt(i));
} else if (expression.charAt(i) == '(') {
s1.add(expression.charAt(i));
} else if (expression.charAt(i) == ')') {
while (s1.peek() != '(') {
s2.add(String.valueOf(s1.pop()));
}
s1.pop();
} else if (Character.isDigit(expression.charAt(i))
|| (expression.charAt(i) == '-' && (i == 0 ||
isNegativeNumberStart(i, expression)))) {
// 当前字符是数字或者负号时
number = String.valueOf(expression.charAt(i));
for (int j = i + 1; j < expression.length(); j++) {
if (!Character.isDigit(expression.charAt(j))) {
break;
}
number = number + String.valueOf(expression.charAt(j));
i++;
}
s2.add(number);
}
}
while (!s1.isEmpty()) {
s2.add(String.valueOf(s1.pop()));
}
// 此时s2里就是我们已经转化好的后缀表达式,但是由于要从栈尾开始遍历,但栈只能从栈顶开始遍历,
// 所以这里再遍历s2换到新的栈s3,此时s3的栈顶就是s2的栈尾,正常遍历s3就可以了(当然也可以使用双端栈就不用这么麻烦了)
Stack<String> s3 = new Stack<>();
while (!s2.isEmpty()) {
s3.add(s2.pop());
}
Stack<Integer> s4 = new Stack<>();
while (!s3.isEmpty()) {
// 如果当前元素长度大于1,则必定是数字,比如负数-1 或 多位数11
// 如果当前元素长度是1且isDigit==true,必定是数字
if (s3.peek().length() > 1 || (s3.peek().length() == 1 &&
Character.isDigit(s3.peek().charAt(0)))) {
s4.add(Integer.parseInt(s3.pop()));
} else {
// 如果遇到运算符,则弹出栈里前两位元素进行运算
s4.add(calculate(s4.pop(), s4.pop(), s3.pop().charAt(0)));
}
}
System.out.println(s4.peek());
}
// 比较运算符的优先级
public static int priorityCompare(char before, char after) {
if (before == '+' || before == '-') {
if (after == '*' || after == '/') {
return -1;
}
} else if (before == '*' || before == '/') {
if (after == '+' || after == '-') {
return 1;
}
}
return 0;
}
// 判断当前字符是否为运算符
public static boolean isOperator(char c, int index, String expr) {
if (c == '+' || c == '*' || c == '/') {
return true;
}
// 如果是-,且不是首位,且不是负号,则说明是减号
if (c == '-' && (index > 0 && !isNegativeNumberStart(index, expr))) {
return true;
}
return false;
}
/**
* 判断当前-字符是否为负号
* @param index 当前字符的下标
* @param expr 表达式
* @return
*/
public static boolean isNegativeNumberStart(int index, String expr) {
// 如果-在首位,那么必定是负号而不是减号
if (index == 0) {
return true;
}
// 既然不是首位,那么获取前一位的字符
char prevChar = expr.charAt(index - 1);
// 如果前一位是以下字符则说明是负号而不是减号
// 比如:1+(-1)、1+-1、1--1、1*-1、1/-1 这些都是负号前一位字符的特征
return prevChar == '(' || prevChar == '+' || prevChar == '-' ||
prevChar == '*' || prevChar == '/';
}
// 计算
public static int calculate(int first, int second, char operator) {
int result = 0;
switch (operator) {
case '+':
result = first + second;
break;
case '-':
// 注意这里是后一位 减去 前一位
result = second - first;
break;
case '*':
result = first * second;
break;
case '/':
// 注意这里是后一位 除以 前一位
result = second / first;
break;
}
return result;
}
}
以上就是栈这种数据结构的四种常见用法,当然栈的运用远不止以上四种,欢迎补充。