草庐IT

数据结构课程实验二:运用栈实现表达式求值

长安er 2023-07-12 原文

实验日期:2022-10-31

目录

一、实验目的

二、实验内容

三、实验提示 

四、实验思路

1.整体思路

2.详细思路

五、实验思考过程

六、代码运行截图

1.个位数的运算

2. 多位数的运算

3.非法输入的情况 

七、程序流程图

八、个人收获及实验总结

1.个人收获 

2.实验总结

九、实验源代码


一、实验目的

1、掌握栈与队列的定义;

2、掌握掌握栈与队列的基本操作。

二、实验内容

1.验证某算术表达式的正确性,若正确,则计算该算术表达式的值。

(1)、从键盘上输入表达式的值。

(2)、分析该表达式是否合法:

(a)是数字,则 判断该数字的合法性。若合法,则压入数据存放堆栈。

(b)是规定的运算符,则根据规则进行处理。在处理过程中,将计算该表达式的值。

(c)若是其它字符,则返回错误信息。

(3)、若上述处理过程中没有发现错误,则认为该表达式合法,并打印处理结果。

三、实验提示 

表达式的计算过程:

1、变量直接输出;

2、遇到“(”,无条件入栈;

3、输入运算优先数是2,3,4,5,时,如栈空则入栈,否则,将其与栈顶元素进行比较,如果优先级大,就进栈,否则就退栈输出; 

4、如遇“)”,同时栈顶元为“(”,则退栈,抹去括号。

四、实验思路

1.整体思路

首先将用户输入一整串的运算式存入字符exp数组然后通过栈进行一系列处理得到运算结果

2.详细思路

(1)输入运算式定义并初始化数字栈和字符栈分别用于存储输入数据中的数字和运算符

(2)若为数字则压入数字栈此处与众不同的是通过归并的思想可实现多位数的运算

(3)若为运算符则首先进行与栈顶元素优先级的判断若优先级低于栈顶元素则运算符栈栈顶元素弹出同时弹出数字栈两个栈顶元素进行运算得到的运算结果压入数字栈若优先级高于栈顶元素则直接入栈若优先级相等则栈顶元素出栈

(4)若为空格则直接跳过

(5)若为其他情况则判为非法字符输入程序停止运行

(6)经过一系列的运算出栈入栈的操作后最终获取数字栈中的数据可得运算结果

五、实验思考过程

此次实验代码经过反复思考通过三次更改得到了最后的版本

  1. 前两次最核心的思想便是将输入的中缀表达式先转化为后缀表达式然后在对后缀表达式进行结果求值的运算
  2. 处理中缀表达式需要两个字符数组,一个读入字符串(a),另一个存放后缀表达式的字符串,另外需要一个栈对符号进行入出栈操作,这个栈称为”字符栈“(CharStack),将处理后的结果存入另一个字符数组中(b),该字符数组(b)中所存字符串被称为后缀表达式。而处理后缀表达式则需要上述字符数组b和一个数字栈完成运算
  3. 前两种版本存在的问题受ASCII数字字符的判断条件限制其仅能够实现对个位数的运算对于多位数则很难判别出其具体数位构成功能大大缩减
  4. 由于存储多位数时一个数组单元格只能存一位数字所以在现版本中采用了一种归并的思想若输入的字符数组第一位和第二位数后一位均为数字则不断归并至后一位元素不为数字为止将这些数按照位数权加此时得到的便是完整的多位数如下

5.此程序总体功能:可实现多位整数的运算但由于时间有限,尚未想出浮点数的求值算法

 6.现版本输入方式的选取:整体输入此方式可通过直接建立一个字符数组利用gets方法或scanf函数获取输入的数据逐个输入则需要使用getchar()进行逐个读取整体输入相对逐个输入更加直观

7.优先级判断函数的思路在前两个版本中我采用的是比较直白的if语句列出可能的情况进行结果输出现版本受网上资料的启发采用以二维数组存储优先列表将可能的7*7种情况列出利用switch语句输出对应的结果更加直观

8.提高程序健壮性的体现

(1)整个程序运算中无可避免的出现了除法运算因此在求值函数operate中当theta为“/利用if语句判断若除数为0,则提示用户输入错误并结束程序

(2)运算式输入的字符串出现非法字符、空格的处理在evaluateExpression函数中同样采用if语句进行判断若为空格则使用continue跳过若不为字符数字和空格则输入为非法字符程序将提示输入错误

(3)在对栈操作方面的体现初始化栈时对内存是否申请成功进行判断入栈时对是否栈满进行判断出栈和获取栈顶元素时对是否栈空进行判断

9.程序运行界面友好的体现输入输出方面进行一系列的提示以输入为更重要受程序算法限制输入的运算式必须以“#结尾否则无法准确的识别

六、代码运行截图

1.个位数的运算

2. 多位数的运算

3.非法输入的情况 

七、程序流程图

八、个人收获及实验总结

1.个人收获 

(1)通过此次实验对栈这一数据结构有了更深一步的认识也更加熟悉了栈的一系列表示和实现操作

(2)通过表达式求值方法的探索深入理解了“后缀表达式”的含义并掌握了将中缀表达式转化为后缀表达式的方法这同样也是表达式求值的重要算法

(3)在功能扩展方面将个位数的运算提升到多位数的运算很好的学习并利用了归并的思想其与递归思想有很多类似之处

(4)对C语言和C++两种编程语言差别的认识又多了一个细微的收获

最初采用的是这种函数参数即函数参数带&符号与教材上的伪代码一致但在实际运行时Xcode编译器却一直对此处报错结果如下)。查阅资料后发现,&为引用符它是C++的内容当需要对原变量进行修改时可以给参数添加&。但部分C语言编译器是无法适用的

问题的解决思考后发现这种函数参数加&的功能与C语言指针的功能十分相似因此将函数参数定义为指针类型即可很好的解决此问题另一方面也可以直接将文件后缀名从.c

改为.cpp,即改成C++文件同样可以解决此问题

2.实验总结

表达式求值这一算法核心在于对运算式的运算过程利用栈来实现是很方便的因此便涉及运算符优先级的判断和如何对进行有序的运算的思考此外对功能是否可以扩展的思考同样极为重要其可大大提高程序的实

九、实验源代码

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
//利用宏常量定义最大存储空间
#define Maxsize 100

//定义数字栈
typedef struct Stack
{
    int *top;
    int *base;
    int stacksize;
}sqstack;

//构造空栈
void initStack(sqstack *s)
{
    //申请内存空间
    s->base=(int *)malloc(Maxsize*sizeof(int));
    
     if(!s->base)
     {
        printf("栈初始化失败!\n");
        return;
     }
    //初始化,此时栈空
    s->top=s->base;
    s->stacksize=Maxsize;
    return;
}

//入栈操作
int push(sqstack *s,int e)
{
    if(s->top-s->base==s->stacksize)
    {
        printf("栈满!\n");
        return 0;
    }
    *s->top++=e;
    return 1;
}

//出栈操作
int pop(sqstack *s,int  *elem)
{
    if(s->base==s->top)
    {
        printf("栈空!\n");
        return 0;
    }
  *elem=*--s->top;
    return 1;
}

//获取栈顶元素
int getTop(sqstack *s)
{
    if(s->base==s->top)
    {
        printf("栈空!无法获取元素!\n");
        return 0;
    }
    return *(s->top-1);
}

//判断运算符优先级
char Precede(char theta1,char theta2)
{
    int i = 0,j = 0;
    //对7*7种可能性进行分列,得到最后的运算符对比结果
    char pre[7][7]={// +   -   *   /   (   )   =
                     {'>','>','<','<','<','>','>'},
                     {'>','>','<','<','<','>','>'},
                     {'>','>','>','>','<','>','>'},
                     {'>','>','>','>','<','>','>'},
                     {'<','<','<','<','<','=','0'},
                     {'>','>','>','>','0','>','>'},
                     {'<','<','<','<','<','0','='}};
                
    switch(theta1){
        case '+': i=0; break;
        case '-': i=1; break;
        case '*': i=2; break;
        case '/': i=3; break;
        case '(': i=4; break;
        case ')': i=5; break;
        case '=': i=6; break;
    }
    switch(theta2){
        case '+': j=0; break;
        case '-': j=1; break;
        case '*': j=2; break;
        case '/': j=3; break;
        case '(': j=4; break;
        case ')': j=5; break;
        case '=': j=6; break;
    }
    return(pre[i][j]);
}

//对操作数进行运算
int Operate(int a,char theta,int b)
{
    switch(theta){
        case'+':return a+b;
        case'-':return a-b;
        case'*':return a*b;
        case'/':
            //考虑分母为0的情况,若除数为零返回错误提示
                if(b!=0)
                return a/b;
                else
                {
                    printf("分母为0,无意义!\n");
                    exit(0);
                }
    }
    return 0;
}

//判断是否为运算符,若是运算符返回1,若不是返回0
int isChar(char c)
{
    switch(c){
        case '+':
        case '-':
        case '*':
        case '/':
        case '(':
        case ')':
        case '=':
                 return 1;
         default:
                 return 0;
    }
}

//核心算法,进行运算式运算
int evaluateExpression(char exp[])
{
    //定义运算数栈、运算符栈
    sqstack OPND,OPTR;
    
    /* a,b,theta用于Operate函数
    X用于存放多余的出栈字符
    X1,X2用于归并 */
    int  a,b;
    int x,X1,X2,theta;
    
    //读取字符的变量
    char ch;
    //指向存放表达式数组的下标指针(其实不是真正的指针,而是数组下标)
    int i=0;
    //建立并初始化运算数栈OPND
    initStack(&OPND);
    //建立并初始化运算符栈OPTR
    initStack(&OPTR);
    //先将“=”号压入OPTR栈(表达式也必须以“=”结束)
    push(&OPTR,'=');
    //ch读取字符数组第一个元素,并将指针i后移一位
    ch=exp[i++];
    
    //表达式没有扫描完毕或OPTR的栈顶不为“=”
    while(ch!='='||getTop(&OPTR)!='=')
    {
        if(isChar(ch)) //判断ch是否为操作符
        {
            //比较ch的与OPTR栈顶元素的优先级
            switch(Precede(getTop(&OPTR),ch))
            {
                case'<':
                        push(&OPTR,ch);
                    //读取下一位字符并将指针向后偏移一位
                        ch=exp[i++];
                        break;
                
                case'>':
                    /*若栈顶操作符优先级更大,则弹出运算符栈顶运算符及数字栈栈顶两个运算数
                     */
                        pop(&OPTR,&theta);
                        pop(&OPND,&b);
                        pop(&OPND,&a);
                    //运算结果压入数字栈
                        push(&OPND,Operate(a,theta,b));
                        break;
                
                case'=':
                    //若优先级相当,则将运算符栈栈顶元素弹出
                        pop(&OPTR,&x);
                    //读取下一位字符并将指针向后偏移一位
                        ch=exp[i++];
                        break;
            }
        
        }
        
        //判断是否为数字字符
        else if(isdigit(ch))
        {
            //将字符形式转化为数字
            X1=ch-'0';
            push(&OPND,X1);
            X2=X1;
            //读取下一位字符并将指针向后偏移一位
            ch=exp[i++];
            //归并思想,实现多位数运算
            while(isdigit(ch)) //判断下一位是否还是数字
            {
                X1=ch-'0';
                X2=10*X2+X1; //归并至X2
                pop(&OPND,&x);
                push(&OPND,X2);
                ch=exp[i++]; //读取下一位字符并将指针向后偏移一位
                
            }
        }
        
        else if(ch==' ') //判断是否为空格
        {
            while(ch==' ') //跳过若干个空格
            {
                ch=exp[i++]; //忽略空格,直接取下一位
            }
            
        }
        
        else //若不是上述三种情况之一,则为非法字符
        {
            printf("Input has illegal characters!\n");
            printf("Please enter again.\n");
            exit(0); //返回错误提示
        }
    }
    return(getTop(&OPND)); //最后返回操作数栈顶为运算结果
}

int main()
{

 char exp[100]; //定义一个字符数组用于存储表达式
  int result;
  printf("请输入运算式(运算式尾部务必加上‘=’):\n");
    gets(exp);
  result=evaluateExpression(exp);
  printf("运算式及运算结果如下:\n");
  printf("%s%d\n",exp,result);
  
    return 0;
}

分享结束,感谢观看!!!

有关数据结构课程实验二:运用栈实现表达式求值的更多相关文章

  1. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby 正则表达式 - 如何替换字符串中匹配项的第 n 个实例 - 2

    在我的应用程序中,我需要能够找到所有数字子字符串,然后扫描每个子字符串,找到第一个匹配范围(例如5到15之间)的子字符串,并将该实例替换为另一个字符串“X”。我的测试字符串s="1foo100bar10gee1"我的初始模式是1个或多个数字的任何字符串,例如,re=Regexp.new(/\d+/)matches=s.scan(re)给出["1","100","10","1"]如果我想用“X”替换第N个匹配项,并且只替换第N个匹配项,我该怎么做?例如,如果我想替换第三个匹配项“10”(匹配项[2]),我不能只说s[matches[2]]="X"因为它做了两次替换“1fooX0barXg

  4. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  5. ruby - Ruby 有 `Pair` 数据类型吗? - 2

    有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳

  6. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  7. ruby - 我如何添加二进制数据来遏制 POST - 2

    我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_

  8. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  9. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  10. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

随机推荐