文西马龙:http://blog.csdn.net/wenximalong/
接上一篇博文:韩顺平_PHP程序员玩转算法公开课(第一季)09_使用栈完成高级计算器(1)_学习笔记_源代码图解_PPT文档整理
如果运算式是:30+2*6-2的时候,上面的代码就出问题了这是jisuan.php代码中哪里考虑的不周全呢
.....
}else{
//是数字,就入数栈
$numsStack->push($ch);
}
当运算式是 30+2*6-2 的时候,它这样扫描
现在把30当成 3和0入栈了,把30这一个数当成3和0这两个数来处里了,必然出问题了
现在处里多位数的运算
为什么没有思路,见的少,这么多的php开源醒目可供学习
学会调试,echo
输入界面
calculator.php
- <html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
- </head>
- <body>
- <h1>高级计算器</h1>
- <form action='jisuan2.php'>
- 请输入一个运算表达式<input type='text' name='exp' />
- <input type='submit' value='计算' />
- </form>
- </body>
- </html>
结果
jisuan2.php
- <html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
- </head>
- <body>
- <h1>计算结果 </h1>
- <?php
- $exp=$_GET['exp'];
- //echo $exp.'结果是=';
- //$exp='3+2*6-2';
- //定义一个数栈和一个符号栈
- $numsStack=new MyStack();
- $operStack=new MyStack();
- $keepNum=''; //专门用于拼接多位数的字符串
- $index=0; //$index就是一个扫描的标记
- while(true){
- //依次取出字符
- $ch=substr($exp,$index,1); //从$exp里的$index位置取出1个字符
- //判断$ch是不是一个运算符号
- if($operStack->isOper($ch)==true){
- //说明是运算符
- /*
- 3.如果发现是运算符
- 3.1如果符号栈为空,就直接入符号栈
- 3.2如果符号栈不为空,就要判读。如果当前运算符的优先级小于等于符号栈顶的这个运算符的优先级,就计算,并把计算结果入数栈,然后把当前符号入栈(然后把当前符号入栈,此处会有问题,碰到复杂运算的时候,先考虑简单的,后面再优化,先死后活)
- 3.3如果符号栈不为空,就要判读。如果当前运算符的优先级大于符号栈顶的这个运算符的优先级,就入栈
- */
- //把思路转成代码
- if($operStack->isEmpty()){
- $operStack->push($ch);
- }else{
- //需要一个函数,来获取运算符的优先级
- //定义 *和/为1 +和-为0
- $chPRI=$operStack->PRI($ch); //这是当前的
- $stackPRI=$operStack->PRI($operStack->getTop()); //这是栈里的
- if($chPRI<=$stackPRI){
- //计算
- //从数栈里依次出栈两个数
- $num1=$numsStack->pop();
- $num2=$numsStack->pop();
- //再从符号栈里取出一个运算符
- $oper=$operStack->pop();
- //这里还需要一个计算的函数
- $res=$operStack->getResult($num1,$num2,$oper);
- //然后还要把$res入数栈
- $numsStack->push($res);
- //把当前这个符号再入符号栈。???这里会有问题,一会再解决
- $operStack->push($ch);
- }else{
- //如果当前运算符的优先级大于符号栈顶的这个运算符的优先级,就入栈
- $operStack->push($ch);
- }
- }
- }else{
- //先不要立马就入栈
- $keepNum.=$ch; //拼接
- //要判断一下$ch字符的下一个字符是数字还是符号
- //如果已经是末尾了,就不用再判断了
- //先判断是否已经到了字符串最后,是的话就直接入栈
- if($index==strlen($exp)-1){ //如果已经到了末尾,直接入栈
- $numsStack->push($keepNum); //要入拼接好的,而不是$ch
- }else{
- //要判断一下$ch字符的下一个字符是数字还是符号
- if($operStack->isOper(substr($exp,$index+1,1))){ //取出下一位的
- $numsStack->push($keepNum);
- $keepNum=''; //清空
- }
- }
- }
- $index++; //让$index指向下一个字符
- //判断是否已经扫描完毕
- if($index==strlen($exp)){
- break;
- }
- //当扫描完毕后,就break
- }
- /*
- 4.当扫描完毕后,就依次弹出数栈和符号栈的数据,并计算,最终留在数栈的值,就是运算结果。
- */
- //只要符号栈不空,就一直计算
- while(!$operStack->isEmpty()){
- $num1=$numsStack->pop();
- $num2=$numsStack->pop();
- $oper=$operStack->pop();
- $res=$operStack->getResult($num1,$num2,$oper);
- $numsStack->push($res);
- }
- //当退出while后,在数栈中一定有一个数,这个数就是最后结果
- echo $exp.'='.$numsStack->getTop();
- //这是我们自定义的栈
- class MyStack{
- private $top=-1; //默认是-1,表示该栈是空的
- private $maxSize=10; //$maxSize表示栈最大容量
- private $stack=array();
- //增加一个函数[提示,在我们开发中,根据需要可以灵活的增加你需要的函数,不要想着一步到位]
- //计算的函数
- public function getResult($num1,$num2,$oper){
- $res=0;
- switch($oper){
- case '+':
- $res=$num1+$num2;
- break;
- case '-':
- //$res=$num1-$num2;要注意减的顺序,这样不对
- $res=$num2-$num1; //注意顺序
- break;
- case '*':
- $res=$num1*$num2;
- break;
- case '/':
- $res=$num2/$num1; //注意顺序
- break;
- }
- return $res;
- }
- //返回栈顶的字符,只取,但是不出栈
- public function getTop(){
- return $this->stack[$this->top];
- }
- //判断优先级的函数
- //定义 *和/为1 +和-为0。先死后活,先考虑简单的,至于带括号的,在后面再改进。
- public function PRI($ch){
- if($ch=='*'||$ch=='/'){
- return 1;
- }else if($ch=='+'||$ch=='-'){
- return 0;
- }
- }
- //判断栈是否为空
- public function isEmpty(){
- if($this->top==-1){
- return TRUE;
- }else{
- return FALSE;
- }
- }
- //判断是不是一个运算符
- public function isOper($ch){
- if($ch=='-'||$ch=='+'||$ch=='*'||$ch=='/'){
- return TRUE;
- }else{
- return FALSE;
- }
- }
- //入栈的操作
- public function push($val){
- //先判断栈是否已经满了
- if($this->top==$this->maxSize-1){ //5-1=4 0 1 2 3 4
- echo'<br/>栈满,不能添加';
- return;
- }
- $this->top++; //先加再放
- $this->stack[$this->top]=$val; //就入栈了
- }
- //出栈的操作,就是把栈顶的值取出
- public function pop(){
- //判断是否栈空
- if($this->top==-1){
- echo'<br/>栈空';
- return;
- }
- //把栈顶的值,取出
- $topVal=$this->stack[$this->top];
- $this->top--;
- return $topVal;
- }
- //显示栈的所有数据的方法
- public function showStack(){
- if($this->top==-1){
- echo'<br/>栈空';
- return;
- }
- echo'<br/>当前栈的情况是...';
- for($i=$this->top;$i>-1;$i--){ //反着显示
- echo'<br/>stack['.$i.']='.$this->stack[$i]; //从栈顶开始显示
- }
- }
- }
- ?>
- </body>
- </html>
现在还有需要改进的地方,还没有完美
迭代开发,趋向完美
当碰到运算式:7*2-5*3-3 //14-15-3=-4
如果还是jisuan2.php,又出错了
这不是我们想要的结果
再对jisuan2.php改进
先分析 7*2-5*3-3
过程
(1)首先扫描到一个数字7,直接入数栈
(2)继续扫描发现是一个*,现在符号栈是空的,直接入符号栈
(3)继续扫描发现是一个数字2,直接入数栈
(4)继续扫描发现是一个-,现在符号栈不为空,并且-小于当前符号栈栈顶*的运算优先级,则计算。从数栈弹出两个数,从符号栈弹出一个运算符。即从数栈弹出7和2,然后从符号栈弹出*。7*2=14,再把14入数栈,并把-入符号栈
如下图所示:
(5)继续扫描发现是一个数字5,直接入数栈
(6)继续扫描发现是一个*,现在符号栈不为空,*是高于当前符号栈栈顶-的运算优先级,那直接入符号栈
(7)继续扫描发现是一个数字3,直接入数栈
(8)继续扫描发现是一个-,现在符号栈不为空,并且-小于当前符号栈栈顶*的运算优先级,则计算。从数栈弹出两个数,从符号栈弹出一个运算符。即从数栈弹出5和3,然后从符号栈弹出*。5*3=15,再把15入数栈, 这个时候就出现问题了
如下图所示:
(9)如果这个时候,你不加控制,就直接把刚才(8)中扫描到的-入栈。我们在上一篇博文中, 定义的思路3.2如下:【如果符号栈不为空,就要判读。如果当前运算符的优先级小于等于符号栈顶的这个运算符的优先级,就计算,并把计算结果入数栈,然后把当前符号入栈】。现在再把-入符号栈,而不是让14和15进行运算,就出问题了。
现在把-入符号栈,看出什么问题,看错误的思路会得出什么结果
(10)接着(9)中直接把-入符号栈的思路,继续扫描发现是一个数字3,直接入数栈
如下图所示:
至此扫描完毕
(11)现在开始依次弹出,先从数栈弹出3和15,从符号栈弹出-,计算15-3-->12(为什么是15-3,我们在jisuan2.php中,定义MyStack类的方法getResult($num1,$num2,$oper),$res=$num2-$num1;即用后出栈的数减先出栈的数)此时这个3就当成正数了。然后把结果12再入数栈
(12)再从数栈弹出12和14,从符号栈弹出-,计算14-12--->2,然后再把结果2入数栈,此时符号栈已经为空了,最终结果为2!!!★很显然这是不对的7*2-5*3-3 //14-15-3=-4,而不是2.
★问题出在哪里了呢?★是(8)中扫描到的-,-要入符号栈,就要先判断在符号栈中是否有和你-相等运算级别的运算符号,而不是直接就入符号栈省去判断的步骤。所以此时的-想入符号栈,就要不停的判断,只要它的优先级小于等于符号栈栈顶的优先级,则数栈和符号栈就一直向下运算,直到它的优先级小于符号栈栈顶的优先级(比如碰到栈顶是*或/,碰到栈顶是+和-都不行),此时它才能入符号栈。
所以把思路3.2修改如下:
如果符号栈不为空,就要判读。如果当前运算符的优先级小于等于符号栈顶的这个运算符的优先级,就计算,并把计算结果入数栈,【然后把当前符号入栈】(把这个话修改为:一直到当前符号的运算级别小于符号栈栈顶的优先级)
对jisuan2.php改进为jisuan3.php
jisuan3.php
- <html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
- </head>
- <body>
- <h1>计算结果 </h1>
- <?php
- $exp=$_GET['exp'];
- //echo $exp.'结果是=';
- //$exp='3+2*6-2';
- //定义一个数栈和一个符号栈
- $numsStack=new MyStack();
- $operStack=new MyStack();
- $keepNum=''; //专门用于拼接多位数的字符串
- $index=0; //$index就是一个扫描的标记
- while(true){
- //依次取出字符
- $ch=substr($exp,$index,1); //从$exp里的$index位置取出1个字符
- //判断$ch是不是一个运算符号
- if($operStack->isOper($ch)==true){
- //说明是运算符
- /*
- 3.如果发现是运算符
- 3.1如果符号栈为空,就直接入符号栈
- 3.2如果符号栈不为空,就要判读。如果当前运算符的优先级小于等于符号栈顶的这个运算符的优先级,就计算,并把计算结果入数栈,然后把当前符号入栈(然后把当前符号入栈,此处会有问题,碰到复杂运算的时候,先考虑简单的,后面再优化,先死后活)
- 3.3如果符号栈不为空,就要判读。如果当前运算符的优先级大于符号栈顶的这个运算符的优先级,就入栈
- */
- //把思路转成代码
- if($operStack->isEmpty()){
- $operStack->push($ch);
- }else{
- //需要一个函数,来获取运算符的优先级
- //定义 *和/为1 +和-为0
- //$chPRI=$operStack->PRI($ch); //这是当前的
- //$stackPRI=$operStack->PRI($operStack->getTop()); //这是栈里的
- //只要你准备入符号栈的运算优先级小于等于当前栈栈顶的运算优先级,就一直计算
- //直到这个条件不满足,我才把当前的符号入符号栈
- //并且只要符号栈不为空
- //!$operStack->isEmpty() && 这个尤为重要,少了此句话,就死循环了,有可能不停的判断
- while(!$operStack->isEmpty() && $operStack->PRI($ch)<=$operStack->PRI($operStack->getTop())){
- //从数栈里依次出栈两个数
- $num1=$numsStack->pop();
- $num2=$numsStack->pop();
- //再从符号栈里取出一个运算符
- $oper=$operStack->pop();
- //这里还需要一个计算的函数
- $res=$operStack->getResult($num1,$num2,$oper);
- //然后还要把$res入数栈
- $numsStack->push($res);
- }
- //经过上面的while判断后,再把当前这个符号再入符号栈。
- $operStack->push($ch);
- }
- }else{
- //先不要立马就入栈
- $keepNum.=$ch; //拼接
- //要判断一下$ch字符的下一个字符是数字还是符号
- //如果已经是末尾了,就不用再判断了
- //先判断是否已经到了字符串最后,是的话就直接入栈
- if($index==strlen($exp)-1){ //如果已经到了末尾,直接入栈
- $numsStack->push($keepNum); //要入拼接好的,而不是$ch
- }else{
- //要判断一下$ch字符的下一个字符是数字还是符号
- if($operStack->isOper(substr($exp,$index+1,1))){ //取出下一位的
- $numsStack->push($keepNum);
- $keepNum=''; //清空
- }
- }
- }
- $index++; //让$index指向下一个字符
- //判断是否已经扫描完毕
- if($index==strlen($exp)){
- break;
- }
- //当扫描完毕后,就break
- }
- /*
- 4.当扫描完毕后,就依次弹出数栈和符号栈的数据,并计算,最终留在数栈的值,就是运算结果。
- */
- //只要符号栈不空,就一直计算
- while(!$operStack->isEmpty()){
- $num1=$numsStack->pop();
- $num2=$numsStack->pop();
- $oper=$operStack->pop();
- $res=$operStack->getResult($num1,$num2,$oper);
- $numsStack->push($res);
- }
- //当退出while后,在数栈中一定有一个数,这个数就是最后结果
- echo $exp.'='.$numsStack->getTop();
- //这是我们自定义的栈
- class MyStack{
- private $top=-1; //默认是-1,表示该栈是空的
- private $maxSize=10; //$maxSize表示栈最大容量
- private $stack=array();
- //增加一个函数[提示,在我们开发中,根据需要可以灵活的增加你需要的函数,不要想着一步到位]
- //计算的函数
- public function getResult($num1,$num2,$oper){
- $res=0;
- switch($oper){
- case '+':
- $res=$num1+$num2;
- break;
- case '-':
- //$res=$num1-$num2;要注意减的顺序,这样不对
- $res=$num2-$num1; //注意顺序
- break;
- case '*':
- $res=$num1*$num2;
- break;
- case '/':
- $res=$num2/$num1; //注意顺序
- break;
- }
- return $res;
- }
- //返回栈顶的字符,只取,但是不出栈
- public function getTop(){
- return $this->stack[$this->top];
- }
- //判断优先级的函数
- //定义 *和/为1 +和-为0。先死后活,先考虑简单的,至于带括号的,在后面再改进。
- public function PRI($ch){
- if($ch=='*'||$ch=='/'){
- return 1;
- }else if($ch=='+'||$ch=='-'){
- return 0;
- }
- }
- //判断栈是否为空
- public function isEmpty(){
- if($this->top==-1){
- return TRUE;
- }else{
- return FALSE;
- }
- }
- //判断是不是一个运算符
- public function isOper($ch){
- if($ch=='-'||$ch=='+'||$ch=='*'||$ch=='/'){
- return TRUE;
- }else{
- return FALSE;
- }
- }
- //入栈的操作
- public function push($val){
- //先判断栈是否已经满了
- if($this->top==$this->maxSize-1){ //5-1=4 0 1 2 3 4
- echo'<br/>栈满,不能添加';
- return;
- }
- $this->top++; //先加再放
- $this->stack[$this->top]=$val; //就入栈了
- }
- //出栈的操作,就是把栈顶的值取出
- public function pop(){
- //判断是否栈空
- if($this->top==-1){
- echo'<br/>栈空';
- return;
- }
- //把栈顶的值,取出
- $topVal=$this->stack[$this->top];
- $this->top--;
- return $topVal;
- }
- //显示栈的所有数据的方法
- public function showStack(){
- if($this->top==-1){
- echo'<br/>栈空';
- return;
- }
- echo'<br/>当前栈的情况是...';
- for($i=$this->top;$i>-1;$i--){ //反着显示
- echo'<br/>stack['.$i.']='.$this->stack[$i]; //从栈顶开始显示
- }
- }
- }
- ?>
- </body>
- </html>
步骤:
1.界面搞定
2.思路->画图说明
3.处里多位数的运算
4.处里连续是减号的运算符
到此,这个计算器还缺少加上小括号的运算
6*8-(90-78)+60-45
更深一步:6*{8-[(90-78)+60]-45} 如此就相当的复杂了
思路:给 ( [ { 都设置不同的运算优先级 来处理不同的运算