C語言數據結構二叉樹的建立並中序線索化

一提到線索化二叉樹,可能有些小夥伴的頭都要炸了!如果你遇到了問題,可以參照一下這個版本,希望對你有幫助。

內容:

1、二叉樹的建立

2、二叉樹的中序線索化

3、二叉樹的線索化遍歷

4、前繼和後繼結點的查找

5、完整代碼

6、測試

結構:

#include<stdio.h>
#include<stdlib.h>
typedef char Elemtype;
typedef enum {
	Link,Thread        //枚舉型,Link默認值爲0,Thread默認值爲1
}PointerTag;
typedef struct _BiTree{
	struct _BiTree *lchild,*rchild,*parent;
	Elemtype data;
	PointerTag LTag,RTag;
}BiTree; 



1、二叉樹的建立:

//先序創建線索二叉樹,初始化雙親地址,LTag、RTag 
BiTree *Create(BiTree *T,BiTree *p)		//p爲給parent賦值的指針,初始爲NULL 
{
	Elemtype i;
	scanf("%c",&i);
	if(i==' ') T=NULL;
	else
	{
		T=(BiTree *)malloc(sizeof(BiTree));
		if(!T) return ;
		T->data=i;
		T->parent=p;
		p=T;
		T->LTag=Link;		//重要的兩步,如果不初始化值後面就會出錯 
		T->RTag=Link;
		T->lchild = Create(T->lchild,p);
		T->rchild = Create(T->rchild,p);
	}
	return T;
}

注意!注意!注意!在二叉樹的建立過程中一定要注意,將所有結點的LTag與RTag的初始值賦Link,也就是0



2、二叉樹的中序線索化

//主線索化函數調用的輔助遞歸線索化函數 
BiTree *InThreading(BiTree *T,BiTree *pre)
{
	if(T)		//重中之重,遞歸條件T必須存在 
	{
		pre=InThreading(T->lchild,pre);    //線索化左孩子
		
		//左孩子不存在Ltag就等於Thread,左孩子指向前驅線索pre 
		if(!T->lchild) {T->LTag = Thread; T->lchild = pre;}
		
		//前驅線索pre不存在RTag就等於Thread,右孩子就等於該層的T 
		if(!pre->rchild) {pre->RTag = Thread; pre->rchild = T;}
		
		//pre隨着T做線性移動 
		pre = T;
		
		pre=InThreading(T->rchild,pre);    //線索化左孩子
	}
	return pre;		//返回pre的值 
}

//主線索化函數,主要開闢頭結點、調用輔助遞歸線索化函數、處理頭結點及最後一個結點 
BiTree *InOrderThreading(BiTree *T)
{
	//開闢頭結點(LTag=Link,lchild指向樹根RTag=Thread,rchild指向自身)
	//定義一個前驅指針pre 
	BiTree *pre,*Thrt = (BiTree *)malloc(sizeof(BiTree));
	if(!Thrt) printf("1失敗");
	
	//頭結點lchild指向樹,rchild回指 
	Thrt->LTag = Link; Thrt->RTag = Thread; Thrt->rchild = Thrt;
	if(!T) Thrt->lchild = Thrt;
	else
	{
		Thrt->lchild = T; pre = Thrt;		//pre指向頭結點 
		pre=InThreading(T,pre);		//線索化 
		
		//線索化樹的最後一個結點 
		pre->RTag = Thread; pre->rchild = Thrt; 
		Thrt->rchild = pre;
	}
	return Thrt;		//返回頭結點地址 
}

就這兩個函數就可以實現中序二叉樹的線索化,可以叫這兩個函數爲父子函數,由主函數調用父函數,再由父函數調用子函數進行遞歸線索化。

父函數宏觀上的作用:

1、開闢一個頭結點並加以處理。給這個頭結點的左、右標誌賦值,讓左指針域指向二叉樹(如果二叉樹存在)右指針域指向自身。
2、調用子函數進行遞歸線索化
3、處理二叉樹的最後一個結點
4、返回這個開闢的頭結點的地址

子函數宏觀上的作用:

1、線索化左指針域
2、判斷左孩子是否存在,若不存在就讓LTag=Thread,然後連接前驅結點
3、判斷前驅結點的右孩子是否存在,若不存在就讓RTag=Thread,然後連接當前的T值
4、讓pre前驅指針跟隨T移動

5、線索化右指針域


3、二叉樹的線索化遍歷

//打印函數 
void Print_1(BiTree *T)
{
	printf("%d<%c>%d\n",T->LTag,T->data,T->RTag);
}

//非遞歸遍歷線索二叉樹,調用打印函數 
void InOrderTraverseThread(BiTree *T,void (*Visit)(BiTree *T))
{
	BiTree *p=T->lchild;
	while(p != T)		//大前提,最後一個結點走完時p=T,走完了一圈 
	{
	    while(p->LTag == Link) p=p->lchild;		//一直向左 
		
	    Print_1(p);			//打印 
		
	    while(p->RTag==Thread && p->rchild!=T)	//RTag=Thread且不是最後一個結點時 
	    {
		p=p->rchild; Print_1(p);
	    }
		
	    p=p->rchild;
	}
} 

因爲二叉樹的線索化已經完成,所以這個函數接受的是頭結點。這個線索二叉樹構成了一個環狀的線性結構,可以從頭結點開始然後遍歷整個二叉樹,最後回到頭結點。



4、前繼和後繼結點的查找

//判斷數據是否相等,相等則返回該地址 
BiTree *Panduan(BiTree *T)
{
	if(T->data == ch) return T;
	else return NULL;	
}

//非遞歸遍歷線索二叉樹,查找期望鍵值的地址 
BiTree *InOrder_Search(BiTree *T,BiTree *(*Visit)(BiTree *T))
{
	BiTree *p=T->lchild;
	while(p != T)		//大前提,最後一個結點走完時p=T,走完了一圈 
	{
		while(p->LTag == Link) p=p->lchild;		//一直向左 
		
		if(Panduan(p)) return p;	//判斷,如果返回值存在就返回這個返回值 
		
		while(p->RTag==Thread && p->rchild!=T)	//RTag=Thread且不是最後一個結點時 
		{
			//判斷,如果返回值存在就返回這個返回值
			p=p->rchild; if(Panduan(p)) return p;
		}
		
		p=p->rchild;
	}
}

//查找中序遍歷指定結點的後繼 
void Search_post(BiTree *p)		//p爲目標結點的地址 
{
	BiTree *q,*temp;
	if(p->RTag == Thread) temp = p->rchild;	//如果右標誌爲線索,則線索指向的就是後繼 
	if(p->RTag == Link)		//右標誌爲結點,則向右一個,再左到底爲後繼 
	{
		q=p->rchild;
		while(q->LTag == Link) q = q->lchild;
		temp = q;
	}
	printf("%c的後繼是%c",p->data,temp->data);
}

//查找中序遍歷指定結點的前繼
void Search_pre(BiTree *p)		//p爲目標結點的地址		
{
	BiTree *q,*temp;
	if(p->LTag == Thread) temp=p->lchild;//若左標誌爲線索,則線索就指向前繼 
	if(p->LTag == Link)		//若左標誌爲結點,則向左一個,再右到底爲前繼 
	{
		q=p->lchild;
		while(q->RTag == Link) q=q->rchild;
		temp = q;
	 } 
	printf("%c的前繼是%c",p->data,temp->data);
} 

開頭兩個函數是通過遍歷整個線索二叉樹找到想查詢鍵值的地址,然後把這個地址傳入Search_post或Search_pre中,去找到後繼或前繼。

Panduan()函數中的ch是個全局字符變量可改變想要查詢的鍵值。



5、完整代碼

#include<stdio.h>
#include<stdlib.h>
typedef char Elemtype;
typedef enum {
	Link,Thread
}PointerTag;
typedef struct _BiTree{
	struct _BiTree *lchild,*rchild,*parent;
	Elemtype data;
	PointerTag LTag,RTag;
}BiTree; 

Elemtype ch;		//全局變量,用於查找,來找到這個鍵值的地址 

//打印數據 
void Print_2(Elemtype e)
{
	printf("%c",e);
}

//打印函數 
void Print_1(BiTree *T)
{
	printf("%d<%c>%d\n",T->LTag,T->data,T->RTag);
}

//非遞歸遍歷線索二叉樹,調用打印函數 
void InOrderTraverseThread(BiTree *T,void (*Visit)(BiTree *T))
{
	//一直向左
	BiTree *p=T->lchild;
	while(p != T)		//大前提,最後一個結點走完時p=T,走完了一圈 
	{
		while(p->LTag == Link) p=p->lchild;		//一直向左 
		
		Print_1(p);			//打印 
		
		while(p->RTag==Thread && p->rchild!=T)	//RTag=Thread且不是最後一個結點時 
		{
			p=p->rchild; Print_1(p);
		}
		
		p=p->rchild;
	}
} 


//遞歸遍歷二叉樹,以廣義表形式輸出 
void Traverse_pre(BiTree *T,void (*Visit)(Elemtype e))
{
	if(T)
	{
		Print_2(T->data);
		if(T->lchild || T->rchild)
		{
			printf("(");
			Traverse_pre(T->lchild,Print_2);
			if(T->rchild) printf(",");
			Traverse_pre(T->rchild,Print_2);
			printf(")");
		}
	}
}

//判斷數據是否相等,相等則返回該地址 
BiTree *Panduan(BiTree *T)
{
	if(T->data == ch) return T;
	else return NULL;	
}

//非遞歸遍歷線索二叉樹,查找期望鍵值的地址 
BiTree *InOrder_Search(BiTree *T,BiTree *(*Visit)(BiTree *T))
{
	//一直向左
	BiTree *p=T->lchild;
	while(p != T)		//大前提,最後一個結點走完時p=T,走完了一圈 
	{
		while(p->LTag == Link) p=p->lchild;		//一直向左 
		
		if(Panduan(p)) return p;			//判斷,如果返回值存在就返回這個返回值 
		
		while(p->RTag==Thread && p->rchild!=T)	//RTag=Thread且不是最後一個結點時 
		{
			//判斷,如果返回值存在就返回這個返回值
			p=p->rchild; if(Panduan(p)) return p;
		}
		
		p=p->rchild;
	}
}

//查找中序遍歷指定結點的後繼 
void Search_post(BiTree *p)		//p爲目標結點的地址 
{
	BiTree *q,*temp;
	if(p->RTag == Thread) temp = p->rchild;	//如果右標誌爲線索,則線索指向的就是後繼 
	if(p->RTag == Link)		//右標誌爲結點,則向右一個,再左到底爲後繼 
	{
		q=p->rchild;
		while(q->LTag == Link) q = q->lchild;
		temp = q;
	}
	printf("%c的後繼是%c",p->data,temp->data);
}

//查找中序遍歷指定結點的前繼
void Search_pre(BiTree *p)		//p爲目標結點的地址		
{
	BiTree *q,*temp;
	if(p->LTag == Thread) temp=p->lchild;//若左標誌爲線索,則線索就指向前繼 
	if(p->LTag == Link)		//若左標誌爲結點,則向左一個,再右到底爲前繼 
	{
		q=p->lchild;
		while(q->RTag == Link) q=q->rchild;
		temp = q;
	 } 
	printf("%c的前繼是%c",p->data,temp->data);
} 

//主線索化函數調用的輔助遞歸線索化函數 
BiTree *InThreading(BiTree *T,BiTree *pre)
{
	if(T)		//重中之重,遞歸條件T必須存在 
	{
		pre=InThreading(T->lchild,pre);
		
		//左孩子不存在Ltag就等於Thread,左孩子指向前驅線索pre 
		if(!T->lchild) {T->LTag = Thread; T->lchild = pre;}
		
		//前驅線索pre不存在RTag就等於Thread,右孩子就等於該層的T 
		if(!pre->rchild) {pre->RTag = Thread; pre->rchild = T;}
		
		//pre隨着T做線性移動 
		pre = T;
		
		pre=InThreading(T->rchild,pre);
	}
	return pre;		//返回pre的值 
}

//主線索化函數,主要開闢頭結點、調用輔助遞歸線索化函數、處理頭結點及最後一個結點 
BiTree *InOrderThreading(BiTree *T)
{
	//開闢頭結點(LTag=Link,lchild指向樹根RTag=Thread,rchild指向自身)
	//定義一個前驅指針pre 
	BiTree *pre,*Thrt = (BiTree *)malloc(sizeof(BiTree));
	if(!Thrt) printf("1失敗");
	
	//頭結點lchild指向樹,rchild回指 
	Thrt->LTag = Link; Thrt->RTag = Thread; Thrt->rchild = Thrt;
	if(!T) Thrt->lchild = Thrt;
	else
	{
		Thrt->lchild = T; pre = Thrt;		//pre指向頭結點 
		pre=InThreading(T,pre);		//線索化 
		
		//線索化樹的最後一個結點 
		pre->RTag = Thread; pre->rchild = Thrt; 
		Thrt->rchild = pre;
	}
	return Thrt;		//返回頭結點地址 
}

//先序創建線索二叉樹,初始化雙親地址,LTag、RTag 
BiTree *Create(BiTree *T,BiTree *p)		//p爲給parent賦值的指針,初始爲NULL 
{
	Elemtype i;
	scanf("%c",&i);
	if(i=='#') T=NULL;
	else
	{
		T=(BiTree *)malloc(sizeof(BiTree));
		if(!T) return ;
		T->data=i;
		T->parent=p;
		p=T;
		T->LTag=Link;		//重要的兩步,如果不初始化值後面就會出錯 
		T->RTag=Link;
		T->lchild = Create(T->lchild,p);
		T->rchild = Create(T->rchild,p);
	}
	return T;
}

int main()
{
	BiTree *T,*p=NULL,*q;
	printf("先序遞歸創建二叉樹:\n");
	T=Create(T,p);
	printf("以廣義表的形式打印二叉樹:\n");Traverse_pre(T,Print_2);
	printf("\n");
	T=InOrderThreading(T);		//已加入頭結點 
	printf("中序遍歷的結果:從上到下\n");
	InOrderTraverseThread(T,Print_1);		//傳遞頭結點 
        ch='A';    //全局變量
	q=InOrder_Search(T,Panduan);		//返回的是鍵值A的地址 
	printf("查找中序遍歷A的後繼\n####################\n");
	Search_post(q);
	printf("\n");
	printf("查找中序遍歷A的前繼繼\n####################\n");
	Search_pre(q);		//傳輸的是鍵值A的地址 
	return 0;
}



6、測試



這些代碼都是前人的智慧所凝聚的精華,我們要做的就是繼承,這就是所謂的站在巨人的肩膀上。

有的同學會認爲學習這些算法沒什麼用處,千萬別這麼想!學習是面向過程的,在學習的過程中鍛鍊的是你自己的學習能力,而不在於學習的東西是否有用處。

可能我們從小到大一直在學習沒什麼用的知識,但還是過來了。在學習的過程中要注重方法,這就是你學習能力的體現,比如說把複雜的問題簡單化,一步一步把問題解決,答案自然浮現在眼前。

這就是在學習的過程中鍛鍊的能力,使自己辦事情的效率更高,在以後的學習工作中也就可以站得更高看得更遠。



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