二叉樹——表達式轉換

論如何把後綴表達式轉成前綴表達式

首先,我們開門見山!——
先從表達式樹開始說起:
在這裏插入圖片描述
這就是一個典型的表達式樹,假設我們表達式樹的葉節點是操作數(簡單理解成數字),表達式樹的葉節點是操作數(簡單理解爲+ - * / 這些字符),假設所有的運算符都是雙目運算符,那麼剛好形成一棵二叉樹,然後我們就可以非常非常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)
(歡迎各位大神評論)
——————都看到這裏了,不點個贊是不是也覺得不好意思呀~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章