课程设计目的:
通过该实践环节的锻炼,加深对数据结构与算法基本知识的认识,初步掌握数据结构与算法的设计与实现,以及利用算法开发应用系统的方法,提高进行工程设计和系统分析的能力,为毕业设计和以后的工程实践打下良好的基础。
课程设计要求:
1、独立思考,态度认真、按时出勤、表现良好,按时完成各阶段实践设计任务。
2、通过需求分析、算法设计、编程调试,掌握数据结构与算法基本理论知识,以及算法的实现步骤,体会算法设计的思想,完成题目要求的功能,对数据结构的理解充分,程序设计合理科学,界面友好,设计工具使用熟练。
3、能够按要求编写课程设计报告书,能正确阐述设计和设计结果、正确绘制流程图、系统和程序框图,报告论述充分,内容齐全,格式规范,文字通顺,条理清楚。
1、下达设计任务书:查阅文献资料,讨论理解题目,明确恰当的数据结构及存储结构,提出解决问题思路。
2、学生完成预设计:查阅资料,进行文献研究,完善解决思路;进一步分析题目中涉及的复杂问题,确定数学模型;讨论解决方案的可行性,特别针对算法复杂性、健壮性进行讨论;确定开发路线、实验平台等;配备实验环境等。
3、详细设计阶段:经教师审查通过预设计方案后,进行编程设计和系统运行调试。
4、设计总结阶段:完成课程设计报告书。
5、设计验收与答辩:完成设计的验收和答辩。
一、概述
1.1 问题描述
准确求解一个表达式的正确值,表达式中包含加、减、乘、除和括号
1.2 功能要求
(1)界面友好。
(2)当输入一个合法的表达式后,能够返回正确的结果。表达式中包括加、减、乘、除和括号。计算的数在实数范围内。能执行多重括号的嵌套运算。
(3)输入表达式不合法时,有错误信息提示。
1.3 存储结构
可以考虑使用静态栈数据结构
1.4 需求分析和问题分析
所设计的程序需要满足在输入正确的表达式和错误的表达式能够用一个程序进行运行,而不是单纯的输入一个表达式进行计算与验证。题目中要求输入的表达式要合法,若表达式合法,则输出的结果应该是正确的,反之则这个程序会弹出错误信息。本次设计运用到的是静态栈的数据结构,则应熟悉栈的基本操作与存储方式。求解表达式求值的这个问题运用到的是“算符优先法”,这种做法比较简单直观,而且是一种广为使用的表达式求值算法。
要把一个表达式翻译成正确求值的一个机器指令序列,或者直接对表达式求值,首先要能够正确解释表达式。算符优先法就是根据算术四则运算规则确定的运算优先关系,实现对表达式的编译或解释执行的。
在表达式计算中先出现的运算符不一定先运算,具体运算顺序是需要通过运算符优先关系的比较,确定合适的运算时机,而运算时机的确定是可以借助栈来完成的。将扫描到的不能进行运算的运算数和运算符先分别压入运算数栈和运算符栈中,在条件满足时再分别从栈中弹出进行运算。
1.5 程序的主要功能实现
通过对题目的分析,我们所设计的这个程序所具有的功能包括:对合法表达式进行赋值的运算和非合法表达式进行错误信息的显示。
(1)合法表达式求值:该程序可以判定用户所输入的表达式进行检验对错,若没有出现符号方面的问题,则称为合法表达式,并且正确计算出输入的合法表达式的计算结果。
(2)表达式错误信息的显示:该程序可以通过分析非合法表达式,检查出其中存在的错误进行提示,错误信息能够完整的显示在运行界面上。
1.6开发环境
Windows 11
VC6.0
1.7技术可行性分析
能够通过应用栈中的数据来进行有关表达式的运算,并在输入不合法的表达式时,能够显示错误信息,提醒用户该输入的表达式中所具有的错误并加以改正。此程序也能够正确的计算出一个合法表达式的数值,而且能够显示。
二、设计的基本概念和原理
通过对题目中所提供的功能要求以及在第一部分对该问题进行的需求分析,我们在设计表达式求值的算法时,需要使用的是数据结构中栈的相关概念以及基本操作。对于表达式中四个运算符,即加、减、乘、除这四个运算符,我们可以通过静态栈的方式对这四个运算符进行存储。对于这四个运算符的存储过程,反映出了栈在存储的过程当中应用到的“后进先出”的原则。应当建立两个栈,一个是用来存储运算符的,另一个是用来存储操作的数字的。首先扫描表达式,读入第一个字符ch,如果表达式没有扫描完毕至“#”或运算符的栈顶元素不为“#”时,循环进行如下的操作:
1.若ch不是运算符,则压入操作数栈,读入下一字符ch;
2.若ch是运算符,则根据运算符栈的栈顶元素和ch的优先级比较结果,做不同的处理:若是小于,则ch压入运算符栈,读入下一字符ch;若是大于,则弹出运算符栈栈顶的运算符,从操作数栈中弹出两个数,即出栈操作,进行相应运算,将所得到的结果压入操作数栈中,即入栈操作;若是等于,则运算符栈的栈顶元素是“(”且ch是“)”,这时弹出运算符栈的栈顶的“(”,相当于括号匹配成功,然后读入下一字符ch。
3.操作数栈的栈顶元素即为表达式的求值结果,然后返回此元素,将结果显示在运行界面中。
本次程序设计也运用到了将中缀表达式转换为后缀表达式的相关栈的应用,具体的操作方式是从左到右进行遍历;运算数,并直接输出;将左括号直接压入堆栈;右括号不断弹出栈顶运算符并输出直到遇到左括号;将该运算符与栈顶运算符进行比较,如果优先级高于栈顶运算符则压入堆栈,如果优先级低于或等于栈顶运算符则将栈顶运算符弹出并输出,然后比较新的栈顶运算符,直到优先级大于栈顶运算符或者栈空,再将该运算符入栈。如果对象处理完毕,则按顺序弹出并输出栈中所有运算符。
这次设计在进行操作数栈和运算符栈的处理时进行了顺序栈的入栈和出栈操作,通过在栈顶插入一个新的元素以及对栈顶元素的删除,最终得出了表达式的结果。同时在设计栈的时候,加入了对栈的空和满的判定操作,目的是为了得到栈顶元素,来进行下一步的运算操作,从而求解得到表达式的结果。
三、总体设计
图-1所示为该程序的设计的总体结构,通过输入操作数与运算符,首先判断字符类型并建立相应的栈,如果是字符型则存放运算符,然后运用优先级比较算法,若数据类型是浮点型,该栈需要存放的是操作数,综合两者进行计算,进一步去判断表达式是否合法。如果不合法,则需要给出错误提示,如果合法,则需要给出对应表达式正确的输出值。图-2所示为进行操作数栈和运算符栈的入栈以及出栈操作的原理示意图,顺序栈的插入和删除操作只在栈顶进行,同时从图中也反映出栈的后进先出的原则。
图-1 基本操作关系图
四、详细设计
4.1 建立栈操作
typedef struct my_stack
{
int a[N];
int top;
}ST;
这一步将栈顶指针以及栈的空间定义出来。
4.2 中缀转后缀
int isempty(ST *T);
int isfull(ST *T);
int gettop(ST *T);
int pop(ST *T);
void push(ST *T,int s);
void transfer(char *in,char *post);
float Calculate_zhong(char *post);
应用中缀转为后缀表达式的方法来计算表达式的值。
4.3 错误信息声明
通过设置合法表达式中可能出现的运算符的相关问题,总结出了以下几个问题。一是式中出现了两个小数点的情况;二是数字后直接跟着的是括号而不是数,造成错误出现;三是有连续两个运算符之间没有数字。此外,如果在式中出现非法的字符,程序也会显示错误信息。如果左右括号没有合理的进行匹配,也会导致错误信息的出现。将这些错误信息分别使用printf(“”)语句进行输出。
4.4 有关小数点的设定
设立一个flag量来判断表达式中的数字是否存在小数点,用len表示小数点前数的长度,aa[N]用来存放表达式的数字,先把数字的表达式存放到ch[N]中,再转化为数字存到aa[N]中。如果判断出数字中没有小数点的存在,则需求出相应的长度,并进行入栈操作。
4.5 运算操作
分别设立四个case,分别给加、减、乘、除四个运算符设立一个运算的方法,通过使用switch(post[i])来实现不同的运算结果之间的运算,最后将计算得到的结果在存入到栈顶并进行输出操作。最后得出的运算结果运用顺序栈的相关知识得知应当存储于栈顶。
五、软件测试
5.1 时间复杂度分析
假设表达式有n个数字,m个符号,那么中缀表达式转后缀表达式操作需要进行n+m次操作,而计算则需要f(m)次,这里的f(m)与m呈线性关系,因此时间复杂度可以认为是O(km+n)。
5.2 空间复杂度分析
由于需要用到栈来存放数字和符号,并且在数字,符号之间要加入空格来方便读取操作,所以该算法的空间复杂度为O(2m+2n-1)
六、简要的使用说明
如图-3,输入一个算术表达式,其合法,可以直接进行输出。
图-3 正确的输出操作
如图-4,因为出现了连续两个运算符之间没有数字的错误,显示出了错误信息,不能得出结果。
图-4 连续两个运算符之间没有数字
如图-5,数中出现了两个小数点的情况,显示出了错误信息,不能得出结果。
图-5 数中有两个小数点
如图-6,式中出现了数字之后直接跟括号的情况,没有运算符,不能显示出来结果,显示错误信息。
图-6 数字之后直接跟括号
void transfer(char *in,char *post)
{
ST T;
int i,j,flag=0;
int count;
int right=0,left=0;
T.top=-1;
for(i=0,j=0;in[i]!='\0';i++)
{
switch(in[i])
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':for(count=0;(in[i]<='9'&&in[i]>='0')||in[i]=='.';i++,j++)
{
post[j]=in[i];
if(in[i]=='.')
count++;
}
i--;
if(count>1)
{
printf("\n表达式错误!!!!\n\n错误原因:数中有两个小数点\n");
exit(0);
}
post[j]=' ';
j++;
flag=1;
break;
case '(':if(flag)
{
printf("\n表达式错误!!!!\n\n错误原因:数字后直接跟括号\n");
exit(0);
}
push(&T,in[i]);
left++;
break;
case ')':right++;
while(gettop(&T)!='(')
{
post[j]=pop(&T);
j++;
}
pop(&T);
break;
case '+':
case '-':if(!flag&&i!=0)
{
printf("\n表达式错误!!!!\n\n错误原因:有连续两个运算符之间没有数字\n");
exit(0);
}