数据结构:栈
介绍
栈和队列一样是一种特殊的线性表结构,它的特点是先进后出(first in last out)。下面有一张图来描述栈
栈有两个常用方法
- pop(),出栈,从尾部弹出元素
- push(int value),压栈,把数据压入栈中
实现栈
实现栈的方式有多种,我列举2种:一种是用数组来实现栈,还有一种是用链表来实现栈
- ArrayStack(数组)
- LinkedListStack(链表)
ArrayStack
package DataStructure;
public class ArrayStackDemo {
public static void main(String[] args) {
ArrayStack stack= new ArrayStack(3);
System.out.println("\t压栈");
stack.push(120);
stack.push(220);
stack.push(221);
stack.push(449);
System.out.println("\t打印栈元素");
stack.print();
try {
System.out.println("\t弹出栈元素");
System.out.println("弹出的元素:"+stack.pop());
}catch(Exception e) {
System.out.println(e.getMessage());
}
System.out.println("\t弹出后的元素");
stack.print();
}
}
class ArrayStack{
private int MaxSize;
private int top;//标号,默认为-1
private int[] array;
//构造器
public ArrayStack(int size) {
MaxSize = size;
top = -1;
array = new int[MaxSize];
}
//判空
public boolean isempty() {
return top == -1;
}
//判满
public boolean isfull() {
//判断满,数组最后一位下标MaxSize-1
return top == MaxSize -1;
}
//入栈,压栈
public void push(int value) {
if(isfull()) {
System.out.println("已满,无法添加元素:"+value);
return;
}
top++;//压栈,先+1
array[top] = value;//开始赋值
}
//出栈,弹出
public int pop() {
if(isempty()) {
throw new RuntimeException("栈为空,无法弹出元素");
}
int value = array[top];
top--;
return value;
}
//遍历栈,从上往下
public void print() {
if(isempty()) {
System.out.println("栈为空");
return;
}
for(int i=top;i>=0;i--) {
System.out.printf("Stack[%d] = %d\n",i,array[i]);
}
}
}
LinkedListStack
package DataStructure;
public class LinkedListStackDemo {
public static void main(String[] args) {
LinkedListStack stack= new LinkedListStack(3);
System.out.println("\t压栈");
stack.push(120);
stack.push(220);
stack.push(221);
stack.push(449);
System.out.println("\t打印栈元素");
stack.print();
try {
System.out.println("\t弹出栈元素");
System.out.println("弹出的元素:"+stack.pop());
}catch(Exception e) {
System.out.println(e.getMessage());
}
System.out.println("\t弹出后的元素");
stack.print();
}
}
class StackNode{
int value;
StackNode next;
public StackNode(int num) {
value = num;
next = null;
}
@Override
public String toString() {
return value+"";
}
}
class LinkedListStack{
private int MaxSize;
private StackNode head;
//构造器
public LinkedListStack(int size) {
MaxSize = size;
head =null;
}
//容器中元素个数
public int size() {
int count =0;
StackNode cur = head;
while(cur != null) {
count ++;
cur = cur.next;
}
return count;
}
//判空
public boolean isempty() {
return head == null;
}
//判满
public boolean isfull() {
return size() == MaxSize;
}
//压栈
public void push(int value) {
StackNode node = new StackNode(value);
if(isempty()) {
head = node;
return;
}
if(isfull()) {
System.out.println("已满,无法添加元素:"+value);
return;
}
StackNode cur = head;
while(cur.next != null) {
cur = cur.next;
}
cur.next = node;
}
//出栈
public StackNode pop() {
if(isempty()) {
throw new RuntimeException("栈为空,无法弹出元素");
}
StackNode pre = head;
if(size()==1) {
head = null;
return pre;
}
StackNode cur = pre.next;
while(cur.next != null) {
pre = pre.next;
cur = cur.next;
}
StackNode temp = cur;
pre.next = null;
return temp;
}
//从上到下遍历
public void print() {
if(isempty()) {
System.out.println("栈为空");
return;
}
LinkedListStack container = new LinkedListStack(size());
StackNode cur = head;
while(cur!=null) {
container.push(cur.value);
cur = cur.next;
}
while(container.size()>0) {
System.out.println(container.pop());
}
}
}
用栈来实现计算器
本计算器支持正整数的加减乘除运算
包含的知识
- 字符串转化为字符数组
- 字符与double类型数据间的转换
- 栈的pop与push
- 链表栈(本计算器是通过链表栈来实现的)
- 优先级判断
输入数字与符号给计算器时,计算器会考虑符号的优先级,例如乘号优先级>减号的优先级;
实现步骤
- 当我们从控制台输入一连串字符时,我们的程序需要对字符串进行拆解,
- for循环区分数字与符号,然后将内容分别压入数字栈与符号栈;
-
使用while循环遍历符号栈,当符号栈为空时,运算终止
-
循环时,我们分别从符号栈和数字栈中pop出元素:num1, num2, operator; 计算完后再push入数字栈中(即计算完的值代替了原来的两个num);通过循环进行最终得出最后的答案。
- 进行计算之前要先判断优先级,例如:“6X6-5”,我们需要先看一下符号栈后一个符号优先级是否大于即将pop的;如果是,我们要将他们数字与符号调换一下顺序:5-6X6 ,由于另一个符号是减号,我们要将减号改为+,其中一个数字乘上-1:-5+6X6
- 直到循环结束,numStack中的最后一个元素就是最终的答案.
本计算器使用上面的链表栈来实现的,其中节点稍作改动,节点接收double类型数据,因为我们不知道用户输入的数字个数具体有多少,所以此链表栈没有isfull(判满)。
在链表栈中,我们还增添了peek方法,用于查看倒数第2个符号是什么(判断优先级)
package DataStructure;
import java.util.Arrays;
import java.util.Scanner;
public class StackCalculatorDemo {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("please input the operation equation: ");
String expression = input.next();
Calculator cal = new Calculator();
cal.process(expression);
input.close();
}
}
class NumNode{
double value;
NumNode next;
public NumNode(double num) {
value = num;
next = null;
}
@Override
public String toString() {
return value+"";
}
}
class Stack{
private NumNode head = null;
//容器中元素个数
public int size() {
int count =0;
NumNode cur = head;
while(cur != null) {
count ++;
cur = cur.next;
}
return count;
}
//判空
public boolean isempty() {
return head == null;
}
//压栈
public void push(double value) {
NumNode node = new NumNode(value);
if(isempty()) {
head = node;
return;
}
NumNode cur = head;
while(cur.next != null) {
cur = cur.next;
}
cur.next = node;
}
//出栈
public NumNode pop() {
if(isempty()) {
throw new RuntimeException("栈为空,无法弹出元素");
}
NumNode pre = head;
if(size()==1) {
head = null;
return pre;
}
NumNode cur = pre.next;
while(cur.next != null) {
pre = pre.next;
cur = cur.next;
}
NumNode temp = cur;
pre.next = null;
return temp;
}
//查看倒数第2个元素,但是不弹出
public NumNode peek() {
if(isempty()) {
throw new RuntimeException("栈为空,无法弹出元素");
}
NumNode pre = head;
if(size()==1) {
return pre;
}
NumNode cur = pre.next;
while(cur.next != null) {
pre = pre.next;
cur = cur.next;
}
return pre;
}
//从上到下遍历
public void print() {
if(isempty()) {
System.out.println("栈为空");
return;
}
Stack container = new Stack();
NumNode cur = head;
while(cur!=null) {
container.push(cur.value);
cur = cur.next;
}
while(container.size()>0) {
System.out.println(container.pop());
}
}
}
class Calculator{
public void process(String question) {
// String question = "7*88+6*6-5/10";
//将输入的字符串转为数组
char[] array = question.toCharArray();
// System.out.println(Arrays.toString(array));
//创建2个栈,一个用于存储符号,另一个用于存储数字
Stack operStack = new Stack();
Stack numStack = new Stack();
//number字符串用于存储多位数字的字符
String number ="";
//for循环遍历一遍
for(char i: array) {
//通过字符判断是否为数字,
if(Character.isDigit(i)) {
number += i;
}
else {
numStack.push(Integer.valueOf(number));
number="";
operStack.push(i);
}
}
//由于最后一个是数字,for循环最后一遍未压入数字栈,需要手动压入
numStack.push(Integer.valueOf(number));
//当最后一个符号栈遍历完,运算结束
while(!operStack.isempty()) {
char nextOpe = (char)operStack.peek().value;
//判断优先级
if(priority(nextOpe) == 1) {
// System.out.println("数字");
// numStack.print();
// System.out.println("符号");
// operStack.print();
double num1 = numStack.pop().value;
double num2 = numStack.pop().value;
double num3 = numStack.pop().value;
double ope1 = operStack.pop().value;
//看下一个是否是‘-’号,
if((char) ope1 == '-') {
//更改符号,改变数字正负
ope1 = (int)'+';
num1*=-1;
}
double ope2 = operStack.pop().value;
numStack.push(num1);
numStack.push(num3);
numStack.push(num2);
operStack.push(ope1);
operStack.push(ope2);
}
double num1 = numStack.pop().value;
double num2 = numStack.pop().value;
char ope = (char)operStack.pop().value;
Double rst = cal(num1, num2, ope);
numStack.push(rst);
}
System.out.println("答案是:"+numStack.pop());
}
//计算过程,使用switch case
public double cal(double num1, double num2, char ope) {
double rst =0;
switch (ope) {
case '*':
rst = num2*num1;
break;
case '/':
rst = num2*1.0/num1;
break;
case '+':
rst = num2+num1;
break;
case '-':
rst = num2-num1;
break;
default:
break;
}
return rst;
}
//优先级判断
public int priority(char ope) {
if(ope == '*' || ope=='/') {
return 1;
}
else if(ope == '+' || ope =='-') {
return 0;
}
else {
return -1;
}
}
}
前、中、后缀表达式(波兰、逆波兰计算器)
式子:3+(7+8)x4-6
按照我们常规的计算方法,我们从左往右计算,看到括号先计算括号里面的数,再根据优先级算其他的数字;这就是所谓的中缀表达式
前缀表达式是把符号置于数字的左侧,以上面式子为例:+3-x+7846。当计算时,我们从右往左把数字压入栈中,顺序依次是:6->4->8->7(此时7在栈顶);接着我们依次从栈中弹出2个元素:7和8,然后读取第一个符号进行计算。计算结果再压入栈中,依次循环,如果下一个是数字,压入栈中直到读取完最后一个符号。
前缀表达式又称波兰表达式,是波兰逻辑学家J·Lukasiewicz所发现的。
因为前缀表达式是从右往左扫描,数字和符号有自己的讲究,不能乱放(要按照优先级:括号>乘号>加号)。
后缀表达式被称为逆波兰表达式。和前缀表达式相似,后缀表达式的符号在数字的后面;
继续以上面的式子为例:378+4x+6-。
讲了这么多,既然中缀表达式与我们生活中使用的计算方式一样,为什么还要学前缀与后缀表达式呢?
由于计算机是线性思维,它无法立刻发现括号等,所以使用中缀表达式不好操作;相反用后缀或前缀表达式可以解决这个问题,计算机只用从前往后读,或从后往前读就行,这样可以方便检索运算。
后缀表达式实现(逆波兰表达式)
- 后缀表达式从左向右扫描,如果遇到数字,将数字压入栈中
- 倘若遇到运算符,弹出2个数字,进行运算,运算后的结果压入栈
- 下一个是数字,再压入栈中,直到遇到运算符号
- 依次循环,向后遍历,直到最后一个符号被遍历完
- 最终存在栈中的元素就是计算的结果
先从中缀转后缀
package DataStructure;
import java.util.Stack;
public class PostfixDemo {
public static void main(String[] args) {
PolanCal cal = new PolanCal("3+(7+8)*4-6");
cal.ToPostExpression();
System.out.println(cal.output);
}
}
class PolanCal{
String input;//中缀表达式
String output = "";//后缀表达式
Stack<Character> TempStack = new Stack<>(); //用于暂时存储中缀转后缀的符号
PolanCal(String input){
this.input = input;
}
public void ToPostExpression() {
//3+(7+8)*4-6
//378+4*+6-
char[] words = this.input.toCharArray();
for(char i:words) {
switch (i) {
case '+':
case '-':
Prio(i,1);//优先级判断
break;
case '*':
case '/':
Prio(i,2);
break;
case '(':
TempStack.push(i);
break;
case ')':
while(!TempStack.isEmpty()) {
//遍历,将括号间的符号加入到output
char operator = TempStack.pop();
if(operator =='(') {
break;
}
else {
output += operator;
}
}
break;
default:
//读取的是数字就直接加入到output字符串中
output += i;
break;
}
}
//遍历完后,将暂时栈里面的符号弹出,存入到output
while(!TempStack.isEmpty()) {
output += TempStack.pop();
}
}
public void Prio(char oper1, int priority1) {
while(!TempStack.isEmpty()) {
char onTop = TempStack.pop();//弹出位于栈顶部的符号
if(onTop =='(') {//如果栈顶的是前括号,就不弹出
TempStack.push(onTop);
break;//将输入的符号压入栈中
}
else {
//要比较栈顶的符号与输入的符号哪个优先级大
int priority2;
if(onTop =='+'||onTop=='-') {
priority2 = 1;
}
else {
priority2 = 2;
}
//栈顶符号优先级小于输入的优先级
//符号还原,不pop
if(priority2 < priority1) {
TempStack.push(onTop);
break;
}
else {
output += onTop;
}
}
}
TempStack.push(oper1);
}
}
实现中缀转后缀之后,现在要实现计算过程
其中有些代码稍作了改动
- 原来的中缀无法转多位数字,现在支持多位中缀转后缀:“3+(7+89)*4-66+8”
- 为了处理数据方便,后缀表达式各符号用空格分开
package DataStructure;
import java.util.Arrays;
import java.util.Stack;
public class PostfixDemo {
public static void main(String[] args) {
PolanCal cal = new PolanCal("3+(7+89)*4-66+8");
cal.ToPostExpression();
System.out.println("转逆波兰后缀表达式:"+cal.output);
cal.StartCal();
}
}
class PolanCal {
String input;// 中缀表达式
String output = "";// 后缀表达式
Stack<Character> TempStack = new Stack<>(); // 用于暂时存储中缀转后缀的符号
PolanCal(String input) {
this.input = input;
}
public void ToPostExpression() {
// 3+(7+8)*4-6
// 378+4*+6-
char[] words = this.input.toCharArray();
for (int i=0;i<words.length;i++) {
char word =words[i];
switch (word) {
case '+':
case '-':
Prio(word, 1);// 优先级判断
break;
case '*':
case '/':
Prio(word, 2);
break;
case '(':
TempStack.push(word);
break;
case ')':
while (!TempStack.isEmpty()) {
char operator = TempStack.pop();
if (operator == '(') {
break;
} else {
output += operator + " ";
}
}
break;
default:
// 读取的是数字就直接加入到output字符串中
output += word;
if(i<words.length-1&&!Character.isDigit(words[i+1])) {
output +=" ";
}
break;
}
}
// 遍历完后,将暂时栈里面的符号弹出,存入到output
output+=" ";
while (!TempStack.isEmpty()) {
output += TempStack.pop();
}
}
public void Prio(char oper1, int priority1) {
while (!TempStack.isEmpty()) {
char onTop = TempStack.pop();// 弹出位于栈顶部的符号
if (onTop == '(') {// 如果栈顶的是前括号,就不弹出
TempStack.push(onTop);
break;// 将输入的符号压入栈中
} else {
// 要比较栈顶的符号与输入的符号哪个优先级大
int priority2;
if (onTop == '+' || onTop == '-') {
priority2 = 1;
} else {
priority2 = 2;
}
// 栈顶符号优先级小于输入的优先级
// 符号还原,不pop
if (priority2 < priority1) {
TempStack.push(onTop);
break;
} else {
output += onTop + " ";
}
}
}
TempStack.push(oper1);
}
public void StartCal() {
String[] nums = output.split(" ");
Stack<Double> numStack = new Stack<>();
for (String i : nums) {
if (isNumeric(i)) {
numStack.push(Double.valueOf(i));
} else {
double num1 = numStack.pop();
double num2 = numStack.pop();
double rst =0;
if(i.equals("+")) {
rst = num2+num1;
}else if(i.equals("-")) {
rst = num2-num1;
}else if(i.equals("*")) {
rst = num2*num1;
}else if(i.equals("/")) {
rst = num2*1.0/num1;
}
numStack.push(Double.valueOf(rst));
}
}
System.out.println("最终计算结果:"+numStack.pop());
}
public static boolean isNumeric(String str){
for (int i = str.length();--i>=0;){
if (!Character.isDigit(str.charAt(i))){
return false;
}
}
return true;
}
}