题目:使用栈实现算法表达式中缀转后缀形式。运算符只包括+,-, *,/,以及括号。
要求:
(1)给出算法的设计思想;
(2)根据设计思想,采用C语言描述算法,关键之处给
出注释;
(3)说明你设计的算法时间复杂度。
一、算法的设计思想
1.理解为什么要转换为后缀表达式
中缀表达式便于人们的理解与计算,但后缀表达式更方便计算机的运算。
2.假设要转换的表达式为a+bc+(de+f)*g进行一个实例推理
读取到a,直接输出
读到“+”,将其放入栈中
读到b,再直接输出
此时栈和输出的情况如下:
紧接着读到“*”,
因为目前栈顶元素“+”的优先级比“”低,所以将“”压入栈中,成为新的栈顶。
读到c,直接输出
此时栈和输出的情况如下:
下一步读到“+”,因为栈顶元素“”的优先级比它高,所以弹出“”并输出,同时,栈中下一个元素“+”的优先级与目前读取到的“+”一样,所以也要弹出并输出。故此时先后输出“*”“+”输出并从栈中弹出,然后将当前读到的“+”压入栈中。
此时栈和输出的情况如下:
下一个读到的为“(”,它的优先级最高,所以就直接入栈。
读到d,将它直接输出。
此时栈和输出情况如下:
接下来读到“”,由于左括号比较特殊,只有碰到右括号才弹出,所以这时候直接将“”压入栈中。
读到e,直接输出。
此时栈和输出情况如下:
读到“+”,弹出“”并输出,(不输出左括号及下面的)然后将当前读取到的“+”压入栈中。
读到f,直接输出。
此时栈和输出情况:
接下来就读到了“)”,从栈顶向下依次将左右括号和其中间的数据弹出,此时左右括号元素中间的数据元素需要打印,而左右括号不需要打印。
读到“”,由于其优先级高于当前栈顶元素“+”,所以将“”压入栈中。紧接着读到g,直接将其输出。
此时输入数据已经全部读完, 栈中还剩下两个操作符“”和“+”,直接弹出并输出。
至此,中缀转后缀整个转换过程完成。
3.抽象出中缀表达式转后缀表达式的算法规则
(1)遇到操作数值,直接输出;
(2)遇到操作符比栈内已有操作符优先级高的,直接放入栈中,遇到左括号也将其放入栈中。
(3)如果遇到右括号,将栈中的元素弹出输出,直到遇到左括号,左括号只弹出不输出。
(4)遇到任何其他的操作符,如(“+”,“”,“(”)等,从栈中弹出输出元素直到遇到发现更低优先级的元素(或者栈为空)为止,优先级同样也弹出。弹出输出完这些元素后,再将当前遇到的操作符压入栈中。注意:“(”优先级最高,只有遇到“)”才弹出,其他情况一律不弹出。
4.核心算法思想:
(1)对声明的栈进行初始化,
(2)声明一个i对传入的字符数组进行遍历
(3)当遇到“#”时,表达式输入结束
(4)循环:
a.字符不等于“#”,进入while循环体。
b.内嵌一个循环,当字符大于等于“0”并且小于等于“9”或者字符大于等于“a”并且小于等于“z”时,即表示该字符为在0-9范围内的数字或者在a-z之间的字母,遇到数字或者字母直接输出。当前i为数字或者字母的时候直接输出,同时对i进行++操作,在这个循环里内嵌一个if判断语句(该循环的用处主要在于数字),如果下一个i属于数字范围内,继续当前循环,即直接打印出,但是如果下一个i不在数字范围内,就打印出空格然后跳出当前循环,这样也做到了当出现了多位数时采用空格间隔开。例如运算符之间有一个数据10,首先i指向1,打印出,然后下一个i指向0此时继续打印出,但再下一个i为运算符,不属于数字,就跳出当前循环,最后就打印出了一个完整的二位数10。
c.接下来如果字符等于‘)’右括号,把栈里剩余的运算符弹出并打印,直到匹配到左括号为止,这时候左括号只弹出不打印,目前匹配到的右括号也不入栈。
d.如果字符等于‘+’或者‘-’,加减优先级最低,如果栈为空,直接入栈,否则要将栈中所有的运算符都弹出栈并打印,注意遇到左括号要停止,将弹出的左括号重新进入栈中。最后将当前匹配到的“+”或者“-”放入栈中。
e.如果字符等于“”或者“/”或者“(”,直接进入栈,因为其优先级最高。
f.如果匹配到字符等于“#”,即表示着表达式输入结束,这时候就可以跳出整个大循环。
g.最后,如果以上if分支都不满足,则不符合我们要求的表达式输入格式,打印出“输入格式错误!”语句。
(5)最后一次出栈操作。当上述的循环跳出后,如果栈不为空,即里面还有运算符,这时候直接按照栈顶到栈底的顺序将栈内的所有数据弹出打印即可。
二、描述算法
核心算法代码:
void TransForm(SqlStack *s,ElemType str[])
{
int i=0; //声明一个i对字符串里的字符进行遍历
ElemType e; //声明一个e来存放char元素,比如接收后续弹出的字符等
InitStack(s); //对栈进行初始化
while(str[i] != '#') //只要字符不等于“#”就一直在循环内
{
while((str[i]>='0' && str[i]<='9')||(str[i]>='a' && str[i]<='z'))
{
//如果是数字或者小写字母,则直接输出
printf("%c",str[i++]);
if(str[i]<'0' || str[i]>'9')
//如果这里为数字,则直接打印出,这样就可以打印出多位数,且依靠下面的空格将各个数直接分隔开。
{
//如果不是数字,则打印空格跳出循环
printf(" ");
}
}
if(')' == str[i]) //遇到字符为右括号
{
Pop(s,&e); //把栈里剩余的运算符弹出
while('(' != e ) //直到匹配到左括号位置
{
printf("%c ",e); //打印运算符
Pop(s,&e); //循环弹出栈里的剩余运算符
}
}
else if('+'==str[i] || '-'==str[i]) //加减优先级最低
{
if(!StackLen(s)) //如果栈为空栈,即加减号可以直接入栈
{
Push(s,str[i]);
}
Else //栈不为空
{
do
{
Pop(s,&e); //将所有的运算符都弹出栈
if('('==e)
{
Push(s,e);//遇到左括号就停止,且将弹出的左括号重新入栈
}
else
{
printf("%c ",e); //将弹出栈的元素打印出来
}
}
while(StackLen(s) && '('!=e); //最后要将当前匹配到的加号或者减号放入站内
Push(s,str[i]);
}
}
else if('*'==str[i] || '/'==str[i] || '('==str[i])
{
Push(s,str[i]); //乘除和左括号,直接入栈,优先级最高
}
else if('#'==str[i]) //“#”表示输入结束,直接跳出当前大循环
{
break;
}
else //不符合上述任何情况,打印格式出错提示语句
{
printf("\n出错:输入格式错误!\n");
exit(0);
}
i++; //进行下一个元素的遍历
}
//最后一次出栈输出
while(StackLen(s)) //上述循环结束后,如果栈不为空
{
Pop(s,&e); //直接将栈内的元素弹出
printf("%c ",e); //打印弹出的元素
}
}
三、算法时间复杂度
在上述的核心代码中,第一个while循环里,里面有很多分支,都是在满足特定条件的情况下才执行,而“i++”这条语句,除了最后结束的符号为“#”,在其他情况下,都要执行,循环一次执行一次,也是嵌套在循环里最深的语句,而“i++”语句的执行次数与我们从键盘端输入进行的字符串长度(含#)有关,例如我们输入“1+2+3#”,这条字符串的长度为6,而第一步i被初始化为0,接下来执行5次,等于“#”时跳出循环。
从上述推理可以得出,当输入的字符串长度为n(含#),“i++”语句执行次数T(n)=n-1.
即可以得到算法时间复杂度为O(n)。