112 - Tree Summing

题意:
判断读入的 LISP S-expressions 表达式是否存在一条从根到叶子节的路径, 使这条路径上所有节点的和为 I.
1. I 与表达式 T 是成对出现的, 但是 T 有可能是跨行的.
2. 所有节点的出现形式都为 (integer()()), 如果有左/右子树, 则在 integer 的左/右括号里.

思路:
1. 由于表达式是跨行的, 所以需要先把表达式完整的读入, 使用 getline(cin, temp, ')'); 进行读入, 即读到 ) 就停止, 然后判断到这里为止是否左右括号完全匹配, 若已匹配完毕, 则说明表达式读入完毕.

2. 根据表达式建树.由题意(2)可知, 每个节点都包含一个整数及由两个括号包裹起来的左/右子树. 所以建树过程可以递归如下:
(1). 先获取读取到的第一个整数, 做为节点的值(注意有可能有负数存在)
(2). 由整数往后, 找到第一个使括号完全匹配的字符串, 作为左子树; 第二个使括号完全匹配的字符串, 作为右子树.
(3). 递归创建左子树及右子树.若括号匹配到的字符串为空, 说明左/右子树为空.
(4). 根据左/右子树创建当前节点.

3. 判断是否存路径满路和为 I, 同样使用递归:
(1). 判断当前节点是否为叶子节点, 且值与 I 相等, 若相等, 则返回 true.
(2). 若不等, 则在其左/右子树中递归查找.
(3). 只要左右子树有一边能查到, 就为 true.

要点:
1. 这里的 tree 的 Node 的成员变量都是直接 public 的, 虽然违反面向对象设计原则, 但是对于一个算法题, 还要弄一堆 get/set 就太过麻烦了;
2. memset(tmp, '\0', 10);
3. string substr() 函数不填第二个参数, 表示取到字符串最后.

题目:
http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=104&page=show_problem&problem=48

代码:

# include <iostream>
# include <string>
# include <cstdio>
# include <cstring>
# include <vector>
# include <algorithm>
# include <cctype>
# include <iterator>
# include <assert.h>
# include <stack>
using namespace std;

// 所有的输入都是有效的, 但是有可能会存在于多行中, 每读入完一个 int, 
// 就按 ) 分隔的 getline 进行读入, 直到 ( 与 ) 完全匹配, 表示一个表达式读入完毕

class Node {
public:
  Node(int n, Node* left, Node* right):
       n_(n), left_(left), right_(right){}

  int n_;
  Node* left_;
  Node* right_;
};


// 从 pos 开始,找到第一个合法的 express 及其所在下标, 即左右括号必须匹配
pair<string, int> getValidExpression(const string&  expression, int startPos) {
  int cntLeft = 0;
  int cntRight = 0;

  int endPos = startPos;

  for (int i=startPos; i<expression.size(); i++) {
    if (expression[i] == '(') {
      ++cntLeft;
    } else if (expression[i] == ')') {
      ++cntRight;

      if (cntLeft == cntRight) {
        endPos = i + 1;
        break;
      }
    }
  }

  return make_pair(expression.substr(startPos, endPos - startPos),
                   endPos);
}

// 获取节点的数字,即找到的第一个整数,返回整数及其最后位所在的下标
// 如果找不到数字, 说明为空, 返回 <-1, -1>
// 这里真正有效的是 second 中的 -1
pair<int, int> getNodeNumber(const string& expression) {
  char tmp[10];
  memset(tmp, '\0', 10);

  int j = 0;
  int pos = 0;
  bool begin = false;
  for (int i=0; i<expression.size(); i++) {
    // digit 有可能是负数
    if (isdigit(expression[i]) || expression[i] == '-') {
      tmp[j++] = expression[i];
      begin = true;
    } else {
      if (begin) {
        pos = i;
        break;
      }
    }
  }

  // 如果没有开始,说明没找到数字, 真正有效的是 second 中的 -1
  if (!begin) return make_pair(-1, -1);

  return make_pair(atoi(tmp), pos);
}

// 根据读入的 expression 创建树, 返回根节点
// expression 中可能包含空格与回车换行符
Node* buildTree(const string& expression) {
  pair<int, int> number = getNodeNumber(expression);

  if (number.second == -1) return NULL;
  // 不能拿 first 来判断,不然 -1 (-1()()) 的 first 正好是 -1
  //  if (number.first == -1) return NULL;  

  pair<string, int> left = getValidExpression(expression, number.second);
  pair<string, int> right = getValidExpression(expression, left.second);

  Node* leftTree = buildTree(left.first);
  Node* rightTree = buildTree(right.first);

  return new Node(number.first, leftTree, rightTree);
}

// 判断 tree 是否存在一条路径的和为 sum
// 往左右子树查找, 只要有一边找到, 就为真
bool isPathExist(const Node* root, int sum) {
  if (root == NULL) return false;

  if (root->left_ == NULL && 
      root->right_ == NULL && 
      root->n_ == sum) return true;

  // bool 值要先初始化
  bool left = false;
  bool right = false;
  if (root->left_ != NULL) {
    left = isPathExist(root->left_, sum - root->n_);
  }
  if (root->right_ != NULL) {
    right = isPathExist(root->right_, sum - root->n_);
  }

  return (left || right);
}


// 判断一个表达式是否为合法, 即其左右括号必须匹配
bool isValid(const string& expression) {
  if (expression.empty()) return false;

  int cntLeft = 0;
  int cntRight = 0;

  for (int i=0; i<expression.size(); i++) {
    if (expression[i] == '(') {
      ++cntLeft;
    } else if (expression[i] == ')') {
      ++cntRight;
    }
  }

  return (cntLeft == cntRight);
}

// 释放树空间
void freeTree(Node* root) {
  if (root == NULL) return;

  freeTree(root->left_);
  freeTree(root->right_);
  delete root;
}

int main(int argc, char const *argv[])
{
  #ifndef ONLINE_JUDGE
    freopen("112_i.txt", "r", stdin);  
    freopen("uva_o.txt", "w", stdout); 
  #endif
  
  int sum;
  while (!cin.eof()) {
    cin >> sum;
    string expression;  // 存放一个合法的表达式

    while (!cin.eof()) {
      string temp;
      getline(cin, temp, ')');
      expression = expression + temp + ")";
      
      if (isValid(expression)) break;
    }

    if (isValid(expression)) {
      Node* root = buildTree(expression);
      if (isPathExist(root, sum)) {
        cout << "yes" << endl;
      } else {
        cout << "no" << endl;
      }
      freeTree(root);
    } 
  }

  return 0;
}

环境: C++ 4.5.3 - GNU C++ Compiler with options: -lm -lcrypt -O2 -pipe -DONLINE_JUDGE

参考: 比较简明的解法, 不用建树 http://blog.sina.com.cn/s/blog_64018c250100qjrt.html

转载于:https://my.oschina.net/zenglingfan/blog/151793

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值