例题11-1 公共表达式消除(Common Subexpression Elimination, ACM/ICPC NWERC 2009, UVa12219)

欢迎访问我的Uva题解目录 https://blog.csdn.net/richenyunqi/article/details/81149109

题目描述

例题11-1 公共表达式消除(Common Subexpression Elimination, ACM/ICPC NWERC 2009, UVa12219)_第1张图片

题意解析

可以用表达式树来表示一个表达式。在本题中,运算符均为二元的,且运算符和运算数均用1~4个小写字母表示。
用消除公共表达式的方法可以减少表达式树上的结点,得到一个图。其中各个结点按照出现顺序编号为1,2,3,…,即编号k表示目前为止写下的第k个结点。

算法设计

参考《算法竞赛入门经典(第2版)》中的提示:

算法的第一步是构造表达式树。接下来应该怎么做呢?是否可以用两两比较的方法去掉重复?比较两棵树的时间复杂度为O(n)(因为要递归比较二者的所有后代),再加上二重循环枚举两棵子树,总时间复杂度高达O(n3),无法承受。此处不仅需要更快地比较两棵树,还需要更快地查找一棵树是否存在过。
借用第5章“集合栈计算机”的思路,用一个map把子树映射成编号1, 2,…。这样一来,子树就可以用根的名字(字符串)和左右子结点编号表示。用(a,0,0)表示根的名字为a,且左右子结点均为空(0表示不存在)的子树,即叶子a。可以看到,下面所有叶子a的编号都是4。再例如,(b,3,6)就是根的名字为b,左右两个子树的编号分别为3,6。可以看到,这样的子树编号均为5。这样,每次判断一棵子树是否出现过只需要在map中查找,总时间复杂度为O(nlogn)。

其中涉及到去掉重复的子树,一棵子树可以用根的名字和左右子节点编号唯一标识。为方便起见,可以将根的名字和左右子节点编号用一个字符串来表示,例如,可以用字符串(a,0,0)表示根的名字为a,且左右子结点均为空(0表示不存在)的子树;用字符串(b,3,6)标识根的名字为b,左右两个子树的编号分别为3,6的子树。然后用unordered_map将该子树映射到子树编号。
具体实现可见代码。

C++代码

#include
using namespace std;
struct Node{
    string data;
    int left,right;
    Node(string d="",int l=-1,int r=-1):data(d),left(l),right(r){}
};
string line;
Node tree[50005];//存储整棵树
int output[50005];//结点是否输出过
int createTree(int&index,int&num,unordered_map<string,int>&trans){//先序遍历建树
    int id=num++,i=index;//id表示当前子树的编号,num表示下一棵子树的编号
    while(index<line.size()&&isalpha(line[index]))//查找最近的非字母字符的位置
        ++index;
    tree[id]=Node(line.substr(i,index-i));//截取根结点的名字
    if(index<line.size()&&line[index]=='('){//如果接下来是(,表示有两个孩子
        tree[id].left=createTree(++index,num,trans);//递归遍历左子树
        tree[id].right=createTree(++index,num,trans);//递归遍历右子树
        ++index;
    }
    string s=tree[id].data+"("+to_string(tree[id].left)+","+to_string(tree[id].right)+")";//唯一标识一棵子树
    if(trans.find(s)==trans.end()){//该子树没有出现过
        trans.insert({s,id});//在map中插入该子树
    }else//子树出现过
        --num;//递减当前下一棵子树的编号
    return trans[s];
}
void DFS(int root,int x){//递归遍历输出
    if(output[root]==x){//该子树已经输出过
        printf("%d",root);//输出子树编号
        return;
    }
    output[root]=x;//置当前子树为已输出过
    printf("%s",(tree[root].data).c_str());
    if(tree[root].left==-1)
        return;
    printf("(");
    DFS(tree[root].left,x);
    printf(",");
    DFS(tree[root].right,x);
    printf(")");
}
int main() {
    int c;
    scanf("%d%*c",&c);
    for(int i=1;getline(cin,line);++i){
        unordered_map<string,int>trans;
        int index=0,num=1;
        DFS(createTree(index,num,trans),i);
        puts("");
    }
    return 0;
}

你可能感兴趣的:(算法竞赛入门经典,-,Uva)