数据结构实验--表达式的后缀表示

2023-11-08

一、问题描述

表达式中包含运算对象、运算符和圆括号等,习惯上使用中缀表示(指运算符夹在两运算符对象中间)形式。计算表达式的值,涉及到运算符的优先级别,如先乘除后加减。括在一对圆括号中的子表达式必须先计算,因此,圆括号可视为特殊的运算符,具有最高优先级别。圆括号可以任意嵌套,这意味着左圆括号后面又是表达式,形成表达式的递归定义。

为了直接指明表达式中各运算对象的先后计算顺序,可将表达式的中缀形式转换成后缀(指运算符放在二运算对象的后面)形式。例如,表达式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)=
测试目的:设计该输入的目的在于测试程序能否检测括号是否匹配;
正确输出:违法括号之前的正确后缀+“括号不匹配”
实际输出:违法括号之前的正确后缀+“括号不匹配”

在这里插入图片描述

测试输入:[]
测试目的:设计该输入的目的在于测试程序能否检测违法字符;
正确输出:非法字符!!!
实际输出:非法字符!!!
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

数据结构实验--表达式的后缀表示 的相关文章

随机推荐

  • 区块链常见的几大共识机制

    区块链技术被广泛应用于许多领域 其中共识机制是构成区块链系统的核心部分 共识机制是指用来维护区块链数据一致性 安全性和可靠性的机制 常见的区块链共识机制有以下几种 1 工作量证明 Proof of Work 是最早的共识机制 它将矿工通过解
  • 毕业设计-基于机器视觉的交通标志识别系统

    目录 前言 课题背景和意义 实现技术思路 一 交通标志识别系统 二 交通标志识别整体方案 三 实验分析 四 总结 实现效果图样例 最后 前言 大四是整个大学期间最忙碌的时光 一边要忙着备考或实习为毕业后面临的就业升学做准备 一边要为毕业设计
  • 开源项目Tinyhttp项目源码详解

    HTTP协议 http协议叫做超文本传输协议 是基于tcp ic协议的应用层协议 具体内容可以借鉴这一篇博客 https blog csdn net qq 36894974 article details 103930478 本文主要涉及T
  • [人工智能-深度学习-33]:卷积神经网络CNN - 常见分类网络- LeNet网络结构分析与详解

    作者主页 文火冰糖的硅基工坊 文火冰糖 王文兵 的博客 文火冰糖的硅基工坊 CSDN博客 本文网址 https blog csdn net HiWangWenBing article details 120893764 目录 第1章 卷积神
  • Ubuntu14.04安装配置NFS用于挂载嵌入式文件系统

    Ubuntu14 04安装配置NFS用于挂载嵌入式文件系统 1 安装 sudo apt get install nfs kernel server rpcbind 2 配置 vi etc exports 在文件的最后一行加上 yaffs2
  • 获取随机位数阿拉伯数字

    int Math random 9 1 1000 这里是随机4位数 需要几位数 就乘以几个零 int Math random 9 1 100 随机3位数 int Math random 9 1 10 随机2位数 来个方法吧 获取随机位数的阿
  • IPSec 基础介绍

    IPSec是IETF Internet Engineering Task Force 制定的一组开放的网络安全协议 它并不是一个单独的协议 而是一系列为IP网络提供安全性的协议和服务的集合 包括认证头AH Authentication He
  • python TimedRotatingFileHandler 配置参数 (转)

    TimedRotatingFileHandler这个模块是满足文件名按时间自动更换的需求 这样就可以保证日志单个文件不会太大 用法很简单 示例代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 impo
  • python学习之【模块】

    前言 上一篇文章 python学习之 深拷贝 中学习了python中的深浅拷贝学习内容 这篇文章接着学习python中的模块 什么是模块 在python中 一个文件 以 py 为后缀名的文件 就叫做一个模块 每一个模块在python里都被看
  • 群晖做网页服务器_群晖NAS软件DS get介绍及使用方法教程

    我的NAS介绍第二篇 群晖NAS软件介绍与应用之DS get篇前言 1 为什么选择NAS之所以我现在建议大家选择NAS 不仅仅因为网盘的不稳定性和不安全性 遇到和谐大神不说 网盘也经历了各种风风雨雨 从和谐到倒闭不过一步之遥 大家都懂的 还
  • Mysql-连接https域名的Mysql数据源踩的坑

    背景介绍 大家在实际项目中 大部分都会用到关系数据库mysql 通常数据库服务器提供的都是ip的方式 所以不会出现本文涉及到的https域名的问题 本文介绍的是基于数据库服务器是分配了指定域名且有ssl证书的https 连接数据源 遇到的问
  • java面向对象基础练习--实现简单的图书管理系统

    这个系统使用的是java的基础语法 没有使用数据库 实现图书管理系统基础的查询 增加 删除 借阅 归还 打印 退出功能 这个小项目作为我java基础语法的综合运用 主要是为了建立面向对象编程的思想 培养编程习惯 如果有错误或者更好的实现方法
  • 深入详解ThreadLocal

    本文已收录至GitHub 推荐阅读 Java随想录 微信公众号 Java随想录 原创不易 注重版权 转载请注明原作者和原文链接 文章目录 什么是ThreadLocal ThreadLocal 原理 set方法 get方法 remove方法
  • 又回来了

    又回来了 一年多没有来了 再次回来还是感觉那么熟悉 那么亲切 怀念以前在学校的日子 怀念苦苦思索技术的日子 除了学习没有繁杂的社会关系要处理 单纯快乐着 为了一个小小的技术难题 愿意不吃不喝去摸索 去测试 在成功的那一刻忘记了一切疲倦和劳累
  • c++ 实现数据库连接池

    c 实现数据库连接池 自己尝试用c 新标准实现了数据库连接池 代码简化了很多 思路 将数据库的连接当作一个对象添加进list队列中 在连接池创建的时候就建立好队列 并添加自定义大小的连接对象 连接对象用智能指针来管理 现代c 中不应该出现d
  • Api接口版本管理实现

    Api接口版本管理实现 引言 实现 RequestCondition 实现代码 ApiVersion注解 ApiVersionRequestCondition 版本匹配 ApiVersionHandlerMapping 将condition
  • fastjson对泛型的反序列化

    文章目录 具体告警分析 告警影响 fastjson未指定泛型具体类型 fastjson TypeReference指定泛型具体类型 可以看到fastjson反序列化时IDEA提示告警 Unchecked assignment 怎么解决这个告
  • ubuntu+vscode构建c++开发调试环境

    1 vscode下载与安装 下载 Visual Studio Code Mac Linux Windows下载deb文件 运行指令安装vscode sudo dpkg i xxx deb 如果报 dpkg 错误 另外一个进程已经为 dpkg
  • python 3.7版本 打不开 python 3.8 保存的pickle文件

    1 问题描述 最近有一个pickle文件 当我使用python3 7 读取的时候报错 ValueError unsupported pickle protocol 5 查找原因发现是原始文件是用高版本的python解释器 比如3 8 保存的
  • 数据结构实验--表达式的后缀表示

    一 问题描述 表达式中包含运算对象 运算符和圆括号等 习惯上使用中缀表示 指运算符夹在两运算符对象中间 形式 计算表达式的值 涉及到运算符的优先级别 如先乘除后加减 括在一对圆括号中的子表达式必须先计算 因此 圆括号可视为特殊的运算符 具有