数据结构实验--表达式的后缀表示
一、问题描述
表达式中包含运算对象、运算符和圆括号等,习惯上使用中缀表示(指运算符夹在两运算符对象中间)形式。计算表达式的值,涉及到运算符的优先级别,如先乘除后加减。括在一对圆括号中的子表达式必须先计算,因此,圆括号可视为特殊的运算符,具有最高优先级别。圆括号可以任意嵌套,这意味着左圆括号后面又是表达式,形成表达式的递归定义。
为了直接指明表达式中各运算对象的先后计算顺序,可将表达式的中缀形式转换成后缀(指运算符放在二运算对象的后面)形式。例如,表达式ab-(c+d)/e,这是通常的中缀形式,其后缀表示是abcd+e/-,其中圆括号在后缀形式中已不见了。设计一转换程序,将输入的任一表达式转换成相应的后缀形式后输出。
【基本要求】
为简单起见,假定运算对象只含变量,且每个变量名以单字母表示;运算符仅含+、-、*、/和圆括号;表达式以分号“;”结尾。在转换过程中,要求作必要的语法检查,例如圆括号是否配对,单词是否合法等。要求分别编写转换程序的非递归与递归算法。
【测试数据】
(1)ABC
(2)a+b*(c-d)-e/f
(3)(A+B)D+E/(F+AD)+C
二、需求分析
要解决的问题:要表达一个算式,通常有三种形式即:前缀、中缀和后缀。其中人们最常用的最便于人理解的就是中缀表达式,但是其不便于计算器进行计算,计算器更擅长的是计算后缀表达式,因此我们需要设计一个算法来把一个已知的中缀表达式转化成后缀形式。
程序的功能:键盘键入一个中缀表达式,系统自动输出其对应的后缀形式。
输入和输出的形式:输入表达式运算符包括(数字(整数和小数)、字母(大写和小写))操作符(加、减、乘、除、括号)。输出时,不同的操作符和运算符之间以空格分开,防止歧义。
三、设计
3.1 设计思想
数据结构设计:
本程序主要采用顺序栈作为数据结构,得益于其后进先出的特性,对表达式的转化问题就可以转化为不同优先级符号的进出栈问题。由于本程序所操作的对象均为比较小的表达式且没有过多的插入删除操作,故采用顺序栈储存操作符,用数组储存中缀表达式。
算法设计:
3.2 详细设计
首先在CCStack.h自定义一个Mystack类模板:
数据成员 data数组用来存数据,topn用来指示栈顶的位置
函数:初始化函数init用来初始化栈顶为0号位置;empty用来判断栈是否为空,空则返回true;top函数返回栈顶元素,不出栈;push(x)函数用于将x进栈;pop用于将栈顶出栈。
在Postfix1.cpp引用CCStack.h并定义以下函数:
isOperator用来判断一个字符是否为加减乘除,是则返回true
isOperands用来判断一个字符是否为操作数(数字+大小写字母+小数点),是则返回true
GetPriority用来返回字符在计算中的优先级:#为零、(为1、±为2、*/为3。
Calculater用非递归的方法转换表达式,
D_Calculater用非递归的方法转换表达式。
转换的原理:
从头开始扫描存储表达式的字符数组arr,直到arr[i] = '='及扫描到尾部的等于号为止,定义op栈用来存储转换过程中的操作符,首先把优先级最低的#压入栈用来判断栈底位置。
判断arr[i]的类型:
1.如果是数字,就直接输出,并且设置一个循环判断下一位是否为数字(确保大于一位的数字以及小数能够正确输出而不被拆开)如果是则接着输出,然后输出一个空格表示当前的数字已经输出完毕
2.如果是左括号,就直接压入栈
3.如果是右括号,设置循环一直推出栈中已有的操作符直到遇到左括号,并把左括号也推出
4.如果是运算符(±*/四种)设置循环,只要#不是栈顶(栈不空),用当前arr[i]和栈顶比较优先级,如果优先级低或等于栈顶则直接输出,如果高于栈顶则则入栈
主循环至此已经结束
继续判断栈顶是否为#,若不是则一直输出操作符,直到#为栈顶
递归的算法:
总体思想与非递归类似,但是用递归调用D_Calculater的方式代替循化,每次判断传入的参数arr[0],每次递归调用时传递的参数为当前数组第二个元素(1号位)的地址
递归出口为:arr[0]为等号,算法进行到这一步,若栈中仍有操作符则依次全部出栈
格式检查:
在非递归算法中,在判断完所有的类型均不符合后,直接当作非法字符 。在递归算法中,进行比较之前第一步用其与支持的所有字符比较,若均不相等,则视为非法字符。
对括号的检查直接在对右括号的操作语句中进行,即判断是右括号即将输出栈中左括号之前所有符号时,判断栈顶是否为#,若是则等号不匹配
main函数:
在 main函数中定义一个栈Oper用来存储递归方式下的操作符,并在其底部插入#作为判断的标志,在提示键盘键入 一个表达式之后,输入需要转化的表达式(以=结尾),用std::cin.getline(x, 250);将表达式读入到字符数组x中,单独输入0作为结束的标志,然后分别把x和Oper作为参数传入两个函数中即Calculater(x);和D_Calculater(x, Oper);
四、代码实现:
#include <iostream>
#include "CCStack.h"
//#include <stack>
using namespace std;
// 判断是否是操作符
bool isOperator(char ch) {
if (ch == '+' || ch == '-' || ch == '*' || ch == '/')
return true;
return false;
}
//判断是否为操作数(数字+字母+小数点)
bool isOperands(char ch) {
if (ch <= '9' && ch >= '0' || ch <= 'Z' && ch >= 'A' || ch <= 'z' && ch >= 'a' || ch == '.')
return true;
return false;
}
// 获取优先级
int GetPriority(char ch) {
int Rank = 0; // 优先级
switch (ch) {
case '#':
Rank = 0;
break;
case '(':
Rank = 1;
break;
case '+':
case '-':
Rank = 2;
break;
case '*':
case '/':
Rank = 3;
break;
default:
break;
}
return Rank;
}
//非递归算法
int Calculater(char arr[]) {
char N; // 用来依次读取从栈中取出的操作符
MyStack<char> Op; // 栈op:存储操作符
Op.push('#');//在头部插入#,用来判读栈底
// for (int i = 0; i < (int)strlen(arr);) {//主循环,逐字符判断
for (int i = 0; arr[i] != '=';) {//主循环,逐字符判断
if (isOperands(arr[i])) { // 如果是数字 就直接出栈 并把连续的数字组成一个完整的数输出 用空格分开
for (; isOperands(arr[i]); i++) std::cout << arr[i];
std::cout << " ";
}
else if (arr[i] == '(') { // 如果是左括号( 就直接进栈
Op.push(arr[i]);
i++;
}
else if (isOperator(arr[i])) { // 判断是否为操作符操作符
while (Op.top() != '#') {
N = Op.top();
if (GetPriority(arr[i]) <= GetPriority(N)) {
// 优先级低或等于栈顶则直接输出
std::cout << N << " ";
Op.pop();
}
else // ch优先级高于栈中操作符
break;
}
Op.push(arr[i]); // 入栈
i++;
}
else if (arr[i] == ')') { // 如果是右括号,一直推出栈中操作符,直到遇到左括号(
while (Op.top() != '(') {
if (Op.top() == '#') { std::cout << " 括号不匹配 ";
return 0;
}
std::cout << Op.top() << " ";
Op.pop();
}
Op.pop(); // 左括号出栈
i++;
}
else {
std::cout << " 非法字符!!! ";
return 0;
}
} //主循环for结束(扫描结束)
while ( Op.top() != '#') { // 当栈不空,继续输出操作符
std::cout << Op.top() << " ";
Op.pop();
}
return 1;
}
//递归算法
int D_Calculater(char arr[], MyStack<char> Op1) {
if (isOperands(arr[0])=='('&& isOperands(arr[0]) != ')'&&isOperands(arr[0]) != '='&&!isOperands(isOperands(arr[0])) && !isOperator(isOperands(arr[0]))){
std::cout << " 非法字符!!! ";
return 0;
}
if (isOperands(arr[0])) {
int i = 0;
for (; isOperands(arr[i]); i++) std::cout << arr[i];
std::cout << " ";
D_Calculater(&arr[i], Op1);
}
else if (arr[0] == '(') {
Op1.push(arr[0]);
D_Calculater(&arr[1], Op1);
}
else if (isOperator(arr[0])) {
while (Op1.top() != '#') {
char c = Op1.top();
if (GetPriority(arr[0]) <= GetPriority(c)) {
// 优先级低或等于
std::cout << c << " ";
Op1.pop();
}
else // ch优先级高于栈中操作符
break;
} // while结束
Op1.push(arr[0]); // 防止不断的推出操作符,最后空栈了;或者ch优先级高了
D_Calculater(&arr[1], Op1);
}
else if (arr[0] == ')') {
while (Op1.top() != '(') {
if (Op1.top() == '#') {
std::cout << " 括号不匹配 ";
return 0;
}
std::cout << Op1.top() << " ";
Op1.pop();
}
Op1.pop(); // 左括号出栈
D_Calculater(&arr[1], Op1);
}
else if (arr[0] == '=' && Op1.top() != '#') { //递归出口(扫描到#)
std::cout << Op1.top() << " ";//输出栈中剩下的元素
Op1.pop();
D_Calculater(&arr[0], Op1);//确保栈中元素输出完(可用循环代替)
return 1;
}
return 1;
}
int main() {
std::cout << "\n请输入要转换的中缀表达式(以=结尾)\n";
while(1){
std::cout << "\n------------Begin------------\n";
std::cout << "表达式:";
char x[250];
MyStack<char> Oper; // 栈oper:存储递归方式下的操作符
Oper.push('#');//在头部插入#,用来判读栈底
std::cin.getline(x, 250);//将读到的表达书存入字符数组x,格式(接收字符串的变量,接收字符个数)
if(x[0]=='0') return 0;//输入0 退出系统
std::cout << "\n非递归转换后的后缀表达式为: ";
Calculater(x);
std::cout << "\n 递归转换后的后缀表达式为: ";
D_Calculater(x, Oper);
std::cout << "\n-------------End-------------\n";
}
return 0;
}
五、用户手册:
打开系统后,待弹出输入要转换的中缀表达式提示后,开始键入表达式。
格式要求为:1.必须以等号结尾 2.必须是正确的表达式 3.支持的字符有数字0-9、大小写字母、小数点、小括号、加减乘除、等于号 4.以上所有字符均为英文下的字符。
输入完表达式后,单击回车即可计算,系统自动显示用递归与非递归方法计算出的后缀表达式,然后可以再次输入其他表达式。
若输入的表达式括号不匹配或者存在非法字符,则直接结束本次计算,输入单个数字0可以关闭软件。
六、测试数据及测试结果:
测试输入:5+8*(9.3-1)-4/9=
测试目的:设计该输入的目的在于测试程序能否处理含整数和小数的表达式;
正确输出:5 8 9.3 1 - * + 4 9 / -
实际输出:5 8 9.3 1 - * + 4 9 / -
测试输入:(A+B)D+E/(F+AD)+C=
测试目的:设计该输入的目的在于测试程序在哪方面可能存在漏洞;
正确输出: A B + D * E F A D * + / + C +
实际输出: A B + D * E F A D * + / + C +
测试输入:0
测试目的:设计该输入的目的在于测试程序能否按照预期的停下来;
正确输出:程序终止
实际输出:程序终止
测试输入:88*9)=
测试目的:设计该输入的目的在于测试程序能否检测括号是否匹配;
正确输出:违法括号之前的正确后缀+“括号不匹配”
实际输出:违法括号之前的正确后缀+“括号不匹配”
测试输入:[]
测试目的:设计该输入的目的在于测试程序能否检测违法字符;
正确输出:非法字符!!!
实际输出:非法字符!!!