表達式·表達式樹·表達式求值

来源:https://www.cnblogs.com/levarz/archive/2020/04/29/12804845.html
-Advertisement-
Play Games

描述 眾所周知,任何一個表達式,都可以用一棵表達式樹來表示。例如,表達式a+b c,可以表示為如下的表達式樹: + / \\ a \ / \\ b c 現在,給你一個中綴表達式,這個中綴表達式用變數來表示(不含數字),請你將這個中綴表達式用表達式二叉樹的形式輸出出來。 輸入 輸入分為三個部分。 第一 ...


描述

眾所周知,任何一個表達式,都可以用一棵表達式樹來表示。例如,表達式a+b*c,可以表示為如下的表達式樹:
   +
  /  \
 a    *
     /  \
     b  c
現在,給你一個中綴表達式,這個中綴表達式用變數來表示(不含數字),請你將這個中綴表達式用表達式二叉樹的形式輸出出來。

輸入

輸入分為三個部分。
第一部分為一行,即中綴表達式(長度不大於50)。中綴表達式可能含有小寫字母代表變數(a-z),也可能含有運算符(+、-、*、/、小括弧),不含有數字,也不含有空格。
第二部分為一個整數n(n < 10),表示中綴表達式的變數數。
第三部分有n行,每行格式為C x,C為變數的字元,x為該變數的值。

輸出

輸出分為三個部分,第一個部分為該表達式的逆波蘭式,即該表達式樹的後根遍歷結果。占一行。
第二部分為表達式樹的顯示,如樣例輸出所示。如果該二叉樹是一棵滿二叉樹,則最底部的葉子結點,分別占據橫坐標的第1、3、5、7……個位置(最左邊的坐標是1),然後它們的父結點的橫坐標,在兩個子結點的中間。如果不是滿二叉樹,則沒有結點的地方,用空格填充(但請略去所有的行末空格)。每一行父結點與子結點中隔開一行,用斜杠(/)與反斜杠(\)來表示樹的關係。/出現的橫坐標位置為父結點的橫坐標偏左一格,\出現的橫坐標位置為父結點的橫坐標偏右一格。也就是說,如果樹高為m,則輸出就有2m-1行。
第三部分為一個整數,表示將值代入變數之後,該中綴表達式的值。需要註意的一點是,除法代表整除運算,即捨棄小數點後的部分。同時,測試數據保證不會出現除以0的現象。

樣例輸入

a+b*c
3
a 2
b 7
c 5

樣例輸出

abc*+
   +
  /  \
 a    *
     /  \
     b  c
37


中綴表達式生成二叉樹

  • 考慮沒有括弧的情況
    對於一個中綴表達式:\(a+b\),由運算符分為左右兩個部分。其二叉樹表示形式自然為:
graph TB root((+))---left((a)) root((+))---right((b))

所以,構建一個表達式樹,關鍵在於找到表達式的根結點,然後分左右兩個部分構建樹;拓展到多個同級運算符的表達式:\(a+b+c+...+n\),可以這樣構建其表達式樹:

  1. 找到最先運算的根結點,若以最右邊的一個+為根結點
  2. 以根結點分為左右兩個部分
  3. 構建根結點,左邊構建樹,右邊構建樹
  4. 若左邊部分又是一個表達式,執行1、2、3步驟
  5. 若右邊部分又是一個表達式,執行1、2、3步驟

這樣就可以構建出整個表達式樹:

graph TB root((+))---left[a+b+...+n-1] root((+))---right((n)) left-.->|左邊部分又是一個表達式|root_l subgraph root_l((+))---left_l[a+b+...+n-2] root_l((+))---right_l((n-1)) end left_l-.->|n-1次重覆後|root_n subgraph root_n((+))---left_n((a)) root_n((+))---right_n((b)) end
  • 考慮有不同優先順序運算符的情況:
    \(a+b*c\),因為+是最後運算,所以它一定是樹的根,*先運算,是+號分得的右邊部分的根,這樣得到其表達式樹:
graph TB root((+))---left((a)) root((+))---right((*)) right((*))---left_((b)) right((*))---right_((c))

所以,含有優先順序不同的表達式,關鍵在於找到表達式最後運算的運算符,作為某個表達式樹的根,然後分左右兩個部分構建樹;同樣,同級運算符以最左邊的運算符為最後運算的運算符,統一第一種情況:拓展到多個不同運算符出現的情況:\(a+b*c p_1...p_k n(p_i為第i個運算符)\)

  1. 找到最後運算的運算符作為根結點
  2. 以根結點分為左右兩個部分
  3. 構建根結點,左邊構建樹,右邊構建樹
  4. 若左邊部分又是一個表達式,執行1、2、3步驟
  5. 若右邊部分又是一個表達式,執行1、2、3步驟

這樣就可以構建出整個表達式樹:

graph TB root((+))---left((a)) root((+))---right[b*c p_1...p_k n] right-.->|右邊邊部分又是一個表達式|root_r subgraph 若p_i是最後運算的運算符 root_r((p_i))---left_r[b*c p_1...p_i-1 n-k+i-1] root_r((p_i))---right_r[n-k+i p_i+1...p_k n] end
  • 考慮存在括弧的情況
    如果表達式中存在括弧,最後運算的運算符一定在括弧外,這樣,需要一個變數記錄括弧位置,在括弧外找到最後運算的運算符,然後分為左右兩個部分,重覆找括弧,建樹操作就可以了,以\(a*b+(c+d)\)為例:
  1. 找到最後運算的+號分左右兩個部分:\(a*b\)\((c+d)\)
  2. 左邊部分建樹
  3. 右邊部分建樹:
    • 右邊部分括弧外找不到最後運算的運算符,說明整個表達式被括弧括起來
    • 去掉括弧:c+d 建樹
  4. 若左邊部分又是一個表達式,執行1、2、3步驟
  5. 若右邊部分又是一個表達式,執行1、2、3步驟

所以,構建表達式樹的函數:CreateExpressionTree(char *expression, int start, int end, Bitree &tree)中,傳入了表達式的開始下標和結尾下標,方便分割左邊和右邊部分:
CreateExpressionTree(char *expression, int start, int end, Bitree &tree)函數:

void CreateExpressionTree(char *expression, int start, int end, Bitree &tree)
{
    /** 
      * 這裡,c1用來記錄括弧外最後運算的+或-
      * c2用來記錄括弧外最後運算的+或-
      * p記錄括弧當讀到一個( p++, ) 時 p--, 只有p==0時才說明c1, c2 
      * 括弧外 
      */
    int i, c1 = -1, c2 = -1, p = 0; 
    if (end - start == 1) {
        //只有一個結點,直接建立結點
        tree = new BTNode;
        tree->data = *(expression+start);
        tree->left_child = tree->right_child = NULL;
        return;
    }
    for (i = start; i < end; i++) {
        switch (*(expression+i))
        {
        case '(': p++; break;
        case ')': p--; break;
        case '+': case '-': if (!p) c1 = i; break;
        case '*': case '/': if (!p) c2 = i; break;
        }
    }
    //c1 < 0,說明括弧外沒有第一優先順序的+或者-,那麼就只能考慮*或者/
    if (c1 < 0) c1 = c2;     
    // 說明括弧外沒有第一優先順序的*或者/,說明此時表達式被括弧括起來, 去掉括弧後建樹
    if (c1 < 0) CreateExpressionTree(expression,start+1,end-1,tree);
    else {
        //建立根
        CreateExpressionTree(expression,c1,c1 + 1,tree);  
        //建立左樹  
        CreateExpressionTree(expression,start,c1,tree->left_child);
        //建立右樹
        CreateExpressionTree(expression,c1+1,end,tree->right_child);
    }
}
對於建樹這一部分,參考了表達式樹與前中尾碼表達式,詳細訪問鏈接

列印表達式樹

如題:

如果該二叉樹是一棵滿二叉樹,則最底部的葉子結點,分別占據橫坐標的第1、3、5、7……個位置(最左邊的坐標是1),然後它們的父結點的橫坐標,在兩個子結點的中間。如果不是滿二叉樹,則沒有結點的地方,用空格填充(但請略去所有的行末空格)。每一行父結點與子結點中隔開一行,用斜杠(/)與反斜杠(\)來表示樹的關係。/出現的橫坐標位置為父結點的橫坐標偏左一格,\出現的橫坐標位置為父結點的橫坐標偏右一格。也就是說,如果樹高為m,則輸出就有2m-1行。


用一個graph[MAX_ROW][MAX_COL] 數組保存表達式樹的位置信息。以題例來看:
   +
  /  \
 a    *
     /  \
     b  c
首先,一共有\(2^h-1\)個結點,層層找結點位置,中間位置,即第\(2^h-1\)個結點+把該層分為兩個部分,且左右部分皆是\(2^h-2\)長度(ps:因為包含了\/的位置),如果有左結點,左結點就在\(2^h-2\)的中間部分,(ps:這裡由於用到了\(2^x\)次方,在巨集定義了一個POW(NUM) 1 << NUM, 1 << NUM 即右移NUM,即\(2的NUM\)次方)右結點也如此處理...這樣,處理下一層時,也如同上一層處理:先處理根結點,在處理左右結點。在處理完根結點後下一層應該留給/\,位置為中間位置左偏一個或右偏一個;

void Print(Bitree root, int row, int col, int len)
{
   if (root == NULL) return;
    graph[row][col-1] = root->data;     //當前層中間位置
    if (root->left_child!=NULL) {
        graph[row+1][col-2] = '/';      //下一層留給/,中間位置偏左一個
        Print(root->left_child,row+2,col-len,len>>1); // 左邊部分
    }
    if (root->right_child!=NULL) {
        graph[row+1][col] = '\\';
        Print(root->right_child,row+2,col+len,len>>1);
    }
}
對於列印表達式樹這一部分,參考了表達式·表達式樹·表達式求值,詳細訪問鏈接

計算部分

對於這一部分,由於已經有了表達式樹了,只需要一層層求解即可:

  1. 如果結點是運算符,將左樹和右樹的計算結果和根運算
  2. 如果結點不是運算符,直接返回變數值
    其中左樹和右樹的計算結果就是一個遞歸過程,反覆執行1、2步;
這一部分由於要用到變數的值, 使用了 map<char,int> to_value的一個映射,給一個結點變數,對應一個變數值
對於計算部分,參考了表達式·表達式樹·表達式求值,詳細訪問鏈接

其它

其他部分根據題意求解即可,關於表達式輸入,使用了一個char*指針,但是我在這個地方初始化 expression = new char [52];一開始記成 expression = new char (52);導致一直不能過...


完整代碼

/*
@File     :   expression_tree.cpp
@Time     :   2020/04/27
@Desc     :   表達式·表達式樹·表達式求值
*/
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <map>
#define POW(NUM) 1<<NUM
#define MAX_ROW 70
#define MAX_COL 300

using namespace std;

typedef struct BTNode
{
    char data;
    struct BTNode *left_child;
    struct BTNode *right_child;
}*Bitree;
char graph[MAX_ROW][MAX_COL];
map <char,int> to_value;
void CreateExpressionTree(char *expression, int start, int end, Bitree &tree)
{
    int i, c1 = -1, c2 = -1, p = 0;
    if (end - start == 1) {
        tree = new BTNode;
        tree->data = *(expression+start);
        tree->left_child = tree->right_child = NULL;
        return;
    }
    for (i = start; i < end; i++) {
        switch (*(expression+i))
        {
        case '(': p++; break;
        case ')': p--; break;
        case '+': case '-': if (!p) c1 = i; break;
        case '*': case '/': if (!p) c2 = i; break;
        }
    }
    if (c1 < 0) c1 = c2;
    if (c1 < 0) CreateExpressionTree(expression,start+1,end-1,tree);
    else {
        CreateExpressionTree(expression,c1,c1 + 1,tree);
        CreateExpressionTree(expression,start,c1,tree->left_child);
        CreateExpressionTree(expression,c1+1,end,tree->right_child);
    }
}
void PostOrder(Bitree tree)
{
    if (tree == NULL) return;
    PostOrder(tree->left_child);
    PostOrder(tree->right_child);
    cout << tree->data;
}
int GetHeight(Bitree tree)
{
    int left_height, right_height;
    if (tree == NULL) return 0;
    else {
        left_height = GetHeight(tree->left_child);
        right_height = GetHeight(tree->right_child);
        return (left_height > right_height)?(left_height+1):(right_height+1);
    }  
}
void Print(Bitree root, int row, int col, int len)
{
   if (root == NULL) return;
    graph[row][col-1] = root->data;
    if (root->left_child!=NULL) {
        graph[row+1][col-2] = '/';
        Print(root->left_child,row+2,col-len,len>>1);
    }
    if (root->right_child!=NULL) {
        graph[row+1][col] = '\\';
        Print(root->right_child,row+2,col+len,len>>1);
    }
}
int Calculate(Bitree tree)
{
    switch (tree->data) 
    {
    case '+':
        return Calculate(tree->left_child) + Calculate (tree->right_child);
        break;
    case '-':
        return Calculate(tree->left_child) - Calculate (tree->right_child);
        break;
    case '*':
        return Calculate(tree->left_child) * Calculate (tree->right_child);
        break;
    case '/':
        return Calculate(tree->left_child) / Calculate (tree->right_child);
        break;
    default:
        return to_value[tree->data];
        break;
    }
}
int main(int argc, char const *argv[])
{
    int n, value;
    char *expression, valuable;
    Bitree tree;
    // expression = (char*)malloc(sizeof(char)*52);
    // expression = new char(52); !!!!!
    expression = new char[52];
    cin >> expression;
    cin >> n;
    while (n--) {
        cin >> valuable >> value;
        to_value[valuable] = value;
    }
    CreateExpressionTree(expression,0,strlen(expression),tree); 
    PostOrder(tree); cout << endl;
    memset(graph,' ',sizeof(graph));
    Print(tree,0,POW(GetHeight(tree)-1),POW(GetHeight(tree)-2)); 
    int j = 0, l = 0;
    for (int i = 0; i < MAX_ROW; ++i) {
        j = MAX_COL - 1;
        while (j >= 0 && graph[i][j] == ' ') --j;
        if (j > -1) {
            ++l;
            graph[i][j+1] = '\0';
        }
        else break;
    }
    for (int i = 0; i < l; ++i) cout << graph[i] << endl;
    cout << Calculate(tree) << endl;
    system("pause");
    return 0;
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 引言 要是世上不曾存在C++14和C++17該有多好! 是好東西,但是讓編譯器開發者痛不欲生;新標準庫的確好用,但改語法細節未必是明智之舉,尤其是3年一次的頻繁改動。C++帶了太多歷史包袱,我們都是為之買賬的一員。 我沒那麼多精力考慮C++14/17的問題,所以本文基於C++11標準。 知其所以然, ...
  • 0.前言:本次主要是針對第二階段的三次作業的總結,這三次作業主要是抽象、繼承、多態展開的針對性練習,所以在此闡述在練習過程中的問題以及感悟。 1.作業總結 1. 三次作業的難度逐步增加,由淺及深。由繼承再逐步深入到多態以及抽象類的學習。第二階段的第一次作業的前兩題還沒有涉及到繼承,只是加深對單一職責 ...
  • 我前幾篇文章都是說一些python爬蟲庫的用法,還沒有說怎樣利用好這些知識玩一些好玩的東西。那我今天帶大家玩好玩又刺激的,嘻嘻!對了,requests庫和正則表達式很重要的,一定要學會!一定要學會!!一定要學會!!!我現在的爬蟲基本都是用這兩樣東西來爬的。所以學不學你看著辦吧。 這裡還要註意:不管你 ...
  • A - Romaji 題意:本題比較簡單,給你一個字元串,要你判斷字元串中的每一個輔音字元後面是否有一個母音字元。 題解:簡單簽到題,模擬即可。 代碼: #include<iostream> #include<cstring> #include<algorithm> #define ll long ...
  • 我的LeetCode:https://leetcode cn.com/u/ituring/ 我的LeetCode刷題源碼[GitHub]:https://github.com/izhoujie/Algorithmcii LeetCode 154. 尋找旋轉排序數組中的最小值 II 題目 假設按照升序 ...
  • 一、阿裡雲 OSS 1、什麼是阿裡雲 OSS? OSS 為 Object Storage Service,即對象存儲服務。是阿裡雲提供的海量、安全、低成本、高可靠的雲存儲服務。 OSS 具有與平臺無關的 RESTful API 介面,可以在任意應用、任意時間、任意地點 存儲與訪問 任何類型的數據。  ...
  • PHP作為一種解釋型語言,不同於編譯型語言編譯結果即為當前CPU體系的指令,PHP源代碼只有編譯成opcode才能夠被zend虛擬機直接執行。 下麵就簡單描述PHP7語言執行原理: 1. 源代碼首先利用Re2c實現的詞法分析器進行詞法分析,將源代碼切割為多個字元串單元,分割後的字元串稱為Token; ...
  • 有的時候我們只想根據一個請求地址跳轉到一個頁面中,中間並沒有任何的處理流程,這個時候創建一個 Controller 類再編寫方法來跳轉就顯得很繁瑣。這個時候我們就可以使用 來直接進行跳轉。 使用 view controller 主要分為三步: 第一步:開啟 mvc 命名空間 第二部:編寫 view ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...