这篇文章讲述的是算法趣味分数部分的表达式求值j问题的java实现,参考的书籍为清华大学出版社出版,贾蓓等编著的《c语言趣味编程1000例》,如有错误或者不当之处,还望各位大神批评指正。
问题描述
表达式求值,给出一个表达式包括 + - * / ( )等元素,数字类型包括整型和浮点型,写一个程序计算它的值
算法分析
- 四则运算的规则是:先乘除后加减,右括号先算括号里的
- 需要解决的问题有
- 把字符串中的每个字符(数字或运算符)提取出来
- 运算符之间比较优先级
- 使用两个站存放数字和运算符
- 数制的转换
- 执行逻辑如下
/*循环结束条件为当前元素为结束符#,且运算符栈只剩# */
while(elem.charAt(0) != '#' || !operStack.getTop().equals('#')){
/*若为数字则入栈*/
if(isNumber(elem)){
//入栈
}else{
/*获取当前运算符和栈顶的运算符判断优先级*/
char prior = getPriority(oper1 , oper2) ;
switch(prior){
case'<':{
//若当前元素优先级较低则入栈
}break ;
case'=':{
//若优先级相等则说明是括号脱括号并判断下一个
}
case'>':{
//若栈顶元素优先级较高则取出数字栈中的元素进行计算
}
}
}
代码实现
要实现这样复杂的操作要将问题分解:1. 编写处理字符串的类 ;2. 处理运算符之间的关系;3. 使用栈来处理核心结构
第一步:编写字符串处理函数
实现将表达式的每个元素都单独拿出来
/*字符串处理类,用于从字符串中分隔操作符*/
class StringHandler{
/*要处理的字符串*/
String STR ;
/*分隔符,默认为空格*/
char TOCKEN = ' ' ;
/*遍历整个字符串的指针*/
int INDEX = 0 ;
public StringHandler(String str) {
this.STR = str ;
}
public StringHandler(String str , char tocken) {
this.STR = str ;
this.TOCKEN = tocken ;
}
/**
* @explain nextTocken方法: 返回当前分隔符到下一个分隔符之间的字符
* @return String 分隔符之间的字符串
* @throws
* @author 叶清逸
* @date 2018年8月2日 下午10:31:36
*/
public String nextElement(){
/*如果指针越界返回空*/
if(INDEX >= STR.length()){
return null ;
}
/*跳过分隔符*/
while(STR.charAt(INDEX) == TOCKEN){
INDEX++ ;
}
String elem = "" ;
/*若当前字符不为分隔符,则放入elem的缓存中*/
while(INDEX<STR.length() && STR.charAt(INDEX) != TOCKEN){
elem = elem + STR.charAt(INDEX) ;
INDEX++ ;
}
INDEX++ ;
return elem ;
}
/**
* @explain hasMoreTocken方法: 判断是否还有分隔符
* @return boolean 如果还有分隔符则返回true,否则返回false
* @throws
* @author 叶清逸
* @date 2018年8月2日 下午10:32:55
*/
public boolean hasMoreElement(){
/*如果指针越界返回空*/
if(INDEX >= STR.length()){
return false ;
}
/*跳过分隔符*/
while(STR.charAt(INDEX) == TOCKEN){
INDEX++ ;
}
String elem = "" ;
int i = INDEX ;
/*若当前字符不为分隔符,则放入elem的缓存中*/
while(i<STR.length() && STR.charAt(i) != TOCKEN){
elem = elem + STR.charAt(i) ;
i++ ;
}
if(elem == ""){
return false ;
}else{
return true;
}
}
}
第二步:处理运算符之间的优先级
- 要实现比较运算符的优先级比较需要将符号索引化:0表示’+’,1表示’-‘,2表示’*’,3表示’/’,4表示’(‘,5表示’)’,6表示’#’
/**
*@explain getIndex方法: 获取运算符的索引代码,0表示'+',1表示'-',2表示'*',3表示'/',4表示'(',5表示')',6表示'#'
* @param oper 运算符
* @return int 运算符的索引
* @throws
* @author 叶清逸
* @date 2018年8月3日 下午5:24:07
*/
private static int getIndex(char oper){
int index ;
switch(oper){
case'+':{
index = 0 ;
} break ;
case'-':{
index = 1 ;
} break ;
case'*':{
index = 2 ;
} break ;
case'/':{
index = 3 ;
} break ;
case'(':{
index = 4 ;
} break ;
case')':{
index = 5 ;
} break ;
case'#':{
index = 6 ;
} break ;
default:{
index = -1 ;
}
};
return index ;
}
- 再根据索引获取两个运算符的优先级考虑使用二维数组
/**
* @explain getPriority方法: 判断连个运算符的优先级
* @param oper1 运算符1
* @param oper2 运算符2
* @return char 运算符的优先级,
* 若为'<'则表示运算符1的优先级小于运算符2,
* 若为'>'则表示运算符1的优先级大于运算符2,
* 若为'='则表示则表示两个括号相遇
* 若为' '即空格则表示优先级不存在
* @throws
* @author 叶清逸
* @date 2018年8月3日 下午5:01:05
*/
private static char getPriority(char oper1 , char oper2){
/*获取两个运算符的索引*/
int index1 = getIndex(oper1) ;
int index2 = getIndex(oper2) ;
/*使用二维数组保存优先级列表*/
char [][] prioritys = { {'>','>','<','<','<','>','>'} ,
{'>','>','<','<','<','>','>'} ,
{'>','>','>','>','<','>','>'} ,
{'>','>','>','>','<','>','>'} ,
{'<','<','<','<','<','=',' '} ,
{'>','>','>','>',' ','>','>'} ,
{'<','<','<','<','<','=',' '} , } ;
return prioritys[index1][index2];
}
第三步:判断一个元素是数字还是运算符
/**
* @explain isNumber方法: 判断传入元素是数字还是运算符
* @param elem 传入元素
* @return boolean 若为元素则返回true,若为运算符则返回false
* @throws
* @author 叶清逸
* @date 2018年8月3日 下午8:03:06
*/
static boolean isNumber(String elem){
/*若为单个字符则判断是否为单独的加减*/
if(elem.length() == 1){
if(elem.charAt(0) == '+' || elem.charAt(0) == '-'){
return false ;
}
}
/*判断每个字符是否为数字*/
for(int i=0 ; i<elem.length() ; i++){
char c = elem.charAt(i) ;
if( !(c == '+' || c == '-' || c >= '0' && c <= '9' || c == '.') ){
return false ;
}
}
return true ;
}
第四步:编写运算方法
功能是传入两个数和这两个数的运算符,返回运算结果
/**
* @explain calculate方法: 运算操作
* @param oper 运算符
* @param num1 数字1
* @param num2 数字2
* @return double 返回运算的结果
* @throws
* @author 叶清逸
* @date 2018年8月3日 下午11:34:25
*/
private static double calculate(char oper , double num1 , double num2){
double result = 0 ;
switch(oper){
case'+':{
result = num1 + num2 ;
}break ;
case'-':{
result = num1 - num2 ;
}break ;
case'*':{
result = num1 * num2 ;
}break ;
case'/':{
result = num1 / num2 ;
}break ;
}
return result ;
}
第五步:编写核心代码
/**
* @explain evaluate方法: 计算表达式的值
* @param exp 表达式
* @return double 表达式的运算结果
* @throws
* @author 叶清逸
* @date 2018年8月4日 下午8:58:32
*/
private static double evaluate(String exp){
/*定义两个辅助栈,一个存放数字,一个存放运算符*/
Stack numStack = new LinkStack() ;
numStack.init();
Stack operStack = new LinkStack() ;
operStack.init() ;
/*初始化用于处理字符串的类*/
StringHandler sh = new StringHandler(exp) ;
/*遍历整个表达式,把字符放入对应的栈*/
operStack.push('#');
String elem = sh.nextElement() ;
while(elem.charAt(0) != '#' || !operStack.getTop().equals('#')){
if(isNumber(elem)){ //若为数字压入栈
numStack.push(elem);
elem = sh.nextElement() ;
}else{
/*获取当前运算符和栈顶的运算符*/
char oper1 = operStack.getTop().toString().charAt(0); //栈顶运算符
char oper2 = elem.charAt(0) ; //当前运算符
char prior = getPriority(oper1 , oper2) ;
switch(prior){
case'<':{ //若当前元素优先级较低则入栈
operStack.push(elem);
elem = sh.nextElement();
}break ;
case'=':{ //若优先级相等则说明是括号脱括号并判断下一个
operStack.pop() ;
elem = sh.nextElement();
continue ;
}
case'>':{ //若栈顶元素优先级较高则进行计算
double num2 = Double.parseDouble(numStack.pop().toString()) ;
double num1 = Double.parseDouble(numStack.pop().toString()) ;
char operator = operStack.pop().toString().charAt(0) ;
double result = calculate(operator, num1, num2) ;
numStack.push(result);
}
}
}
}
/*运算最后数字栈中剩余的数字即为运算结果*/
double result = Double.parseDouble(numStack.pop().toString()) ;
/*返回表达式运算结果*/
return result ;
}
完整代码
package stack_question;
import stack.LinkStack;
import stack.Stack;
/**
* @author 叶清逸
* @date 2018年8月2日下午9:37:37
* @version 1.0
* @project stack_question
*/
public class Q3_ExpressionEvaluate {
/**
* 问题描述:表达式求值,给出一个表达式包括 + - * / ( )等元素,写一个程序计算它的值
*
* 算法分析:1. 把字符串中的每个字符(数字或运算符)提取出来
* 2. 运算符之间比较优先级
* 3. 使用两个栈来存放数字和运算符
* 4. 数制之间的转换
*
* 复杂度分析:时间复杂度:O(n) , 空间复杂度:O(n)
*/
public static void main(String[] args) {
/*初始化表达式*/
String exp = "4 * ( -10 + 2.5 ) + 1 #" ;
/*进行表达式运算*/
double result = evaluate(exp) ;
System.out.println(exp + " = " + result);
}
/**
* @explain evaluate方法: 计算表达式的值
* @param exp 表达式
* @return double 表达式的运算结果
* @throws
* @author 叶清逸
* @date 2018年8月4日 下午8:58:32
*/
private static double evaluate(String exp){
/*定义两个辅助栈,一个存放数字,一个存放运算符*/
Stack numStack = new LinkStack() ;
numStack.init();
Stack operStack = new LinkStack() ;
operStack.init() ;
/*初始化用于处理字符串的类*/
StringHandler sh = new StringHandler(exp) ;
/*遍历整个表达式,把字符放入对应的栈*/
operStack.push('#');
String elem = sh.nextElement() ;
while(elem.charAt(0) != '#' || !operStack.getTop().equals('#')){
if(isNumber(elem)){ //若为数字压入栈
numStack.push(elem);
elem = sh.nextElement() ;
}else{
/*获取当前运算符和栈顶的运算符*/
char oper1 = operStack.getTop().toString().charAt(0); //栈顶运算符
char oper2 = elem.charAt(0) ; //当前运算符
char prior = getPriority(oper1 , oper2) ;
switch(prior){
case'<':{ //若当前元素优先级较低则入栈
operStack.push(elem);
elem = sh.nextElement();
}break ;
case'=':{ //若优先级相等则说明是括号脱括号并判断下一个
operStack.pop() ;
elem = sh.nextElement();
continue ;
}
case'>':{ //若栈顶元素优先级较高则进行计算
double num2 = Double.parseDouble(numStack.pop().toString()) ;
double num1 = Double.parseDouble(numStack.pop().toString()) ;
char operator = operStack.pop().toString().charAt(0) ;
double result = calculate(operator, num1, num2) ;
numStack.push(result);
}
}
}
}
/*运算最后数字栈中剩余的数字即为运算结果*/
double result = Double.parseDouble(numStack.pop().toString()) ;
/*返回表达式运算结果*/
return result ;
}
/**
* @explain getPriority方法: 判断连个运算符的优先级
* @param oper1 运算符1
* @param oper2 运算符2
* @return char 运算符的优先级,
* 若为'<'则表示运算符1的优先级小于运算符2,
* 若为'>'则表示运算符1的优先级大于运算符2,
* 若为'='则表示则表示两个括号相遇
* 若为' '即空格则表示优先级不存在
* @throws
* @author 叶清逸
* @date 2018年8月3日 下午5:01:05
*/
private static char getPriority(char oper1 , char oper2){
/*获取两个运算符的索引*/
int index1 = getIndex(oper1) ;
int index2 = getIndex(oper2) ;
/*使用二维数组保存优先级列表*/
char [][] prioritys = { {'>','>','<','<','<','>','>'} ,
{'>','>','<','<','<','>','>'} ,
{'>','>','>','>','<','>','>'} ,
{'>','>','>','>','<','>','>'} ,
{'<','<','<','<','<','=',' '} ,
{'>','>','>','>',' ','>','>'} ,
{'<','<','<','<','<','=',' '} , } ;
return prioritys[index1][index2];
}
/**
* @explain getIndex方法: 获取运算符的索引代码,0表示'+',1表示'-',2表示'*',3表示'/',4表示'(',5表示')',6表示'#'
* @param oper 运算符
* @return int 运算符的索引
* @throws
* @author 叶清逸
* @date 2018年8月3日 下午5:24:07
*/
private static int getIndex(char oper){
int index ;
switch(oper){
case'+':{
index = 0 ;
} break ;
case'-':{
index = 1 ;
} break ;
case'*':{
index = 2 ;
} break ;
case'/':{
index = 3 ;
} break ;
case'(':{
index = 4 ;
} break ;
case')':{
index = 5 ;
} break ;
case'#':{
index = 6 ;
} break ;
default:{
index = -1 ;
}
};
return index ;
}
/**
* @explain isNumber方法: 判断传入元素是数字还是运算符
* @param elem 传入元素
* @return boolean 若为元素则返回true,若为运算符则返回false
* @throws
* @author 叶清逸
* @date 2018年8月3日 下午8:03:06
*/
static boolean isNumber(String elem){
/*若为单个字符则判断是否为单独的加减*/
if(elem.length() == 1){
if(elem.charAt(0) == '+' || elem.charAt(0) == '-'){
return false ;
}
}
/*判断每个字符是否为数字*/
for(int i=0 ; i<elem.length() ; i++){
char c = elem.charAt(i) ;
if( !(c == '+' || c == '-' || c >= '0' && c <= '9' || c == '.') ){
return false ;
}
}
return true ;
}
/**
* @explain calculate方法: 运算操作
* @param oper 运算符
* @param num1 数字1
* @param num2 数字2
* @return double 返回运算的结果
* @throws
* @author 叶清逸
* @date 2018年8月3日 下午11:34:25
*/
private static double calculate(char oper , double num1 , double num2){
double result = 0 ;
switch(oper){
case'+':{
result = num1 + num2 ;
}break ;
case'-':{
result = num1 - num2 ;
}break ;
case'*':{
result = num1 * num2 ;
}break ;
case'/':{
result = num1 / num2 ;
}break ;
}
return result ;
}
}
/*字符串处理类,用于从字符串中分隔操作符*/
class StringHandler{
/*要处理的字符串*/
String STR ;
/*分隔符,默认为空格*/
char TOCKEN = ' ' ;
/*遍历整个字符串的指针*/
int INDEX = 0 ;
public StringHandler(String str) {
this.STR = str ;
}
public StringHandler(String str , char tocken) {
this.STR = str ;
this.TOCKEN = tocken ;
}
/**
* @explain nextTocken方法: 返回当前分隔符到下一个分隔符之间的字符
* @return String 分隔符之间的字符串
* @throws
* @author 叶清逸
* @date 2018年8月2日 下午10:31:36
*/
public String nextElement(){
/*如果指针越界返回空*/
if(INDEX >= STR.length()){
return null ;
}
/*跳过分隔符*/
while(STR.charAt(INDEX) == TOCKEN){
INDEX++ ;
}
String elem = "" ;
/*若当前字符不为分隔符,则放入elem的缓存中*/
while(INDEX<STR.length() && STR.charAt(INDEX) != TOCKEN){
elem = elem + STR.charAt(INDEX) ;
INDEX++ ;
}
INDEX++ ;
return elem ;
}
/**
* @explain hasMoreTocken方法: 判断是否还有分隔符
* @return boolean 如果还有分隔符则返回true,否则返回false
* @throws
* @author 叶清逸
* @date 2018年8月2日 下午10:32:55
*/
public boolean hasMoreElement(){
/*如果指针越界返回空*/
if(INDEX >= STR.length()){
return false ;
}
/*跳过分隔符*/
while(STR.charAt(INDEX) == TOCKEN){
INDEX++ ;
}
String elem = "" ;
int i = INDEX ;
/*若当前字符不为分隔符,则放入elem的缓存中*/
while(i<STR.length() && STR.charAt(i) != TOCKEN){
elem = elem + STR.charAt(i) ;
i++ ;
}
if(elem == ""){
return false ;
}else{
return true;
}
}
}
样例输出
初始串:”4 * ( -10 + 2.5 ) + 1 #”
4 * ( -10 + 2.5 ) + 1 # = -29.0