好了,又要开始写数学小论文了。开始,本来是想写一些高大上的数学定理,但是发现其实自己根本没有理解到这个定理的本质,讲也讲不清。
所以,我决定,从最开始的东西为基础来写本次的数学小论文,那就是加减乘除(当然,还有括号),在现实生活中,这些都是十分简单的东西,是一位学生,就肯定把这简单的四则运算牢记于心。还有很多同学,利用计算器,去研究或者推测一些数学定理。
但是,又有几个人知道,在这计算机的背后,人们是用什么程序来实现四则运算以及括号的匹配的呢?今天,我就来聊一聊计算机是如何实现这一切的。
接下来,我们要推广到有加减乘除四则运算以及有括号参与的计算了。
在这里介绍两种方法:
1.用栈来实现:
首先用一个栈来存储数字,用一个栈来存储符号。
栈是什么呢,栈就像一个火车站,火车进进出出,只有一个轨道。在栈底的元素如果想出来的话,那么就必须等他上面的元素都出来之后才能出来。火车也是这样,在里面的火车必须等在外面的火车都出去了之后才能出来。期间,可能还会又新的火车进站。
如果进来的是左括号,那么先放着不做处理,如果进来的是加号和减号,就直接做到小括号为止。如果是乘号和除号,就做到第一个级别比他小的地方或者左括号为止。如果进来的是右括号,那么做到第一个左括号为止,并且删去该左括号,直到做到结束。这样在存数字的栈中唯一剩余的那一个数就是答案。
如果还是有点懵逼的童鞋可以看程序理解一下:
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
using namespace std;
const int maxN=100100;
const int mod=10000;
string s;
long long op1[maxN];
char op2[maxN];
int num1,num2;
void handle()
{
switch(op2[num2]){
case '+':
op1[num1-1]=(op1[num1-1]+op1[num1])%mod;
num1--;
break;
case '-':
op1[num1-1]=(op1[num1-1]-op1[num1])%mod;
num1--;
break;
case '*':
op1[num1-1]=(op1[num1-1]*op1[num1])%mod;
num1--;
break;
case '/':
op1[num1-1]=(op1[num1-1]/op1[num1])%mod;
num1--;
break;
}
}
void work1()
{
while(num2>0&&op2[num2]!='('){
handle();
num2--;
}
}
void work2()
{
while(num2>0&&(op2[num2]=='*'||op2[num2]=='/')){
handle();
num2--;
}
}
int main()
{
//freopen("expr.in","r",stdin);
//freopen("expr.out","w",stdout);
cin>>s;
int len=s.length();
int i=0;
while(i<len){
if(s[i]<='9'&&s[i]>'0'){
long long temp=0;
while(i<len&&s[i]>='0'&&s[i]<='9'){
temp=(temp*10+s[i]-'0')%mod;
i++;
}
num1++;
op1[num1]=temp;
}
if(i==len)
break;
switch(s[i]){
case '+':
case '-':
work1();
op2[++num2]=s[i];
break;
case '*':
case '/':
work2();
op2[++num2]=s[i];
break;
case '(':
op2[++num2]=s[i];
break;
case ')':
work1();
num2--;
break;
}
++i;
cout<<num1<<endl;
for(int i=1;i<=num1;++i)
cout<<op1[i]<<" ";
cout<<endl;
cout<<num2<<endl;
for(int i=1;i<=num2;++i)
cout<<op2[i]<<" ";
cout<<endl<<endl;
}
work1();
cout<<op1[1]<<endl;
return 0;
}
2.递归
说实话,用栈来实现比用递归来实现要快多了,但是为了锻炼自己,还是要每一种方法都要写一下。
先讲一下思路吧:首先我们要找到最后一次运算的符号在哪里,并且从这分开,在他的左右两边去寻找各自区间的最后运算的符号在哪里,然后以此类推,直到这个区间所有的都是数字为止。
首先,我们要解决括号配对的问题。我们依然可以用栈的思想来实现,并且我们还要记录每一个点他外面所拥有的括号层数,以此来判断是否为最后一次运算的符号。说完思路,可能大家还是有点蒙蒙的,举一个栗子好了:
假如说我们要计算 (1+2)*3 的值
用栈将s[0]的左括号和 s[4] 的右括号匹配,那么s[1]到s[3]的外层括号数都为1
首先递归0到(len-1)寻找最后做的运算符号,发现是s[5]的乘号,所以再递归两边的最后的符号,即递归0到4,和递归6到6,将二者的值乘起来就是答案。
下面考虑6到6:由于是一个数字,所以结束递推,返回值s[6]
再来考虑0到4:由于s[0]和s[4]是一对匹配的左右括号,所以直接考虑1到3,1+2…这个就不必再往下了吧?
最后附上代码~~~
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxN=100100;
string s;
int len,ans,stack[maxN],stacknum=0,kuo[maxN],kuonum=0;
int peidui[maxN];
bool isDigit(int l,int r)
{
for(int i=l;i<=r;++i){
if(s[i]<'0'||s[i]>'9')
return false;
}
return true;
}
int dg(int l,int r)
{
if(isDigit(l,r)){
int t=s[l]-'0';
for(int i=l+1;i<=r;++i)
t=t*10+s[i]-'0';
return t;
}
if(peidui[l]==r){
for(int i=l+1;i<=r-1;++i)
kuo[i]--;
return dg(l+1,r-1);
}
char p='.';
int p1=-1;
for(int i=l+1;i<=r-1;++i){
switch(s[i]){
case '+':
case '-':
if(kuo[i]==0){
p=s[i];
p1=i;
}
break;
case '*':
case '/':
if(kuo[i]==0){
if(p1==-1){
p1=i;
p=s[i];
}
else{
if(p=='*'||p=='/'){
p1=i;
p=s[i];
}
}
}
break;
}
}
switch(p){
case '+':
return dg(l,p1-1)+dg(p1+1,r);
case '-':
return dg(l,p1-1)-dg(p1+1,r);
case '/':
return dg(l,p1-1)/dg(p1+1,r);
case '*':
return dg(l,p1-1)*dg(p1+1,r);
}
}
int main()
{
cin>>s;
len=s.size();
for(int i=0;i<len;++i){
if(s[i]=='('){
stack[++stacknum]=i;
kuonum++;
kuo[i]=-1;
continue;
}
if(s[i]==')'){
peidui[stack[stacknum]]=i;
peidui[i]=stack[stacknum];
stacknum--;
kuonum--;
continue;
}
kuo[i]=kuonum;
}
cout<<dg(0,len-1)<<endl;
return 0;
}
这只是简单的四则运算加上括号的匹配,还没有涉及开根,求方程,绝对值,分数等符号,其中,有一些的代码实现比四则运算简单,又有一些的实现难度是四则运算无法比拟的。
其实,计算机在四则运算的代码实现上就是一个思想,看拿一个符号是第一次做的,再考虑这个符号的左右的最后一个符号是哪个,就这么一直推下去,最终得到答案。。。