二叉树的应用----表达式树

文章目录

\color{#0AAD1A}{何为表达式树}
\color{#0FAC1A}{表达式树实现}
\color{#0FAC1A}{表达式树优化}
\color{#0FAC1A}{Reference}


何为表达式树

1

上图就是一个表达式树(手动滑稽)
下面我会给出把中缀表达式(字符串)转化成表达式树的代码


表达式树实现

作为二叉树的一个应用,这里就用二叉树这种数据结构来实现~

这里有一个重要的知识点,在利用指针实现的二叉树中,叶节点(叶结点)与非叶节点是否使用相同的定义十分重要。使用相同的类可以简化实现,但是可能导致空间上的浪费。

有一些应用只需要叶结点存储数据,还有一些应用要求分支结点(非叶节点)与叶结点存储不同类型的数据。
代码(1)^2用同样的方式实现叶子节点和非叶节点
代码(2)^2用不同的方式实现叶子节点和非叶节点

(感觉(1)不好理解的话可以先看(2))

(1)

// Node implementation with simple inheritance
class VarBinNode { // Node  base class基类
public:
virtual ˜VarBinNode() {}
virtual bool isLeaf() = 0; // Subclasses must implement
};
class LeafNode : public VarBinNode { // Leaf node叶子结点
private:
Operand var; // Operand value
public:
LeafNode(const Operand& val) { var = val; } // Constructor
bool isLeaf() { return true; } // Version for LeafNode
Operand value() { return var; } // Return node value
};
class IntlNode : public VarBinNode { // Internal node非叶子结点
private:
VarBinNode* left; // Left child
VarBinNode* right; // Right child
Operator opx; // Operator value
public:
IntlNode(const Operator& op, VarBinNode* l, VarBinNode* r)
{ opx = op; left = l; right = r; } // Constructor

bool isLeaf() { return false; } // Version for IntlNode

VarBinNode* leftchild() { return left; } // Left child

VarBinNode* rightchild() { return right; } // Right child

Operator value() { return opx; } // Value

};
void traverse(VarBinNode *root) { // Preorder traversal
if (root == NULL) return; // Nothing to visit

if (root->isLeaf()) // Do leaf node
cout << "Leaf: " << ((LeafNode *)root)->value() << endl;

else { // Do internal node

cout << "Internal: "

<< ((IntlNode *)root)->value() << endl;

traverse(((IntlNode *)root)->leftchild());

traverse(((IntlNode *)root)->rightchild());

} }

(2)

#include<iostream>
using namespace std;
#define Operand char
#define Operator char
//Node implementation with the composite design pattern 复合设计模式
class VarBinNode {      //Node  base class  基类
public:
    virtual ~VarBinNode(){}
    virtual bool isLeaf() = 0;
    virtual void traverse() = 0;
};

class LeafNode :public VarBinNode {     //Leaf node 叶子结点
private:
    Operand var;        //Operand value 运算数

public:
    LeafNode(const Operand& val) { var = val; }
    bool isLeaf() { return true; }
    Operand value() { return var; } //Return node value
    void traverse() {   //叶子结点的遍历
        cout << "Leaf:" << value() << endl;
    }
};

class IntlNode :public VarBinNode { //Internal node 非叶子节点
private:
    VarBinNode* lc;     //Left child 左孩子
    VarBinNode* rc;     //Right child 右孩子
    Operator opx;       //Operator value 运算符号

public:
    IntlNode(const Operator& op, VarBinNode* l, VarBinNode* r) {
        opx = op; lc = l; rc = r;
    }
    bool isLeaf() { return false; }
    VarBinNode* left() { return lc; }   //Return left child左孩子
    VarBinNode* right() { return rc; }  //Return right child右孩子
    Operator value() { return opx; }        //Return operator value 运算符号

    void traverse() {       //非叶子结点的遍历
        cout << "Internal:" << value() << endl;
        if (left() != NULL)left()->traverse();
        if (right() != NULL)right()->traverse();
    }
};

//Do a preorder traversal先序遍历
void traverse(VarBinNode* root) {
    if (root != NULL)root->traverse();
}

之后再用上面任意一种完成表达式树的构建^3(这里用(2))

#include<iostream>
using namespace std;
#define Operand char
#define Operator char
//Node implementation with the composite design pattern 复合设计模式
class VarBinNode {      //Node base class  基类
public:
    virtual ~VarBinNode() {}
    virtual bool isLeaf() = 0;
    virtual void traverse() = 0;
};

class LeafNode :public VarBinNode {     //Leaf node 叶子结点
private:
    Operand var;        //Operand value 运算数

public:
    LeafNode(const Operand& val) { var = val; }
    bool isLeaf() { return true; }
    Operand value() { return var; } //Return node value
    void traverse() {   //叶子结点的遍历
        cout << "Leaf:" << value() << endl;
    }
};

class IntlNode :public VarBinNode { //Internal node 非叶子节点
private:
    VarBinNode* lc;     //Left child 左孩子
    VarBinNode* rc;     //Right child 右孩子
    Operator opx;       //Operator value 运算符号

public:
    IntlNode(const Operator& op, VarBinNode* l, VarBinNode* r) {
        opx = op;lc = l; rc = r;
    }
    bool isLeaf() { return false; }
    VarBinNode* left() { return lc; }   //Return left child左孩子
    VarBinNode* right() { return rc; }  //Return right child右孩子
    Operator value() { return opx; }        //Return operator value 运算符号

    void traverse() {       //非叶子结点的遍历
        cout << "Internal:" << value() << endl;
        if (left() != NULL)
        left()->traverse();
        if (right() != NULL)
        right()->traverse();
    }
};

//Do a preorder traversal先序遍历
void traverse(VarBinNode* root) {
    if (root != NULL)root->traverse();
}

class Expressiontree {      //表达式树
private:
    VarBinNode* Root;       //Tree root 树根
public:
    Expressiontree(Operand op) {        //Leaf constructor 叶子结点的构造函数
        Root = new LeafNode(op);
    }
    //Intlnode constructor  非叶子节点的构造函数
    Expressiontree(const Operator& op, Expressiontree* l, Expressiontree* r) {
        Root = new IntlNode(op, l->Root, r->Root);
    }
    Expressiontree(VarBinNode*& node) {
        Root = node;
    }
    void traversetree() {       //遍历表达式树
        traverse(Root);
    }
    VarBinNode* getRoot() {     //获取树根结点
        return Root;
    }
    ~Expressiontree(){}
};

Expressiontree* buildtree(string s, int l, int r) {     
    //在创建树的时候,如果孩子节点为空,让它指向emptynode节点
    if (r < l)
        return NULL;
    //上面这个判断条件没有必要,因为不会出现这种情况,除非错误输入,比如只输入了: "()"或者"2+"
    int c1 = -1, c2 = -1;
    int flag = 0;
    if (r == l) {           //叶子结点
        return new Expressiontree(s[r]);
    }
    for (int i = l; i <= r; ++i) {
        if (s[i] == '(')
            ++flag;
        else if (s[i] == ')')
            --flag;
        else if (s[i] == '+' || s[i] == '-') {
            if (flag == 0) {        //说明这里的加减号在括号外,可以拿来做根结点
                c1 = i;
            }
        }
        else if (s[i] == '*' || s[i] == '/') {
            if (flag == 0) {
                c2 = i;
            }
        }
    }
    if (c1 < 0)c1 = c2; //如果括号外面没有加减号,用乘除号代替
    if (c1 < 0)return buildtree(s, l + 1, r - 1);
    Expressiontree* left = buildtree(s, l, c1 - 1);
    Expressiontree* right = buildtree(s, c1 + 1, r);
    Expressiontree* op = new Expressiontree(s[c1], left, right);
    return op;
}

void deletetree(IntlNode*tree) {        //删除表达式树,释放空间
    if (tree->left() != NULL) {
        if (tree->left()->isLeaf() == false) {
            deletetree((IntlNode*)tree->left());
        }
        else
            delete tree->left();
    }
    if (tree->right() != NULL) {
        if (tree->right()->isLeaf() == false) {
            deletetree((IntlNode*)tree->right());
        }
        else
            delete tree->right();
    }
    delete tree;
}

int main() {
    string expression = "4*x*(2*x+a)-c";
    //构建表达式树
    Expressiontree* expressiontree = buildtree(expression, 0, expression.size() - 1);
    expressiontree->traversetree();     //遍历表达式树
    deletetree((IntlNode*)expressiontree->getRoot());       //释放空间
}

然后有两个概念需要提及:

  • 前缀表达式
    它将运算符写在前面,操作数写在后面。例如,- 1 + 2 3,它等价于1-(2+3)。

  • 后缀表达式
    和前面相反,操作数在前,运算符在后,
    例如,(2+3)*5,它等价于23+5 *

接着,还是这个图,

1
先序遍历(先根后左再右)就可以得到我们想要的前缀表达式

后序遍历(先左后右再根)就可以得到我们想要的后缀表达式

(2)代码 :先序遍历结果


2

后序遍历代码:(在(2)基础上加了一个traverse2()方法)

#include<iostream>
using namespace std;
#define Operand char
#define Operator char
//Node implementation with the composite design pattern 复合设计模式
class VarBinNode {      //Node base class  基类
public:
    virtual ~VarBinNode() {}
    virtual bool isLeaf() = 0;
    virtual void traverse() = 0;    //用来先序遍历
    virtual void traverse2() = 0;   //用来后序遍历
};

class LeafNode :public VarBinNode {     //Leaf node 叶子结点
private:
    Operand var;        //Operand value 运算数

public:
    LeafNode(const Operand& val) { var = val; }
    bool isLeaf() { return true; }
    Operand value() { return var; } //Return node value
    void traverse() {   //叶子结点的遍历
        cout << "Leaf:" << value() << endl;
    }
    void traverse2() {  //叶子结点的遍历
        cout << "Leaf:" << value() << endl;
    }
};

class IntlNode :public VarBinNode { //Internal node 非叶子节点
private:
    VarBinNode* lc;     //Left child 左孩子
    VarBinNode* rc;     //Right child 右孩子
    Operator opx;       //Operator value 运算符号

public:
    IntlNode(const Operator& op, VarBinNode* l, VarBinNode* r) {
        opx = op;lc = l; rc = r;
    }
    bool isLeaf() { return false; }
    VarBinNode* left() { return lc; }   //Return left child左孩子
    VarBinNode* right() { return rc; }  //Return right child右孩子
    Operator value() { return opx; }        //Return operator value 运算符号

    void traverse() {       //非叶子结点的遍历
        cout << "Internal:" << value() << endl;
        if (left() != NULL)
        left()->traverse();
        if (right() != NULL)
        right()->traverse();
    }
    void traverse2() {      //非叶子结点的遍历
        if (left() != NULL)left()->traverse2();
        if (right() != NULL)right()->traverse2();
        cout << "Internal:" << value() << endl;
    }
};

//Do a preorder traversal先序遍历
void traverse(VarBinNode* root) {
    if (root != NULL)root->traverse();
}
//Do a postorder traversal后序遍历
void traverse2(VarBinNode* root) {
    if (root != NULL)root->traverse2();
}

class Expressiontree {      //表达式树
private:
    VarBinNode* Root;       //Tree root 树根
public:
    Expressiontree(Operand op) {        //Leaf constructor 叶子结点的构造函数
        Root = new LeafNode(op);
    }
    //Intlnode constructor  非叶子节点的构造函数
    Expressiontree(const Operator& op, Expressiontree* l, Expressiontree* r) {
        Root = new IntlNode(op, l->Root, r->Root);
    }
    Expressiontree(VarBinNode*& node) {
        Root = node;
    }
    void traversetree() {       //遍历表达式树,先序
        traverse(Root);
    }
    void traversetree2() {      //遍历表达式树,后序
        traverse2(Root);
    }
    VarBinNode* getRoot() {     //获取树根结点
        return Root;
    }
    ~Expressiontree(){}
};

Expressiontree* buildtree(string s, int l, int r) {     
    //在创建树的时候,如果孩子节点为空,让它指向emptynode节点
    if (r < l)
        return NULL;
    //上面这个判断条件没有必要,因为不会出现这种情况,除非错误输入,比如只输入了: "()"或者"2+"
    int c1 = -1, c2 = -1;
    int flag = 0;
    if (r == l) {           //叶子结点
        return new Expressiontree(s[r]);
    }
    for (int i = l; i <= r; ++i) {
        if (s[i] == '(')
            ++flag;
        else if (s[i] == ')')
            --flag;
        else if (s[i] == '+' || s[i] == '-') {
            if (flag == 0) {        //说明这里的加减号在括号外,可以拿来做根结点
                c1 = i;
            }
        }
        else if (s[i] == '*' || s[i] == '/') {
            if (flag == 0) {
                c2 = i;
            }
        }
    }
    if (c1 < 0)c1 = c2; //如果括号外面没有加减号,用乘除号代替
    if (c1 < 0)return buildtree(s, l + 1, r - 1);
    Expressiontree* left = buildtree(s, l, c1 - 1);
    Expressiontree* right = buildtree(s, c1 + 1, r);
    Expressiontree* op = new Expressiontree(s[c1], left, right);
    return op;
}

void deletetree(IntlNode*tree) {        //删除表达式树,释放空间
    if (tree->left() != NULL) {
        if (tree->left()->isLeaf() == false) {
            deletetree((IntlNode*)tree->left());
        }
        else
            delete tree->left();
    }
    if (tree->right() != NULL) {
        if (tree->right()->isLeaf() == false) {
            deletetree((IntlNode*)tree->right());
        }
        else
            delete tree->right();
    }
    delete tree;
}

int main() {
    string expression = "4*x*(2*x+a)-c";
    //构建表达式树
    Expressiontree* expressiontree = buildtree(expression, 0, expression.size() - 1);
    expressiontree->traversetree2();        //遍历表达式树
    deletetree((IntlNode*)expressiontree->getRoot());       //释放空间
}

后序遍历结果:


3

---------------------------

表达式树优化

代码优化:
在非叶节点traverse()方法中,我们看到每次递归遍历前,都要判断左右子节点是否为空,当树的深度增加时,这造成的时间开销是呈指数增长的,因此我们最好把这两个判断条件给去掉。

(不过因为这里情况特殊,即我们的表达式树一定是一个满二叉树(二元运算符肯定对应了两个操作数),所以可以大胆地去掉判断条件,不过这样还是不好,如果我们的输入出现差错,就有可能导致程序崩溃,因为使用了空指针)

优化实现


Reference

^1《数据结构与算法分析(C++版)(第三版)》P103

^2《数据结构与算法分析(C++版)(第三版)》P104,P105

^3表达式树与前中后缀表达式

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容