描述 眾所周知,任何一個表達式,都可以用一棵表達式樹來表示。例如,表達式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\),由運算符分為左右兩個部分。其二叉樹表示形式自然為:
所以,構建一個表達式樹,關鍵在於找到表達式的根結點,然後分左右兩個部分構建樹;拓展到多個同級運算符的表達式:\(a+b+c+...+n\),可以這樣構建其表達式樹:
- 找到最先運算的根結點,若以最右邊的一個+為根結點
- 以根結點分為左右兩個部分
- 構建根結點,左邊構建樹,右邊構建樹
- 若左邊部分又是一個表達式,執行1、2、3步驟
- 若右邊部分又是一個表達式,執行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\),因為+是最後運算,所以它一定是樹的根,*先運算,是+號分得的右邊部分的根,這樣得到其表達式樹:
所以,含有優先順序不同的表達式,關鍵在於找到表達式最後運算的運算符,作為某個表達式樹的根,然後分左右兩個部分構建樹;同樣,同級運算符以最左邊的運算符為最後運算的運算符,統一第一種情況:拓展到多個不同運算符出現的情況:\(a+b*c p_1...p_k n(p_i為第i個運算符)\)
- 找到最後運算的運算符作為根結點
- 以根結點分為左右兩個部分
- 構建根結點,左邊構建樹,右邊構建樹
- 若左邊部分又是一個表達式,執行1、2、3步驟
- 若右邊部分又是一個表達式,執行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)\)為例:
- 找到最後運算的+號分左右兩個部分:\(a*b\)和\((c+d)\)
- 左邊部分建樹
- 右邊部分建樹:
- 右邊部分括弧外找不到最後運算的運算符,說明整個表達式被括弧括起來
- 去掉括弧:c+d 建樹
- 若左邊部分又是一個表達式,執行1、2、3步驟
- 若右邊部分又是一個表達式,執行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步;
這一部分由於要用到變數的值, 使用了 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;
}