紫书354页的题,将表达式树进行公共表达式消除,化为等价的图。因为需要判断某一个是否出现过,所以需要快速比较,采用哈希表的形式,将a~b与1~27一一对应,不采用0,因为0与0000是相同的,对于每一个树,都预先给予其一个编号,然后将其所表示的字符串化为27进制的数,然后递归建造其左右子树,如果发现其是出现过的字符串表达式,则取消其编号,返回编号,即dict[u]。建完树之后进行输出。具体细节见代码:

#include<cstdio>
#include<string>
#include<map>
using namespace std;

const int maxn = 60000;
int T, kase, cnt;
char expr[maxn*5], *p;
int done[maxn]; /// 该结点是否已输出

struct Node
{
    string s;
    int hash, left, right;
    bool operator < (const Node& rhs) const
    {
        if(hash != rhs.hash) return hash < rhs.hash;
        if(left != rhs.left) return left < rhs.left;
        return right < rhs.right;
    }
} node[maxn];

map<Node,int> dict;

int parse()
{
    int id = cnt++;///编号
    Node& u = node[id];///表示结点
    u.left = u.right = -1;
    u.s = "";///初始化字符串
    u.hash = 0;
    while(isalpha(*p)){
        u.hash = u.hash * 27 + *p - 'a' + 1;///用27进制数来表示字符串,并取消0,1~27与a~b一一对应
        u.s.push_back(*p);///节点所代表的字符串
        p++;///指针后移,检查下一个字符
    }
    if (*p == '('){
        p++;
        u.left = parse();
        p++;///因为递归返回时指针所指的应是')',所以要后移到下一个
        u.right = parse();
        p++;
    }
    if (dict.count(u) != 0){///如果出现过,则取消预设的编号,返回其编号
        id--;
        cnt--;
        return dict[u];
    }
    return dict[u] = id;
}

void print(int v)
{
    if(done[v] == kase)
        printf("%d", v + 1);
    else{
        done[v] = kase; /// 常见小技巧,可以避免memset(done, 0, sizeof(done))
        printf("%s", node[v].s.c_str());///c_str将string转换为C语言中的字符数组的形式
        if(node[v].left != -1){
            putchar('(');
            print(node[v].left);
            putchar(',');
            print(node[v].right);
            putchar(')');
        }
    }
}

int main()
{
    scanf("%d", &T);
    for(kase = 1; kase <= T; kase++){
        dict.clear();
        cnt = 0;
        scanf("%s", expr);
        p = expr;
        print(parse());
        putchar('\n');
    }
    return 0;
}