論如何把後綴表達式轉成前綴表達式
首先,我們開門見山!——
先從表達式樹開始說起:
這就是一個典型的表達式樹,假設我們表達式樹的葉節點是操作數(簡單理解成數字),表達式樹的葉節點是操作數(簡單理解爲+ - * / 這些字符),假設所有的運算符都是雙目運算符,那麼剛好形成一棵二叉樹,然後我們就可以非常非常easy的遍歷這課樹來獲得——前綴、中綴、後綴表達式,
它們的關係分別爲——
前序遍歷:前綴表達式
中序遍歷:中綴表達式
後序遍歷:後綴表達式
假設我們現在有了一棵非常非常厲害的 表達式樹
它的前序遍歷:++abcde——這就是前綴表達式
中序遍歷:a+bc+de——這就是中值表達式(我們最熟悉的)
後序遍歷:abc*+de*+——這就是後綴表達式
當然,現在我們面臨的一個問題,就是如何將後綴表達式轉化成一棵無敵的二叉樹!
後置表達式建樹
1.大致思路
舉例說明!
假設我們有後綴表達式 12+34++ ,就是我們常見的1+2+3+4轉換成後綴的樣子
first,我們先有一個棧
(狀態初始化爲空)
然後——我們從左往右掃這個後綴表達式12+34++
遇到 1 ,是數字,入棧!
現在棧內元素——1
接着,我們掃到了 2 ,還是數字,進棧
現在棧的狀態——1 2
繼續掃描,掃到了操作符 +,彈出棧頂的兩個元素,與加號建成一棵二叉樹,然後將這課二叉樹入棧
就長這個醜樣子
再掃描表達式,數字3,進棧。
然後數字4,進棧
現在棧的狀態就是上面這個樣子
掃完3和4,接下來就是兩個加號 +
棧的狀態分別爲
然後是——
於是,由圖可得,我們棧頂的那個元素就是那個我們需要的二叉樹(表達式樹)。但是,現在我們, 面臨着一個非常嚴肅的問題——如何用代碼實現!
代碼實現
我們先分佈介紹,最後在總結在一起!
1.結構體部分(棧的結構、二叉樹的結構)
首先——二叉樹必備部分是數據域、指針域(左孩子、右孩子、父親節點)
//創造二叉樹的節點構造
typedef struct node
{
char data; //數據域
node *lc; //左
node *rc; //右
}*TREE;
接着,二叉樹的結構
typedef struct node_father
{
TREE father;
}Tree;
解決了二叉樹的結構,我們來構造棧的結構
//棧的節點構造
typedef struct Stack
{
Tree tree; //存儲數根(數據域)
Stack *next; //指向下一個節點
}*Stack_;
接着,棧的構造——
typedef struct stack_
{
Stack_ top; //指向棧頂元素
int l; //棧的長度
}stack;
構造完基本的結構後,我們還要加入一個枚舉類型的變量(其實不用也可以),但是爲了代碼的通俗易懂(可讀性強,我們加入一個)
//枚舉是左孩子還是右孩子
typedef enum is_child
{
_left=1,
_right=2
}is_child;
結束了基本的結構後,我們開始寫它們的function,它們的功能以及基本操作
2.函數部分
首先,先來寫關於建樹的函數——
//創建樹節點 (左右均爲空)
TREE buildroot(char c)
{
TREE value=(TREE)malloc(sizeof(node)); //這句話尤其重要,分配內存是是否段錯誤的根本
value->data=c;
value->lc=value->rc=NULL;
return value;
}
然後,就是一個插入根節點的函數——
//插入根節點
void insert_root(Tree *tree,TREE value)
{
tree->father=value;
}
接着,我們來做合併二叉樹的函數——
//合併二叉樹
void merge(Tree *father,Tree *child,is_child flag)
{
if(flag==_left)
father->father->lc=child->father;
else
father->father->rc=child->father;
}
上面都是二叉樹的操作,然後我們要寫棧的操作:
先來初始化棧——
void initstack(stack *sta) //初始化棧
{
sta->l=0;
sta->top=NULL;
}
然後是入棧操作——
void push(stack *sta,Tree t)
{
Stack_ value=(Stack_)malloc(sizeof(Stack));
value->tree=t;
value->next=sta->top;
sta->top=value;
sta->l++;
}
接着出棧——
void pop(stack *sta,Tree *t)
{
if(sta->top==NULL) //這是爲了防止空棧時我們pop後會爆棧(STL棧在空棧的時候執行pop操作就會爆棧)
return;
Stack_ value=sta->top;
(*t)=value->tree; //這裏就把我們棧頂的值賦給了傳進來的Tree
sta->top=value->next;
sta->l--;
}
我們也可以再多此一舉的寫一個結構體來封裝棧的功能
typedef class function_sta
{
public:
void initstack(stack *sta) //初始化棧
{
sta->l=0;
sta->top=NULL;
}
void push(stack *sta,Tree t)
{
Stack_ value=(Stack_)malloc(sizeof(Stack));
value->tree=t;
value->next=sta->top;
sta->top=value;
sta->l++;
}
void pop(stack *sta,Tree *t)
{
if(sta->top==NULL)
return;
Stack_ value=sta->top;
(*t)=value->tree;
sta->top=value->next;
sta->l--;
}
}fun_s;
因爲我們C++是面向對象的語言,感覺要是不用class(類)來寫的話,就太偏向C語言了。(不能太偏心)
寫完後,我們迎來了最重要的一個部分——生成表達式樹的部分
3.生成表達式樹
//構造表達式樹
void build_poland(Tree *tree)
{
stack s;
fun_s sta;
sta.initstack(&s);
for(int i=0;i<l_1;i++)
{
TREE val=buildroot(ch[i]); //得到一個父節點爲ch[i],兩個子節點爲空的二叉樹
Tree left,right,father;
if(cmp(ch[i])) //如果是運算符 ,那麼將棧頂的兩個元素彈出,與運算符建立成二叉樹,然後再把生成的二叉樹入棧
{
insert_root(&father,val); //插入節點(建立一個二叉樹)
sta.pop(&s,&right); //出棧
merge(&father,&right,_right); //合併到右孩子去
sta.pop(&s,&left); //出棧
merge(&father,&left,_left); //合併到左孩子去
sta.push(&s,father); //入棧
}
else //如果不是運算符 ,那麼直接入棧
{
insert_root(&father,val); //插入節點(建立一個二叉樹)
sta.push(&s,father);
}
}
sta.pop(&s,tree); //這是結果的值
}
當然,我們會發現,上面有一個cmp函數還沒定義
#define cmp(c) (c=='+'||c=='-'||c=='*'||c=='/')
這就是cmp函數的定義,意思是判斷這個字符只不是運算符,如果是——返回1(true);
不是——返回0(false)
這個build_poland函數裏面還有一個ch[], 和 l_1,沒有講到;
其實,ch[] ,就是我們存表達式的字符串, 而 l_1 就是這個字符串的長度。
我們在全局區定義——
char ch[50005]; //表達式
int l_1; //長度
但是,爲了通用性,我們也可以在build_poland函數裏面這麼寫
void build_poland(Tree *tree,char ch[])
{
stack s;
fun_s sta;
sta.initstack(&s);
int l_1=strlen(ch);
for(int i=0;i<l_1;i++)
{
TREE val=buildroot(ch[i]); //得到一個父節點爲ch[i],兩個子節點爲空的二叉樹
Tree left,right,father;
if(cmp(ch[i]))
{
insert_root(&father,val); //插入節點(建立一個二叉樹)
sta.pop(&s,&right); //出棧
merge(&father,&right,_right); //合併到右孩子去
sta.pop(&s,&left); //出棧
merge(&father,&left,_left); //合併到左孩子去
sta.push(&s,father); //入棧
}
else //如果不是運算符 ,那麼直接入棧
{
insert_root(&father,val); //插入節點(建立一個二叉樹)
sta.push(&s,father);
}
}
sta.pop(&s,tree);
}
這樣我們就有了這個函數的通用性
最後,我們要寫樹的三種遍歷方式,來遍歷表達式樹,得到三種表達式
//先序遍歷輸出結果;
void first(TREE tree)
{
if(tree==NULL)
return;
putchar(tree->data);
first(tree->lc);
first(tree->rc);
}
//中序遍歷輸出結果;
void second(TREE tree)
{
if(tree==NULL)
return;
second(tree->lc);
putchar(tree->data);
second(tree->rc);
}
//後序遍歷輸出結果;
void last(TREE tree)
{
if(tree==NULL)
return;
last(tree->lc);
last(tree->rc);
putchar(tree->data);
}
最後是我們的終極的目標——main函數
int main()
{
gets(ch); //輸入後綴表達式
l_1=strlen(ch); //得到表達式長度
Tree ans; //表達式樹
build_poland(&ans); //得到表達式樹
first(ans.father); //輸出前綴表達式
puts(""); //遍歷完成,輸出換行
second(ans.father); //輸出中綴表達式
puts(""); //遍歷完成,輸出換行
last(ans.father); //輸出後綴表達式
puts(""); //遍歷完成,輸出換行
return 0;
}
大致我們的表達式轉換就完成了!
運行結果:
輸入——12+34++
輸出——
是正確的!程序沒毛病~
完整代碼展示——
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define cmp(c) (c=='+'||c=='-'||c=='*'||c=='/')
char ch[50005]; //表達式
int l_1; //長度
//二叉樹節點存儲結構
typedef struct node
{
char data; //數據域
node *lc; //左
node *rc; //右
}*TREE;
//二叉樹結構
typedef struct node_father
{
TREE father;
}Tree;
typedef struct Stack
{
Tree tree; //數根
Stack *next; //單鏈表存儲
}*Stack_,Stack;
typedef struct stack_
{
Stack_ top; //指向棧頂元素
int l; //棧的長度
}stack;
typedef class funtion_sta
{
public:
void initstack(stack *sta) //初始化棧
{
sta->l=0;
sta->top=NULL;
}
void push(stack *sta,Tree t)
{
Stack_ value=(Stack_)malloc(sizeof(Stack));
value->tree=t;
value->next=sta->top;
sta->top=value;
sta->l++;
}
void pop(stack *sta,Tree *t)
{
if(sta->top==NULL)
return;
Stack_ value=sta->top;
(*t)=value->tree;
sta->top=value->next;
sta->l--;
}
}fun_s;
//枚舉是左孩子還是右孩子
typedef enum is_child
{
_left=1,
_right=2
}is_child;
//創建樹節點 (左右均爲空)
TREE buildroot(char c)
{
TREE value=(TREE)malloc(sizeof(node)); //由此可見,分配內存的重要性!!!
//爲什麼可以不free,但一定要malloc???嗚嗚嗚~T_T難受——段錯誤的根本!!!
value->data=c;
value->lc=value->rc=NULL;
return value;
}
//插入根節點
void insert_root(Tree *tree,TREE value)
{
tree->father=value;
}
//合併二叉樹
void merge(Tree *father,Tree *child,is_child flag)
{
if(flag==_left)
father->father->lc=child->father;
else
father->father->rc=child->father;
}
//先序遍歷輸出結果;
//先序遍歷輸出結果;
void first(TREE tree)
{
if(tree==NULL)
return;
putchar(tree->data);
first(tree->lc);
first(tree->rc);
}
//中序遍歷輸出結果;
void second(TREE tree)
{
if(tree==NULL)
return;
second(tree->lc);
putchar(tree->data);
second(tree->rc);
}
//後序遍歷輸出結果;
void last(TREE tree)
{
if(tree==NULL)
return;
last(tree->lc);
last(tree->rc);
putchar(tree->data);
}
//構造表達式樹
void build_poland(Tree *tree)
{
stack s;
fun_s sta;
sta.initstack(&s);
for(int i=0;i<l_1;i++)
{
TREE val=buildroot(ch[i]); //得到一個父節點爲ch[i],兩個子節點爲空的二叉樹
Tree left,right,father;
if(cmp(ch[i])) //如果是運算符 ,那麼將棧頂的兩個元素彈出,與運算符建立成二叉樹,然後再把生成的二叉樹入棧,
{
insert_root(&father,val); //插入節點(建立一個二叉樹)
sta.pop(&s,&right); //出棧
merge(&father,&right,_right); //合併到右孩子去
sta.pop(&s,&left); //出棧
merge(&father,&left,_left); //合併到左孩子去
sta.push(&s,father); //入棧
}
else //如果不是運算符 ,那麼直接入棧
{
insert_root(&father,val); //插入節點(建立一個二叉樹)
sta.push(&s,father);
}
}
sta.pop(&s,tree); //將結果的值賦給ans
}
int main()
{
gets(ch); //輸入後綴表達式
l_1=strlen(ch); //得到表達式長度
Tree ans; //表達式樹
build_poland(&ans); //得到表達式樹
printf("輸出:\n");
first(ans.father); //輸出前綴表達式
puts(""); //遍歷完成,輸出換行
second(ans.father); //輸出中綴表達式
puts(""); //遍歷完成,輸出換行
last(ans.father); //輸出後綴表達式
puts(""); //遍歷完成,輸出換行
return 0;
}
好了,講到這裏,完結了。
若是對這些有興趣的,請一定一定關注我,一起多多學習
我是Michael_cmr,蒟蒻一枚。如果有說錯的地方,各位大神千千萬萬要指出來。
謝謝大家!歡迎各位大神指點!
(轉載請標註出處與樓主姓名)
(QQ:2437844684)
(歡迎各位大神評論)
——————都看到這裏了,不點個贊是不是也覺得不好意思呀~