一个计算简单数学表达式值的算法。

Posted on 2008-03-26 20:00  Samson小天  阅读(1781)  评论(0编辑  收藏  举报

    昨天帮“妮无可代替”MM解决了点computer problems,突然发现很久没有写部落格了。于是还答应为她写一篇介绍超级兔子的文章(这个还是放在这个周末吧,^-^)。Samson注:结果到现在还没完成这篇文章,=_=||
    今天做了数据结构的第一节实验课,要求是将中缀式转为后缀式(输入的是类似于a+b-c/d的格式),然后自己输入各个字幕对应的值,最后求解。我发现这个太麻烦了。我是个很懒的人,就连打开个程序我也希望一个按键到位(所以写了个MoreEffectiveKey,让Alt+1~9都可以打开对应的程序)。
    今天讲的是如果让输入的数字中缀式自动替换为字母中缀式并自动创建字母映射,然后将字母中缀式转换成字母后缀式最后计算值。
    程序使用VS2005,纯C++编写,非C++.NET。
    共三个文件,Main.cpp(主函数文件),CalConvert.h(方法声明),CalConvert.cpp(方法实现)

 1//中缀式到后缀式的转换头文件
 2#pragma once
 3#include <string>
 4using std::string;
 5
 6//中缀式转后缀式,返回后缀式
 7string MidToPos(string mid_e);
 8
 9//将前缀式中的数字替换成字母,返回字母对应的数字,a为数组中的第一个,b是第二个
10string ReplaceTextToChar(string p_textMid,double* p_cnMap,int* cnMap_nLen);
11
12//将映射表内容输出
13void displayMap(double* p_dmap,int map_nLen);
14
15//pos为后缀式,如果计算无错则将结果返回给p_dResult
16bool CalculateTheValue(string Pos_e,double* p_dResult,double* p_dMap);

    这是CalConvert.h。接下来是CalConvert.cpp
//中缀式-》后缀式的具体实现

#include 
<iostream> //displayMap needs it
#include <string>
using std::string;
using std::cout;
using std::endl;
#define AscII2Num(AscII) AscII-48    //由'1'->1
#define Num2Char(Num) Num+97  //由0->a '97'
#define Char2Num(charVal) charVal-97 // 由'0'->0  97-97=0

int icp(char c)
{
    
switch (c)
    
{
    
case '^':
        
return 4;
        
break;
    
case '*':
    
case '/':
        
return 2;
    
case '+':
    
case '-':
        
return 1;
    }

}

int isp(char c)
{
    
switch (c)
    
{
    
case '^':
        
return 3;
    
case '*':
    
case '/':
        
return 2;
    
case '+':
    
case '-':
        
return 1;
    
case '(':
        
return 0;
    
case '$':
        
return -1;
    }

}

void displayMap(double* p_dmap,int map_nLen)
{
    cout
<<"当前映射表内容为:"<<endl;
    
for (int i=0;i<map_nLen;i++)
        cout
<<(char)(Num2Char(i))<<" 被映射为: "<<p_dmap[i]<<endl;
}

string MidToPos(string mid_e)
{
    
string pos_e=mid_e;
    
char stack[20],c;
    
int top=0,i=0,j=0;
    stack[
0]='$';
    c
=mid_e[0];
    
while (c!='\0')
    
{
        
if (islower(c))
        
{
            pos_e[j
++]=c;
        }

        
else
            
switch(c)
            
{
            
case '+':
            
case '-':
            
case '*':
            
case '/':
            
case '^':
                
while (icp(c)<=isp(stack[top]))
                    pos_e[j
++]=stack[top--];
                stack[
++top]=c;
                
break;
            
case '(':
                stack[
++top]=c;
                
break;
            
case ')':
                
while (stack[top]!='(')
                    pos_e[j
++]=stack[top--];
                top
--;
                
break;
//            default:
//                break;
            }

        c
=mid_e[++i];
    }

    
while (top>0)
    
{
        pos_e[j
++]=stack[top--];
    }

    pos_e[j]
='\0';
    
if (strlen(&pos_e[0])<strlen(&mid_e[0]))
    
{
        
return pos_e.substr(0,j);
    }

    
return pos_e;
}

string ReplaceTextToChar(string p_textMid,double* p_cnMap,int* cnMap_nLen)
{
    
//p_cnMap映射原理,将数组的下标[0]位映射为a,[1]位映射为b,Ascii差48(见宏)
    string cMid=p_textMid;
    
int cMid_nPosition=0;
    
int textMid_nLen=strlen(&p_textMid[0]);
    
int HiNum=0,LoNum=0,HiNumCount=1,LoNumCount=1,j=0;
    
double numTemp=0.0;//合成最后的浮点数
    bool ExistInMap=false,MeetOperation=false;
    
bool MeetRBrackets=false,Negative=false,SwitchLow=false;
    
//ExistInMap表示在映射表中存在此数,MeetOperation表示遇到运算符
    
//MeetRBrackets表示遇到有括弧,Negative表示此数为负数,SwitchLow表示遇到小数点
    char OperationName;
    
for (int i=0;i<textMid_nLen;i++)
    
{
        
if ((char)p_textMid[i]=='+'||(char)p_textMid[i]=='-'||(char)p_textMid[i]=='*'||(char)p_textMid[i]=='/'||(char)p_textMid[i]=='^'||(char)p_textMid[i]=='('||(char)p_textMid[i]==')')
        
{
            
//遇到+-*/先将原来的数字输出,弹出后加入到映射表,然后将符号输出
            
//遇到左右括号直接输出括号2+3*(4-2),括弧旁边一定是符号,不需要弹数字
            switch (p_textMid[i])
            
{
            
case '('//左边一定是运算符
                
//此处添加直接输出代码
                cMid[cMid_nPosition++]='(';
                
break;
            
case '-':
                
if (i==0||(char)p_textMid[i-1]=='+'||(char)p_textMid[i-1]=='-'||(char)p_textMid[i-1]=='*'||(char)p_textMid[i-1]=='/'||(char)p_textMid[i-1]=='^'||(char)p_textMid[i-1]=='(')
                
{//如果 第一个就是'-'或前面是运算符或左括弧,则此'-'仅表示此数为负数。
                    Negative=true;
                    
continue;
                }

            
case '+':
            
case '*':
            
case '/':
            
case '^':
                MeetOperation
=true;
                OperationName
=p_textMid[i];
            
case ')':
                
//先弹数字,再加')'
                if (p_textMid[i]==')')
                    MeetRBrackets
=true;
            
default:
                
//不是+-*/()
                
//此处添加先弹数字再弹操作符代码,记得判断映射表
                if (MeetOperation)//如果弹过')'则没有数字弹了,只要直接输出操作符即可
                {
                    
if (cMid_nPosition!=0&&cMid[cMid_nPosition-1]==')')
                    
{
                        cMid[cMid_nPosition
++]=OperationName;
                        MeetOperation
=false;
                        
continue;
                    }

                }

                
//把高位低位合成浮点数
                numTemp=HiNum+LoNum/1.0/LoNumCount;
                
//判断是否为负数
                if (Negative)
                
{
                    numTemp
=0.0-numTemp;
                    Negative
=false;
                }


                
//判断此数是否在映射表中
                for (j=0;j<*cnMap_nLen;j++)
                    
if(p_cnMap[j]==numTemp) //p_cnMap指向double型映射数组
                    {
                        ExistInMap
=true;
                        
break;
                    }

                
if (ExistInMap)//已经存在映射就直接将此数字的映射加入即可
                {
                    cMid[cMid_nPosition
++]=Num2Char(j); 
                    ExistInMap
=false;
                }

                
else
                
{
                    
//不存在映射,添加映射,并加入c_Mid
                    p_cnMap[(*cnMap_nLen)++]=numTemp;
                    cMid[cMid_nPosition
++]=Num2Char(j);
                }

                
if (MeetOperation)
                
{
                    cMid[cMid_nPosition
++]=OperationName;
                    MeetOperation
=false;
                }

                
if (MeetRBrackets)
                
{
                    cMid[cMid_nPosition
++]=')';
                    MeetRBrackets
=false;
                }

                
//计数归位
                HiNum=LoNum=0;
                HiNumCount
=LoNumCount=1;
                numTemp
=0.0;
                SwitchLow
=false;
                
break;
            }

        }
 
        
else //进入此处说明当前字符是数字,或 '.'
        {
            
switch (p_textMid[i])
            
{
            
case '.':
                SwitchLow
=true;
                
break;
            
default:
                
if (SwitchLow)
                
{
                    LoNum
=LoNum*10+AscII2Num(p_textMid[i]);
                    LoNumCount
*=10;
                }
 
                
else
                
{
                    HiNum
=HiNum*10+AscII2Num(p_textMid[i]);
                    HiNumCount
*=10;
                }

                
break;
            }

        }

    }

    
//整个式子循环完毕后若最后一个不是')'就把最后一个数字弹出,是')',则上面数字和括弧都输出了
    if(cMid[cMid_nPosition]!=')'//因为最后一个是)时最后一个数字已经弹出了
    {//操作符不能结尾,所以不用考虑

        
//把高位低位合成浮点数
        numTemp=HiNum+LoNum/1.0/LoNumCount;
        
for (j=0;j<*cnMap_nLen;j++)
            
if(p_cnMap[j]==numTemp) //p_cnMap指向int型映射数组
            {
                ExistInMap
=true;
                
break;
            }

        
if (ExistInMap)//已经存在映射就直接将此数字的映射加入即可
        {
            cMid[cMid_nPosition
++]=Num2Char(j); 
            ExistInMap
=false;
        }

        
else
        
{
            
//不存在映射,添加映射,并加入c_Mid
            p_cnMap[(*cnMap_nLen)++]=numTemp;
            cMid[cMid_nPosition
++]=Num2Char(j);
        }

    }

    
//整个转换完成后在下一位加入'\0',但是由于string是静态定义的,长度并没有变
    
//我们用strlen可以检测到第一个'\0'的长度,然后用substr方法截断
    if (cMid_nPosition<textMid_nLen)
        
return cMid.substr(0,cMid_nPosition);//如果长度短了就截断,正好就不用动了
    return cMid;
}

bool CalculateTheValue(string Pos_e,double* p_dResult,double* p_dMap)
{
    
double stack[27];//压着数字的栈,字母最多26个,此处定27个,防止有运算符意外进栈
    int i=0,k=0,top=-1,pos_nLen=strlen(&Pos_e[0]); //都是计数器
    double x=0,y=0,z=0,j=0;
    
//X为后数,Y为前数,即Y+X,Y*X;Z为乘方运算的结果
    
//J为X的正副本,最后根据X的值对Z求倒数,Z=Y^J
    char c=Pos_e[0];
    
while (i++<pos_nLen)
    
{
        
if (islower(c))
        
{//如果是小写字母则转换成数字后压入栈中
            stack[++top]=p_dMap[Char2Num(c)];
        }

        
else
        
{
            x
=stack[top--];
            
switch (c)
            
{
            
case '+':
                stack[top]
+=x;
                
break;
            
case '-':
                stack[top]
-=x;
                
break;
            
case '*':
                stack[top]
*=x;
                
break;
            
case '/':
                stack[top]
/=x;
                
break;
            
case '^':
                y
=stack[top];
                
if (y==0)
                    
return false//底数不能为0
                if (x==0)
                    stack[top]
=1;
                
else
                
{
                    
if (x>0)
                        j
=x;
                    
else j=-x; //将X化为正数
                    for (z=1,k=1;k<=j;k++
                        z
*=y;//第一次Z=Y,第二次Z=Y^2,以此类推
                    if (x<0)//计算的是y^x
                        z=1.0000000000000/z;
                    stack[top]
=z;
                }

                
break;
            
default:
                
return false;
            }

        }

        c
=Pos_e[i];
    }

    
if (top>0)
        
return false;
    
*p_dResult=stack[top];
    
return true;
}
    这里要说明下,isp(),icp(),MidToPos(),CalculateTheValue()都是摘自《数据结构教程》(蔡子经,施伯乐著)。ReplaceTextToChar()和Display()方法都是自己写的。期间也遇到不少问题。此书的两位作者在写CalculateTheValue()方法时并未考虑2^1.2次方的问题,所以此处只能计算整数次方。其实可以调用math.h方法中的pow函数解决。这我在作业中修改了。改天有空我会贴上来的。
    最后是Main.cpp,也就是测试用的主函数。
 1//Main.cpp
 2#include <iostream>
 3#include <string>
 4#include "CalConvert.h"
 5
 6using std::cin;
 7using std::cout;
 8using std::endl;
 9using std::string;
10
11
12int main()
13{
14    string nMid; //中缀式
15    cout<<"Please Enter the Mid Expression:"<<endl;
16//    cin>>nMid;
17    nMid="3+4*5/6+2^3^2+4";
18    cout<<nMid<<endl;
19    cout<<"------------"<<endl;
20    double cnMap[26];
21    int Map_nLen=0;
22    string cMid=ReplaceTextToChar(nMid,&cnMap[0],&Map_nLen);
23    cout<<"The expression after number Convert to char is:"<<endl;
24    cout<<cMid<<endl;
25    cout<<"--------------\nThe double map is:"<<endl;
26    displayMap(&cnMap[0],Map_nLen);
27    cout<<"The Mid-expression after converted to Pos-expression is:"<<endl;
28    string cPos=MidToPos(cMid);
29    cout<<cPos<<endl<<"---------------"<<endl;
30    cout<<"The final Result is:"<<endl;
31    double d_result=0.0;
32    CalculateTheValue(cPos,&d_result,&cnMap[0]);
33    cout.precision(10);
34    cout<<d_result;
35    getchar();
36    getchar();
37}

    补充修改:
    如果include <math.h>,那么可以实现更复杂的乘方运算,当前的程序只支持整数乘方,如果用微软的库,那么可以小数,负数和整数都行。只要修改乘方运算的那部分代码为:
    y=stack[top];
    stack[top]=pow(y,x);
    如果产生异常,会由库函数抛出,自己就不用操心了~
    这写方法可以改造到MFC,C#,和纯C语言。这里需要注意为了效率C#中最好使用StringBuilder来处理经常改变的string值。如果是要改造到C语言,我也有一份源代码(老师作业要求用C语言写),需要提醒大家的是C语言米有stirng类型,也不能返回一个char数组,所以大家就顶一个超级大的char[]数字来代替string吧。

Copyright © 2024 Samson小天
Powered by .NET 9.0 on Kubernetes