一提到線索化二叉樹,可能有些小夥伴的頭都要炸了!如果你遇到了問題,可以參照一下這個版本,希望對你有幫助。
內容:
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、測試
這些代碼都是前人的智慧所凝聚的精華,我們要做的就是繼承,這就是所謂的站在巨人的肩膀上。
有的同學會認爲學習這些算法沒什麼用處,千萬別這麼想!學習是面向過程的,在學習的過程中鍛鍊的是你自己的學習能力,而不在於學習的東西是否有用處。
可能我們從小到大一直在學習沒什麼用的知識,但還是過來了。在學習的過程中要注重方法,這就是你學習能力的體現,比如說把複雜的問題簡單化,一步一步把問題解決,答案自然浮現在眼前。
這就是在學習的過程中鍛鍊的能力,使自己辦事情的效率更高,在以後的學習工作中也就可以站得更高看得更遠。