题解 | #表达式求值#

表达式求值

http://www.nowcoder.com/practice/9566499a2e1546c0a257e885dfdbf30d

题意

给定一个表达式,计算表达式的值

限制:表达式长度不大于100,计算过程保证在int内,只包含 数字 加减乘除 和 括号,保证表达式正确

方法

递归同时计算

考虑表达式,其实是由数值 运算符 数值 运算符 数值 运算符构成的

其中第一个数值可能是负号开始,可以通过判断起始字符进行处理

所有数值 可能是括号表达式,可以通过判断前括号进行处理


当我们处理掉括号和负数以后。

我们可以用栈来完成表达式计算。

以样例数据为例,400+5

初始化为空

数值栈 符号栈
[] []

首先我们读入400

那么数值栈中压入

数值栈 符号栈
[400] []

然后读入加号

数值栈 符号栈
[400] [+]

读入5

数值栈 符号栈
[400,5] [+]

字符串读取完,处理栈的末尾

数值栈 符号栈
[400+5=405] []

输出数值栈的第一个

代码

#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for (int i=a;i<n;i++)
int getExp(char *s,int n,int &idx);

int getNumber(char * s, int n, int & idx){ // 获得数值
  if(s[idx] == '('){ // 是括号表达式
    return getExp(s,n,idx);
  }
  int v = 0;
  while(isdigit(s[idx])){ // 值
    v*=10;
    v+=s[idx]-'0';
    idx++;
  }
  return v;
}

void calc(vector<int> & v,vector<char> & op){ // 计算栈末尾
  int v1 = v.back();
  int o = op.back();
  v.pop_back();
  op.pop_back();
  // printf("%d %c %d",v0,o,v1);
  if(o == '-')v.back()-=v1;
  if(o == '+')v.back()+=v1;
  if(o == '*')v.back()*=v1;
  if(o == '/')v.back()/=v1;
  // printf("=%d\n",v[v.size()-1]);
}

int getExp(char *s,int n,int &idx){ // 获取表达式
  int firstch = s[idx]; // 首个字符
  vector<int>vals = {};
  vector<char> op = {};
  if(firstch == '-' || firstch == '('){ // 负号 或 左括号
    idx++;
  }
  // number op number op number op number
  int v = getNumber(s,n,idx); // 获得第一个数值
  vals.push_back(firstch =='-'?-v:v); // 负数处理
  // printf("PUSH:%d\n",vals[vals.size()-1]);
  while(idx < n){
    if(firstch == '(' && s[idx] == ')'){ // 匹配的右括号
      while(op.size()){ // 清理栈上内容
        calc(vals,op);
      }
      idx++;
      return vals.front();
    }
    char o = s[idx];
    if(o == '-' || o == '+'){ // 加减优先级低
      while(op.size()){
        calc(vals,op);
      }
    }else if(o == '*' || o == '/'){ // 优先级高
      while(op.size() && (op[op.size()-1] == '*' || op[op.size()-1] == '/')){
        calc(vals,op);
      }
    }
    op.push_back(o);
    // printf("PUSH:%c\n",op[op.size()-1]);
    idx++;
    vals.push_back(getNumber(s,n,idx));
    // printf("PUSH:%d\n",vals[vals.size()-1]);
  }
  while(op.size()){ // 清理栈上内容
    calc(vals,op);
  }
  return vals.front();
}


int main(){
  char s[110];
  scanf("%s",s);
  int i = 0;
  printf("%d\n",getExp(s,strlen(s),i));
  return 0;
}

复杂度分析

时间复杂度: 我们每个字符至多读取访问两遍,每个字符操作复杂度都是常数,所以总时间复杂度为O(n)O(n)

空间复杂度: 最坏情况把整个内存中都储存了表达式的所有内容,所以空间复杂度为O(n)O(n)

三步:token,ast,计算

上面过程中是解析和计算同步进行,对于想扩展时可能代码较难更改。

我们如果把整个分成三个步骤

  1. 字符串到一定不会分为两部分的token转换
  2. token序列到计算二叉树
  3. 计算二叉树的值

这样去实现,那么如果有扩展需求,会更容易更改

代码

#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for (int i=a;i<n;i++)

enum NODE_TYPE {NUMBER,EXP,NEG}; // 数值,双目表达式,负表达式

struct Node{
  NODE_TYPE nt;

  int value; // 数值

  char op; // 双目表达式的运算符
  vector<struct Node> ns;
};

enum TOKEN_TYPE {OP,NUM};

struct Token{
  TOKEN_TYPE tt;
  int value; // 值
  char op; // 符号
};

Token getNumberToken(char * s,int& idx,int n){ // 获取不含符号的数字
  int v = 0;
  while(idx < n && isdigit(s[idx])){
    v*=10;
    v+=s[idx] - '0';
    idx++;
  }
  return Token{NUM,v,0};
}

void zipNode(vector<Node> &ns, vector<char> &ops){ // 把栈的末尾合并成一个Exp Node
  Node n1 = ns.back();
  ns.pop_back();
  Node n0 = ns.back();
  ns.pop_back();
  char op = ops.back();
  ops.pop_back();
  ns.push_back(Node{EXP,0,op,{n0,n1}});
}

Node genAst(const vector<Token> & ts,int & i){ // 生成Ast
  bool lbrace = false;
  bool neg = false;
  if(ts[i].tt == OP && ts[i].op == '('){ // 左括号
    lbrace = true;
    i++;
  }
  if(ts[i].tt == OP && ts[i].op == '-'){ // 负号
    neg = true;
    i++;
  }
  vector<Node> ns = {};
  vector<char> op;

  // Node op Node op Node op Node 运算的结构
  while(i < int(ts.size())){
    if(ts[i].tt == NUM){ // 数值
      if(neg){
        ns.push_back(Node{NUMBER,-ts[i++].value,0,{}});
        neg = false; // 消耗了负号
      }else{
        ns.push_back(Node{NUMBER,ts[i++].value,0,{}});
      }
    }else if(ts[i].op == '('){ // 递归找括号 表达式
      if(neg){
        ns.push_back(Node{NEG,0,0,{genAst(ts,i)}});
        neg = false; // 消耗了负号
      }else{
        ns.push_back(genAst(ts,i));
      }
    }else if(ts[i].op == ')'){ // 右括号 返回整个表达式Node
      assert(lbrace);
      while(op.size()){
        zipNode(ns,op);
      }
      i++;
      return ns[0];
    }else if(ts[i].op == '+' || ts[i].op == '-'){ // 加减运算,前面栈上的优先级更高
      while(op.size()){
        zipNode(ns,op);
      }
      op.push_back(ts[i++].op);
    }else if(ts[i].op == '*' || ts[i].op == '/'){ // 乘除,注意优先级
      while(op.size() && (op.back() == '*' || op.back() == '/')){
        zipNode(ns,op);
      }
      op.push_back(ts[i++].op);
    }
  }
  while(op.size()){ // 处理结束
    zipNode(ns,op);
  }
  return ns[0];
}

int calcAst(const Node &node) { // 运算 二叉树
  if(node.nt == NEG){
    return -calcAst(node.ns[0]);
  }
  if(node.nt == NUMBER){
    return node.value;
  }
  if(node.nt == EXP){
    if(node.op == '+')return calcAst(node.ns[0])+calcAst(node.ns[1]);
    if(node.op == '-')return calcAst(node.ns[0])-calcAst(node.ns[1]);
    if(node.op == '*')return calcAst(node.ns[0])*calcAst(node.ns[1]);
    if(node.op == '/')return calcAst(node.ns[0])/calcAst(node.ns[1]);
  };
  assert(false);
  return 0;
}


int main(){
  char s[110];
  scanf("%s",s);
  int n = strlen(s);
  // string 2 tokens
  vector<Token> ts; // tokens
  int i = 0;
  while(i < n){
    if(isdigit(s[i])){
      ts.push_back(getNumberToken(s,i,n));
    }else{
      ts.push_back(Token{OP,0,s[i++]});
    }
  }
  // for(auto k :ts){
  //   printf("%d %d %c\n",k.tt,k.value,k.op);
  // }
  i = 0;
  // tokens to ast
  Node node = genAst(ts, i);
  // printAst(node);
  // calculate ast
  printf("%d\n",calcAst(node));
  return 0;
}

void printAst(const Node & node,int dep = 0){ // 方便调试 查看Ast
  if(node.nt == NUMBER){
    rep(i,0,dep){
      printf("  ");
    }
    printf("%d\n",node.value);
  }else if(node.nt == EXP){
    printAst(node.ns[0],dep+1);
    rep(i,0,dep){
      printf("  ");
    }
    printf("%c\n",node.op);
    printAst(node.ns[1],dep+1);
  }else if(node.nt == NEG){
    rep(i,0,dep){
      printf("  ");
    }
    printf("-\n");
    printAst(node.ns[0],dep+1);
  }
}

复杂度分析

时间复杂度: 整个字符串每个位置的访问次数是常数次,所以总时间复杂度为O(n)O(n)

空间复杂度: 建立的ast和读入的储存都是字符串的常数倍,所以空间复杂度为为O(n)O(n)

全部评论

相关推荐

最近又搬回宿舍了,在工位坐不住,写一写秋招起伏不断的心态变化,也算对自己心态的一些思考表演式学习从开始为实习准备的时候就特别焦虑,楼主一开始选择的是cpp后端,但是24届这个方向已经炸了,同时自己又因为本科非92且非科班,所以感到机会更加迷茫。在某天晚上用java写出hello&nbsp;world并失眠一整晚后选择老本行干嵌入式。理想是美好的,现实情况是每天忙但又没有实质性进展,总是在配环境,调工具,顺带还要推科研。而这时候才发现自己一直在表演式学习,徘徊在设想如何展开工作的循环里,导致没有实质性进展。现在看来当时如果把精力专注在动手写而不是两只手端着看教程,基本功或许不会那么差。实习的焦虑5月,楼主...
耶比:哲学上有一个问题,玛丽的房间:玛丽知道眼睛识别色彩的原理知道各种颜色,但是她生活在黑白的房间里,直到有一天玛丽的房门打开了她亲眼看到了颜色,才知道什么是色彩。我现在最大可能的减少对非工作事情的思考,如果有一件事困扰了我, 能解决的我就直接做(去哪里或者和谁吵架等等……),解决不了的我就不想了,每一天都是最年轻的一天,珍惜今天吧
投递比亚迪等公司10个岗位 > 秋招被确诊为…… 牛客创作赏金赛
点赞 评论 收藏
分享
评论
点赞
收藏
分享
牛客网
牛客企业服务