数据结构:栈

数据结构:栈

介绍

栈和队列一样是一种特殊的线性表结构,它的特点是先进后出(first in last out)。下面有一张图来描述栈
在这里插入图片描述

栈有两个常用方法

  • pop(),出栈,从尾部弹出元素
  • push(int value),压栈,把数据压入栈中

实现栈

实现栈的方式有多种,我列举2种:一种是用数组来实现栈,还有一种是用链表来实现栈

  1. ArrayStack(数组)
  2. 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());
		}
	}
}

在这里插入图片描述

用栈来实现计算器

本计算器支持正整数的加减乘除运算

包含的知识
  1. 字符串转化为字符数组
  2. 字符与double类型数据间的转换
  3. 栈的pop与push
  4. 链表栈(本计算器是通过链表栈来实现的)
  5. 优先级判断

输入数字与符号给计算器时,计算器会考虑符号的优先级,例如乘号优先级>减号的优先级;

实现步骤
  1. 当我们从控制台输入一连串字符时,我们的程序需要对字符串进行拆解,
  2. for循环区分数字与符号,然后将内容分别压入数字栈与符号栈;

在这里插入图片描述

  1. 使用while循环遍历符号栈,当符号栈为空时,运算终止

  2. 循环时,我们分别从符号栈和数字栈中pop出元素:num1, num2, operator; 计算完后再push入数字栈中(即计算完的值代替了原来的两个num);通过循环进行最终得出最后的答案。

在这里插入图片描述

  1. 进行计算之前要先判断优先级,例如:“6X6-5”,我们需要先看一下符号栈后一个符号优先级是否大于即将pop的;如果是,我们要将他们数字与符号调换一下顺序:5-6X6 ,由于另一个符号是减号,我们要将减号改为+,其中一个数字乘上-1:-5+6X6

在这里插入图片描述

  1. 直到循环结束,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-

讲了这么多,既然中缀表达式与我们生活中使用的计算方式一样,为什么还要学前缀与后缀表达式呢?

由于计算机是线性思维,它无法立刻发现括号等,所以使用中缀表达式不好操作;相反用后缀或前缀表达式可以解决这个问题,计算机只用从前往后读,或从后往前读就行,这样可以方便检索运算。

后缀表达式实现(逆波兰表达式)
  1. 后缀表达式从左向右扫描,如果遇到数字,将数字压入栈中
  2. 倘若遇到运算符,弹出2个数字,进行运算,运算后的结果压入栈
  3. 下一个是数字,再压入栈中,直到遇到运算符号
  4. 依次循环,向后遍历,直到最后一个符号被遍历完
  5. 最终存在栈中的元素就是计算的结果

先从中缀转后缀

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);
	}
	
	
}

实现中缀转后缀之后,现在要实现计算过程

其中有些代码稍作了改动

  1. 原来的中缀无法转多位数字,现在支持多位中缀转后缀:“3+(7+89)*4-66+8”
  2. 为了处理数据方便,后缀表达式各符号用空格分开
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;
	}

}

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值