本文以2022-2023学年HFUT数据结构课程设计题:表达式求值 为例子。
题目:
目的: 表达式求值问题在程序设计中经常遇见,通过本课程设计,使学生掌握表达式的不同表示形式及相应的求值方法,加深对栈的基本原理和方法的理解和应用,培养学生运用语言编程及调试的能力,运用数据结构解决简单的实际问题的能力,为后续计算机专业课程的学习打下坚实的基础。 假定: 表达式中的操作数皆为实数。运算符为:+、-、*、/、^(幂运算)等二元运算符。 要求: 交互输入或从文本文件输入中缀表达式, 解析表达式的合法性;直接从中缀表达式求值; 将中缀表达式转换为后缀表达式;后缀表达式求值; 将中缀表达式转换为前缀表达式;前缀表达式求值;
题意理解:直接输入或者从文本文件读入一串中缀表达式,检验其合法性并输出其前缀、后缀表达式和计算结果。
难点:对表达式各种符号和数字的处理,前缀、后缀表达式的存储方式。
主要思想:
借用“表达式二叉树”完成相关操作。下面介绍表达式二叉树的个人理解:
首先举个例子,1+2是一个中缀表达式,将其转化为二叉树的结构即为:
不难发现这个二叉树的前序遍历序列,即+ 1 2就为原表达式的前缀形式,同理其后缀遍历序列,即1 2 + 就为原表达式的后缀形式。
那么将这个概念抽象化,形如A O B 的表达式 , O为一个操作符(二元) , A和B为一个子表达式,且A是一个形如A O B表达式的根结点,将该表达式转化为二叉树形式得到表达式二叉树即可轻松地求出其前缀表达式和后缀表达式,仅需要前序遍历和后序遍历该树即可。
此外:
计算结果仅需要遍历该二叉树,从底层开始计算后不断往上层计算直到根结点即可。
对于表达式中的小数部分,利用字符串处理,即识别到数字和小数点时将其直接添加进一个临时字符串中,最后将该字符串转化为double数据即可。
对于合法性检验,分别检验括号匹配和除数不为0:对于括号匹配,仅需将所有左括号入栈,遇见一个右括号弹出栈顶左括号,判断是否符合每个右括号都能对应一个左括号即遇见右括号时当前栈顶有元素,且遍历完字符串后栈中没有元素。除数不为0的检查,需在括号匹配的基础上构建并遍历表达式二叉树,遇到"/"时判断其右子结点的值是否为0,遇到表达式时计算出表达式是否为0。
最后附上源程序,仅供参考。
#include "iostream"
#include "stack"
#include "math.h"
#include "fstream"
#include "string"
using namespace std;
//the data structure of expTree
struct expTree{
char dataop;
double datanu;
expTree* left;
expTree* right;
expTree(char a){dataop=a;datanu=INFINITY;left= nullptr;right= nullptr;}
expTree(double a){dataop='\0';datanu=a;left= nullptr;right= nullptr;}
};
int priority(char ope);
string readfd(string path);
//build exp tree
expTree* buildExpTree(string exp){
//use two stack - one contains nodes , the other contains operations
stack<expTree*> nodeStack;
stack<char> opStack;
int i=0, len=exp.size();
while(i<len){
if(exp[i]==' '){ //ignore the " "
i++;
continue;
}
else if(isdigit(exp[i]) || exp[i]=='.'){ //deal with number-type
string numStr="";
while(i<len && (isdigit(exp[i]) || exp[i]=='.')){
numStr += exp[i];//string add
i++;
}
double number = stod(numStr);//string change to number
nodeStack.push(new expTree(number));//new a node and put it into node stack
}
else{ //deal with ( )
char op = exp[i];
if(op == '(') { //'(' put into operation stack
opStack.push(op);
i++;
}else if(op == ')'){ //meet ")" pop every element until meet '('
while(!opStack.empty() && opStack.top() != '('){
char topOp = opStack.top();opStack.pop();
//A*B : A is left , B is right , use a root connect them
auto rightNode=nodeStack.top();nodeStack.pop();
auto leftNode=nodeStack.top();nodeStack.pop();
auto newNode=new expTree(topOp);newNode->left=leftNode;newNode->right=rightNode;
nodeStack.push(newNode);//put the new root into node stack
}
if(!opStack.empty()) //pop '('
opStack.pop();
i++;
}else{ //deal with operation
if(op=='-' && (nodeStack.empty() || exp[i-1]=='(')){ //judge if it is a minus sign
nodeStack.push(new expTree('-'));
i++;
continue;
}
while(!opStack.empty() && priority(op)<=priority(opStack.top())){//pop the operation with higher priority and build new node
char topOp = opStack.top();opStack.pop();
auto rightNode=nodeStack.top();nodeStack.pop();
auto leftNode=nodeStack.top();nodeStack.pop();
auto newNode=new expTree(topOp);newNode->left=leftNode;newNode->right=rightNode;
nodeStack.push(newNode);//put it into node stack
}
opStack.push(op);//put current operation into stack
i++;
}
}
}
//deal with left elements
while (!opStack.empty())
{
char topOP = opStack.top();opStack.pop();
auto rightnode = nodeStack.top();nodeStack.pop();
auto leftnode = nodeStack.top();nodeStack.pop();
auto newnode = new expTree(topOP);
newnode->left = leftnode;
newnode->right=rightnode;
nodeStack.push(newnode);
}
return nodeStack.top();
}
//travel the tree in preorder or postorder
//can get the pre-expression or post-expression
void preorder(expTree* T)
{
if(T->datanu != INFINITY)cout<<T->datanu<<" ";
else if(T->dataop != '\0')cout<<T->dataop<<" ";
if(T->left)
preorder(T->left);
if(T->right)
preorder(T->right);
}
void postorder(expTree* T)
{
if(T->left)
postorder(T->left);
if(T->right)
postorder(T->right);
if(T->datanu != INFINITY)cout<<T->datanu<<" ";
else if(T->dataop != '\0')cout<<T->dataop<<" ";
}
double calculateExp(expTree* T){
//the node is a number , return
if(T->datanu != INFINITY) return T->datanu;
//recursively process the left/right child
double leftVal = calculateExp(T->left);
double rightVal = calculateExp(T->right);
//the node is an operation , choose function
switch (T->dataop)
{
case '+': return leftVal + rightVal;
case '-': return leftVal - rightVal;
case '*': return leftVal * rightVal;
case '/': return leftVal / rightVal;
case '^': return pow(leftVal, rightVal);
case '&': return (int)leftVal & (int)rightVal;
case '|': return (int)leftVal | (int)rightVal;
case '%': return (int)leftVal % (int)rightVal;
default: break; // wrong operation , do nothing
}
return 0.0; // have nothing to do , return 0.0
}
int priority(char op)
{
if(op=='(')return 0;
else if(op=='&'||op=='|')return 1;
else if(op=='+'||op=='-')return 2;
else if(op=='*'||op=='/'||op=='%')return 3;
else if(op=='^')return 4;
return 0;
}//judge the priority
string readfd(string path)//read exp from file
{
ifstream file(path);
if(file.is_open())
{
string exp;
char c;
while (file.get(c))
{
if(c=='#')
break;
else
exp+=c;
}
file.close();
return exp;
}
else
{
throw "failed to open the file!\n";
}
}
void travelwithjudge(expTree* T,int &flag)
{
flag = 1;
if(T->dataop=='/')
{
if (T->right->datanu == 0 || calculateExp(T->right) == 0) {
cout << "wrong : division by zero!\n";
flag = 0;
return;
}
}
if(flag != 0)
{
if(T->left)travelwithjudge(T->left,flag);
if(T->right)travelwithjudge(T->right,flag);
}
}
bool exptruth(string exp)
{
stack<char> a;
for(int i=0;exp[i];i++)//judge the parentheses
{
if(exp[i]=='(')a.push('(');
else if(exp[i]==')')
{
if(!a.empty())a.pop();
else
{
cout<<"the parentheses are not suitable!\n";
return false;
}
}
}
if(!a.empty())
{
cout<<"the parentheses are not suitable!\n";
return false;
}
expTree* tmp = buildExpTree(exp);
int flag = 1;
travelwithjudge(tmp,flag);
if(!flag)
{
cout<<"the expression is wrong , cannot calculate!\n";
return false;
}
return true;
}
int main(){
sta:
cout<<"choose the way you input the expression:\n"
"input directly (1) or input from file (2)\n";
string myc;cin>>myc;
string path = "C:\\Users\\w\\Documents\\C!\\CandC++learning\\From Clion\\calculator\\input_exp";
string exp;
if(myc=="1")
cin>>exp;
else if(myc=="2")
exp= readfd(path);
if(exptruth(exp))
{
expTree* a = buildExpTree(exp);
cout<<"the pre-exp:";
preorder(a);
cout<<"\n";
cout<<"the post-exp: ";
postorder(a);
cout<<"\n";
cout<<"the outcome: "<<calculateExp(a)<<"\n";
}
cout<<"would u want to input another expression?\n"
"(1-YES/else-NO)\n";
string tty;cin>>tty;
if(tty=="1")goto sta;
else cout<<"see u!";
return 0;
}
ps:事实上,因为太懒的缘故,该程序还有很多潜在问题没有找出,仅经历10次左右的测试,可能不能很好的应对各种奇怪复杂的表达式,仅供学习。如有错误欢迎指出!
下面附上一些测试结果:
直接输入:
1+(2*3)^2+(3|1)
1.6+3.4*(1+3-6)/2
1+2*3((4+5) //此为错误样例
114514+1919810+12490/0 //此为错误样例
文档输入:
1+(2*3)^2+(3|1)
1.6+3.4*(1+3-6)/2
1+2*3((4+5) //此为错误样例
114514+1919810+12490/0 //此为错误样例
最后,对于二叉树表达式的思想,参考来源为: