数据结构与算法- 前缀、中缀、后缀表达式

1、前缀、中缀、后缀表达式

1.1 介绍

  • 前缀表达式 (波兰表达式): 运算符位于操作数之前,(1+2)*3-4 对应的前缀表达式为 - * + 1 2 3 4
  • 中缀表达式 : 常见的运算表达式,如(1+2)*3-4。中缀表达式的求值是我们人最熟悉的,但是对计算机来说却不好操作,因此,在计算结果时,往往会将中缀表达式转成其他表达式来操作(常见的是转成后缀表达式)
  • 后缀表达式 (逆波兰表达式): 运算符位于操作数之后, (1+2)*3-4对应的后缀表达式为1 2 + 3 * 4 -

在后面要注意区别 :前缀和后缀表达式的运算中栈顶元素和次栈顶元素,计算时的前后位置。

1.2 前缀表达式在计算机求值

1.2.1 流程、举例

  • 从右至左扫描表达式,遇到数字时,将数字压入栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素和次栈顶元素),并将结果入栈;重复上述过程直到表达式的最左端,最后运算得出的值即为表达式的结果。

  • 例如:(1+2)*3-4对应的前缀表达式就是 - * + 1 2 3 4,求值步骤如下:

  1. 从右至左扫描,将4,3,2,1压入栈。
  2. 遇到 + 运算符,因此弹出1和2(1为栈顶元素,2为次栈顶元素),计算出1+2的值,得到3,再将3入栈。
  3. 接下来是 * 运算符,因此弹出3和3,计算出3 * 3=9,将9入栈。
  4. 最后是 - 运算符,计算出9-4的值,得到5,因此最终结果是5。

1.2.2 代码实现

注意点:给出的波兰表达式要用空格隔开,并且表达式正确,支持多位数

public class Poland {
	public static void main(String[] args) {
		//给出波兰表达式
		String Expression="- * + 1 2 3 4";
		List<String> LISTNUM=getList(Expression);
		System.out.println(LISTNUM);
		int result=Calculate(LISTNUM);
		System.out.println("波兰表达式的结果为:"+result);
	}
	
	/**
	 * 将波兰表达式发到list集合中
	 * @return
	 */
	public static List<String> getList(String Expression){
		String[] split=Expression.split(" "); //用空格分割
		List<String> list=new ArrayList<String>();
		//循环
		for(String item : split) {
			list.add(item);
		}
		return list;
	}
	
	/**
	 * 求值
	 * @param list
	 * @return
	 */
	public static int Calculate(List<String> list) {
		//建栈
		Stack<String> stack=new Stack<String>();
		for(int i=list.size()-1;i>=0;i--) {
			String ele=list.get(i);
			//这里使用正则表达式,匹配的是多位数
			if(ele.matches("\\d+")) {
				//入栈
				stack.push(ele);
			}else {
				//先pop两个数进行计算,在入栈【这里是先pop出来的数与后pop出来的数运算】
				int num1=Integer.parseInt(stack.pop());
				int num2=Integer.parseInt(stack.pop());
				System.out.println("num1="+num1+" 和   num2="+num2+"进行"+ele+"运算");
				int res=0;
				if(ele.equals("+")) {
					res=num1+num2;
				}else if(ele.equals("-")) {
					res=num1-num2;
				}else if(ele.equals("*")) {
					res=num1*num2;
				}else if(ele.equals("/")) {
					res=num1/num2;
				}else {
					throw new RuntimeException("波兰表达式出现了问题!!!");
				}
				//入栈
				stack.push(""+res);
			}
		}
		//最后留在栈中的是运算结果
		return Integer.parseInt(stack.pop());
	}
}

1.3 中缀表达式转前缀表达式

1.3.1 思路分析

  1. 初始化两个栈:运算符栈s1和存储中间结果栈s2;

  2. 从右到左扫描中缀表达式;

  3. 遇到操作数时,将其压入s2;

  4. 遇到运算符时,比较其与s1栈顶运算符的优先级;
    (4-1)如果s1为空,或者栈顶运算符为右括号 “ ) ”,则直接将此运算符入栈;
    (4-2)否则,若优先级比栈顶运算符的高或者相等,也将运算符压入s1;
    (4-3)否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较;

  5. 遇到括号时:
    (5-1)如果是右括号 “ ) ”,则直接压入s1;
    (5-2)如果是左括号" ( ",则依次弹出s1中栈顶的运算符压入s2,直到遇到右括号为止,此时将这一对括号丢弃;

  6. 重复步骤2至5,直到表达式的最左边;

  7. 将s1中剩余的运算符依次弹出并压入到s2中;

  8. 依次弹出s2中的元素并输出,结果即为中缀表达式对应的前缀表达式。

举例: 1+((2+3) x 4)-5转前缀表达式

扫描到的元素s1(栈底=>栈顶)s2(栈底=>栈顶)说明
55数字,直接入栈
--5s1为空,运算符直接入栈
- )5右括号,直接入栈
4- )5,4数字,直接入栈
x- ) x5,4s1栈顶为右括号,直接入栈
)- ) x )5,4右括号,直接入栈
3- ) x )5,4,3数字,直接入栈
+- ) x ) +5,4,3s1栈顶为右括号,直接入栈
2- ) x ) +5,4,3,2数字,直接入栈
(- ) x5,4,3,2,+左括号,弹出s1中栈顶的运算符压入s2,直到遇到右括号为止,此时将这一对括号丢弃
(-5,4,3,2,+,x左括号,弹出s1中栈顶的运算符压入s2,直到遇到右括号为止,此时将这一对括号丢弃
+- +5,4,3,2,+,x-与+优先级相同,压入+到s1
1- +5,4,3,2,+,x ,1数字,直接入栈
到达最左端5,4,3,2,+,x ,1,+,-将s1中剩余的运算符依次弹出并压入到s2中

1.3.2 代码实现+运行结果

public class MidToBefore {
	public static void main(String[] args) {
		//中缀表达式
		String Expression="1+((2+3)*4)-5";
		List<String> LISTNUM=MidToList(Expression);
		System.out.println("中缀表达式的list=>"+LISTNUM);
		List<String> ls=ToBeforeList(LISTNUM);
		Collections.reverse(ls); //逆序
		System.out.println("前缀表达式的list=>"+ls);
	}
	
	/**
	 * 将中缀list=>前缀list
	 * @param list
	 * @return
	 */
	public static List<String> ToBeforeList(List<String> list){
		//定义一个栈
		Stack<String> s1=new Stack<String>(); //符号栈
		//定义一个集合替代存储中间结果的栈s2
		List<String> s2=new ArrayList<String>();
		
		//遍历list,从右到左
		for (int i=list.size()-1;i>=0;i--) {
			//如果是一个数,直接入栈,正则判断
			if(list.get(i).matches("\\d+")) {
				s2.add(list.get(i));
			//如果是右括号,直接压入s1
			}else if(list.get(i).equals(")")){
				s1.push(list.get(i));
			//如果是左括号
			}else if(list.get(i).equals("(")) {
				//如果是左括号,则依次弹出s1的栈顶运算符,并压入s2中,直到遇到右括号,然后将这个括号丢弃
				while(!s1.peek().equals(")")) {
					s2.add(s1.pop());
				}
				s1.pop(); //消除小括号
			}else {
				//当遍历出来的元素 的优先级小于s1栈顶运算符,将s1栈顶的运算符弹出并加入到s2中,再次与新的s1栈顶运算符相比较
				while(s1.size()!=0 && Operation1.getPro(list.get(i))<Operation1.getPro(s1.peek())) {
					s2.add(s1.pop());
				}
				//还需将遍历出来的元素 压入栈
				s1.push(list.get(i));
			}
		}
		
		//将s1中剩余的符号依次弹出,压入到s2中
		while(s1.size()!=0) {
			s2.add(s1.pop());
		}
		return s2;//遍历出来就是前缀表达式的逆序
	}
	
	/**
	 * 将中缀表达式转成对应的list
	 * @param s
	 * @return
	 */
	public static List<String> MidToList(String s){
		//定义一个list,存放中缀表达式 对应的内容
		List<String> list=new ArrayList<String>();
		int index=0; //作为一个扫描的指针
		String str; //用于多位数的拼接
		char c; //每一次扫到的字符
		
		do {
			//如果c是一个非数字,就加入到list中
			//Char("48")==>0  Char("57")==>9
			if((c=s.charAt(index))<48 ||(c=s.charAt(index))>57) {
				list.add(""+c);
				index++;  //扫描指针后移
			}else {//如果是一个数字
				str=""; //先将str设置为""
				while(index<s.length() && (c=s.charAt(index))>=48 &&(c=s.charAt(index))<=57) {
					str+=c; //拼接
					index++;
				}
				list.add(str);
			}
		}while(index<s.length());
		return list;
	}
}


//编写一个类,可以返回一个运算符对应的优先级
class Operation1{
	private static int LK=0; //在栈顶为(时,依然用出来比较
	private static int RK=0; 
	private static int ADD=1;
	private static int SUB=1;
	private static int MUL=2;
	private static int DIV=2;
	
	//写一个方法,返回一个对应的优先级
	public static int getPro(String key) {
		int result=0;
		switch (key) {
		case "(":
			result=LK;
			break;
		case ")":
			result=RK;
			break;
		case "+":
			result=ADD;
			break;
		case "-":
			result=SUB;
			break;
		case "*":
			result=MUL;
			break;
		case "/":
			result=DIV;
			break;
		default:
			System.out.println("不支持"+key+"运算符!");
			break;
		}
		
		return result;
	}
}

运行结果图:
在这里插入图片描述

1.4 后缀表达式在计算机求值

1.4.1 流程、举例

  • 从左至右扫描表达式,遇到数字时,将数字压入栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次栈顶元素和栈顶元素),并将结果入栈;重复上述过程直到表达式的最右端,最后运算得出的值即为表达式的结果。
  • 例如:(1+2)*3-4对应的前缀表达式就是 1 2 + 3 * 4 -,求值步骤如下:
  1. 从左至右扫描,将1和2压入栈。
  2. 遇到 + 运算符,因此弹出2和1(2为栈顶元素,1为次栈顶元素),计算出1+2的值,得到3,再将3入栈。
  3. 再将3入栈(表达式中的3)。
  4. 接下来是 * 运算符,因此弹出3和3,计算出3 * 3=9,将9入栈。
  5. 再将4入栈。
  6. 最后是 - 运算符,计算出9-4的值,得到5,因此最终结果是5。

1.4.2 代码实现

注意点:给出的逆波兰表达式要用空格隔开,并且表达式正确,支持多位数

public class PolandDemo {
	public static void main(String[] args) {
		//逆波兰表达式
		String Expression="1 2 + 3 * 4 -";
		List<String> LISTNUM=getList(Expression);
		System.out.println(LISTNUM);
		int result=Calculate(LISTNUM);
		System.out.println("逆波兰表达式的结果为:"+result);
	}
	
	/**
	 * 将逆波兰表达式发到list集合中
	 * @return
	 */
	public static List<String> getList(String Expression){
		String[] split=Expression.split(" ");
		List<String> list=new ArrayList<String>();
		//循环
		for(String item : split) {
			list.add(item);
		}
		return list;
	}
	
	/**
	 * 求值
	 * @param list
	 * @return
	 */
	public static int Calculate(List<String> list) {
		//建栈
		Stack<String> stack=new Stack<String>();
		for(String ele : list) {
			//这里使用正则表达式,匹配的是多位数
			if(ele.matches("\\d+")) {
				//入栈
				stack.push(ele);
			}else {
				//先pop两个数进行计算,在入栈【这里是后pop出来的数与先pop出来的数运算】
				int num2=Integer.parseInt(stack.pop());
				int num1=Integer.parseInt(stack.pop());
				System.out.println("num1="+num1+"\t和num2="+num2+"进行"+ele+"运算");
				int res=0;
				if(ele.equals("+")) {
					res=num1+num2;
				}else if(ele.equals("-")) {
					res=num1-num2;
				}else if(ele.equals("*")) {
					res=num1*num2;
				}else if(ele.equals("/")) {
					res=num1/num2;
				}else {
					throw new RuntimeException("逆波兰表达式出现了问题!!!");
				}
				//入栈
				stack.push(""+res);
			}
		}
		//最后留在栈中的是运算结果
		return Integer.parseInt(stack.pop());
	}
}

1.5 中缀表达式转后缀表达式(常用)

1.5.1 思路分析

  1. 初始化两个栈:运算符栈s1和存储中间结果栈s2;

  2. 从左到右扫描中缀表达式;

  3. 遇到操作数时,将其压入s2;

  4. 遇到运算符时,比较其与s1栈顶运算符的优先级;
    (4-1)如果s1为空,或者栈顶运算符为左括号 “ ( ”,则直接将此运算符入栈;
    (4-2)否则,若优先级比栈顶运算符的高,也将运算符压入s1;
    (4-3)否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较;

  5. 遇到括号时:
    (5-1)如果是左括号 “ ( ”,则直接压入s1;
    (5-2)如果是右括号" ) ",则依次弹出s1中栈顶的运算符压入s2,直到遇到左括号为止,此时将这一对括号丢弃;

  6. 重复步骤2至5,直到表达式的最右边;

  7. 将s1中剩余的运算符依次弹出并压入到s2中;

  8. 依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式。

举例: 1+((2+3) x 4)-5转后缀表达式

扫描到的元素s1(栈底=>栈顶)s2(栈底=>栈顶)说明
11数字,直接入栈
++1s1为空,运算符直接入栈
(+ (1左括号,直接入栈
(+((1同上
2+((1,2数字,直接入栈
++(( +1,2s1栈顶为左括号,直接入栈
3+(( +1,2,3数字,直接入栈
+(1,2,3,+右括号,弹出s1中栈顶的运算符压入s2,直到遇到左括号为止,此时将这一对括号丢弃
x+( x1,2,3,+s1栈顶为左括号,直接入栈
4+( x1,2,3,+ ,4数字,直接入栈
)+1,2,3,+ ,4,x右括号,弹出s1中栈顶的运算符压入s2,直到遇到左括号为止,此时将这一对括号丢弃
--1,2,3,+ ,4,x,+-与+优先级相同,因此弹出+,再压入-
5-1,2,3,+ ,4,x,+ ,5数字,直接入栈
到达最右端1,2,3,+ ,4,x,+ ,5 ,-s1中剩余的运算符

1.5.2 代码实现+运行结果

public class MidToAfter {
	public static void main(String[] args) {
		//中缀表达式
		String Expression="1+((2+3)*4)-5";
		List<String> LISTNUM=MidToList(Expression);
		System.out.println("中缀表达式的list=>"+LISTNUM);
		List<String> ls=ToAfterList(LISTNUM);
		System.out.println("后缀表达式的list=>"+ls);
	}
	
	/**
	 * 将中缀list=>后缀list
	 * @param list
	 * @return
	 */
	public static List<String> ToAfterList(List<String> list){
		//定义一个栈
		Stack<String> s1=new Stack<String>(); //符号栈
		//定义一个集合替代存储中间结果的栈s2
		List<String> s2=new ArrayList<String>();
		
		//遍历list
		for (String item : list) {
			//如果是一个数,直接入栈
			if(item.matches("\\d+")) {
				s2.add(item);
			}else if(item.equals("(")){
				s1.push(item);
			}else if(item.equals(")")) {
				//如果是右括号,则依次弹出s1的栈顶运算符,并压入s2中,直到遇到左括号,然后将这个括号丢弃
				while(!s1.peek().equals("(")) {
					s2.add(s1.pop());
				}
				s1.pop(); //消除小括号
			}else {
				//当item的优先级小于或者等于s1栈顶运算符,将s1栈顶的运算符弹出并加入到s2中,再次与新的s1栈顶运算符
				while(s1.size()!=0 && Operation.getPro(item)<=Operation.getPro(s1.peek())) {
					s2.add(s1.pop());
				}
				//还需将item压入栈
				s1.push(item);
			}
		}
		
		//将s1中剩余的符号依次弹出,压入到s2中
		while(s1.size()!=0) {
			s2.add(s1.pop());
		}
		return s2;//因为存放再list中,遍历出来就是后缀表达式
	}
	
	/**
	 * 将中缀表达式转成对应的list
	 * @param s
	 * @return
	 */
	public static List<String> MidToList(String s){
		//定义一个list,存放中缀表达式 对应的内容
		List<String> list=new ArrayList<String>();
		int index=0; //作为一个扫描的指针
		String str; //用于多位数的拼接
		char c; //每一次扫到的字符
		
		do {
			//如果c是一个非数字,就加入到list中
			//Char("48")==>0  Char("57")==>9
			if((c=s.charAt(index))<48 ||(c=s.charAt(index))>57) {
				list.add(""+c);
				index++;  //扫描指针后移
			}else {//如果是一个数字
				str=""; //先将str设置为""
				while(index<s.length() && (c=s.charAt(index))>=48 &&(c=s.charAt(index))<=57) {
					str+=c; //拼接
					index++;
				}
				list.add(str);
			}
		}while(index<s.length());
		return list;
	}
}


//编写一个类,可以返回一个运算符对应的优先级
class Operation{
	private static int LK=0; //在栈顶为(时,依然用出来比较
	private static int RK=0; 
	private static int ADD=1;
	private static int SUB=1;
	private static int MUL=2;
	private static int DIV=2;
	
	//写一个方法,返回一个对应的优先级
	public static int getPro(String key) {
		int result=0;
		switch (key) {
		case "(":
			result=LK;
			break;
		case ")":
			result=RK;
			break;
		case "+":
			result=ADD;
			break;
		case "-":
			result=SUB;
			break;
		case "*":
			result=MUL;
			break;
		case "/":
			result=DIV;
			break;
		default:
			System.out.println("不支持"+key+"运算符!");
			break;
		}
		
		return result;
	}
}

运行结果图:
在这里插入图片描述

最后,如果想要使用中缀表达式转后缀表达式进行运算,可以将转换好的后缀表达式进行拼接成字符串,然后调用先前 1.4.2 代码实现

		String str="";
		for(String item:ls) {
			str+=item.concat(" ");
		}
		System.out.println(str);
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值