資料結構--樹--線索二叉樹(中序,前序,後序)
線索二叉樹
在遍歷二叉樹的時候,會有許多空指標域,這些空間不儲存任何事物,白白浪費了記憶體的資源。那麼在做遍歷的時候,提前記錄下每個結點的前驅和後繼,這樣就更加節約了時間。
[ lchild ] [ LTag ] [ data ] [ RTag ] [ rchild ]
LTag = { 0 : lchild 域指示結點的左孩子 1 : lchild 域指示結點的前驅 }
RTag = { 0 : rchild 域指示結點的右孩子 1 : rchild 域指示結點的後繼 }
以這種結點結構構成的二叉連結串列作為二叉樹的儲存結構,叫做線索連結串列,其中,指向結點前驅和後繼的指標,叫做線索
加上線索的二叉樹叫做線索二叉樹(Threaded Binary Tree)
對二叉樹以某種次序遍歷使其變成線索二叉樹的過程叫做線索化
★線索二叉樹結構:
#define TElemType char typedef enum{ Link,Thread }PointerTag;//Link == 0 :指標 ,Thread == 1: 線索 typedef struct BiThrNode{ TElemType data; struct BiThrNode *lchild, *rchild; //左右孩子指標 PointerTag LTag , RTag; //左右標誌 }BiThrNode, *BiThrTree;
[ lchild ] [ LTag ] [ data ] [ RTag ] [ rchild ]
LTag = { 0 : lchild 域指示結點的左孩子 1 : lchild 域指示結點的前驅 }
RTag = { 0 : rchild 域指示結點的右孩子 1 : rchild 域指示結點的後繼 }
★線索化二叉樹之前,咱們先把樹建起來(用前序遍歷建樹)
//char Vexch[20]={'H','D','A','$','$','C','$','B','$','$','G','F','$','E','$','$','$'}; char Vexch[26]={'A','B','D','H','$','$','I','$','$','E','J','$','$','$','C','F','$','$','G','$','$'}; int i=0; //二叉樹的建立 Status CreatBiThrTree(BiThrTree &T) { if(Vexch[i++]=='$') T=NULL; else { T= (BiThrTree)malloc(sizeof(BiThrNode)); if(!T) return 0; T->data=Vexch[i-1];//生成根節點 printf("%5c",T->data); T->LTag=Link; CreatBiThrTree(T->lchild);//建立左子樹 T->RTag=Link; CreatBiThrTree(T->rchild);//建立右子樹 } return 1; }
建立樹為該樹:
遍歷visit()函式
Status visit(TElemType e){
printf("%5c",e);
return OK;
}
(1)中序遍歷,線索二叉樹
【讓一棵樹 直接變成一個線性表去遍歷】遍歷 順序為:H - >D - > I - > B - > J - > E - > A - > F - > C - > G
【建立二叉樹頭結點】(下面的程式碼沒有迴圈,只是單純的建立了一個頭結點,連線上主體的樹部分,方便遍歷。)
//建立頭結點,中序線索二叉樹
Status InOrderThreading(BiThrTree &Thrt,BiThrTree T){
//中序遍歷二叉樹T,並將其中序線索化,Thrt指向頭結點。
if(!(Thrt = (BiThrTree)malloc(sizeof(BiThrNode))))
return ERROR;
Thrt->RTag = Link; //建頭結點
Thrt->rchild = Thrt ; //右指標回指
if(!T){
Thrt->lchild = Thrt;
Thrt->LTag = Link;
}else{
pre = Thrt ;
Thrt->lchild = T;
Thrt->LTag = Link;
InThreading(T);
pre->rchild = Thrt ;
pre->RTag = Thread;
Thrt->rchild = pre;
}
return OK;
}
【解釋】(pre永遠指向上一個節點)Thrt 就是 下圖的空指標, 初始化 pre 為 這個空指標,完成下圖的【1】【2】步驟
做完樹的線索化後,pre 已經到了最後一個節點,那麼就可以完成【3】【4】兩個步驟了
如下圖所示。
【1】讓最左的結點,就是中序遍歷時 第一個結點的左指標指向 空的頭結點。
【第一個結點的左標記肯定是 Thread而不是 link ,這樣就能找到最左的結點】 那麼既然是線索Thread,就讓他指向空的頭結點(反正空著也是空著)
【2】頭結點順下來,從左子樹開始找
【3】因為空節點的右標記是 Thread線索,那麼讓他指向最右的,最終結點。(反正空著也是空著)
【4】最終結點G的右線索指向頭結點,標記著終結。
構成迴圈:
↓ → → → → 【 空的頭結點】 ← ← ← ← ↑
H → D → I → B → J → E → A → F → C → G
★LTag = { 0 : lchild 域指示結點的左孩子 1 : lchild 域指示結點的前驅 } RTag = { 0 : rchild 域指示結點的右孩子 1 : rchild 域指示結點的後繼 }
概括為:LTag=0(Link)【左孩子】,LTag=1(Thread)【前驅】;RTag=0(Link)【右孩子】 ,RTag=1(Thread)【後繼】
【問】那麼怎樣的稱為Link指標 ,怎樣的 稱為 Thread 線索
【答】可以這樣理解,Link指標是本來建樹的時候就有的,而Thread線索是為了線索化,而增添的。
【中序遍歷線索化】BiThrTree pre; //全域性變數,始終指向剛剛訪問過的結點。
void InThreading(BiThrTree p){
if(p){
InThreading(p->lchild); //左子樹線索化
if(!p->lchild){ //沒有左孩子
p->LTag = Thread; //前驅線索
p->lchild = pre; //左孩子指標指向前驅
}
if(!pre->rchild){
pre->RTag = Thread; //後繼線索
pre->rchild = p ; //前驅右孩子指標指向後繼
}
pre = p;
InThreading(p->rchild); //右子樹線索化
}
}
【解釋】首先,很明顯:中序遍歷線索化,其實也是基於中序遍歷的。(從程式碼中可以看出)
也是先左,再中,後右(左 > 中 > 右)
只是在中間部分,對結點的處理的時候,有些不一樣。1、中序遍歷時是輸出。2、而現在我們把它替換為 處理標記和指標。
介紹如何處理,很簡單:
★沒有左孩子,或者沒有右孩子那肯定是線索,而不是指標
★因為是中序,遍歷肯定是從左到右,那麼左邊的線索肯定是指向前驅的,右邊的線索肯定是指向後繼的。
【中序遍歷】
//中序 遍歷線索二叉樹
Status InOrderTraverse_Thr(BiThrTree T ,Status(* visit)(TElemType e) ){
//T指向頭結點,頭結點的左鏈lchild指向根節點,可參見線索化演算法
//中序遍歷二叉線索樹T的非遞迴演算法,對每個資料元素呼叫函式visit
BiThrTree p ;
p = T->lchild; // p指向根節點
while(p != T){ //空樹 或者遍歷結束時 p == T
while(p->LTag == Link ) // 走到最左結點
p = p->lchild;
visit(p->data);
while(p->RTag == Thread && p->rchild !=T){
p = p->rchild ; // 若有右線索,
visit(p->data);
}
p = p->rchild;
}
return OK;
}
【解釋】最外面的while 從上面構造的時候就已經說明了,當p回到T的時候,那麼就標記結束了。
裡面的第一個 while(p->LTag == Link) 迴圈,走到最左結點
輸出該節點
while(p->RTag == Thread && p->rchild !=T) 如果右邊有線索,且指向的不是最後的根T, 優先按著線索走。
發現這裡沒有線索了,那麼就繼續往右孩子找。
【總的中序遍歷線索二叉樹程式碼】
#include <iostream>
#include <string.h>
#include <cstdio>
#include <stdlib.h>
using namespace std;
#define Status int
#define OK 1
#define ERROR 0
#define TElemType char
typedef enum{
Link,Thread
}PointerTag;//Link == 0 :指標 ,Thread == 1: 線索
typedef struct BiThrNode{
TElemType data;
struct BiThrNode *lchild, *rchild; //左右孩子指標
PointerTag LTag , RTag; //左右標誌
}BiThrNode, *BiThrTree;
//char Vexch[20]={'H','D','A','$','$','C','$','B','$','$','G','F','$','E','$','$','$'};
char Vexch[26]={'A','B','D','H','$','$','I','$','$','E','J','$','$','$','C','F','$','$','G','$','$'};
int i=0;
//二叉樹的建立
Status CreatBiThrTree(BiThrTree &T)
{
if(Vexch[i++]=='$') T=NULL;
else
{
T= (BiThrTree)malloc(sizeof(BiThrNode));
if(!T) return 0;
T->data=Vexch[i-1];//生成根節點
printf("%5c",T->data);
T->LTag=Link;
CreatBiThrTree(T->lchild);//建立左子樹
T->RTag=Link;
CreatBiThrTree(T->rchild);//建立右子樹
}
return 1;
}
Status visit(TElemType e){
printf("%5c",e);
return OK;
}
BiThrTree pre; //全域性變數,始終指向剛剛訪問過的結點。
void InThreading(BiThrTree p){
if(p){
InThreading(p->lchild); //左子樹線索化
if(!p->lchild){ //沒有左孩子
p->LTag = Thread; //前驅線索
p->lchild = pre; //左孩子指標指向前驅
}
if(!pre->rchild){
pre->RTag = Thread; //後繼線索
pre->rchild = p ; //前驅右孩子指標指向後繼
}
pre = p;
InThreading(p->rchild); //右子樹線索化
}
}
//建立頭結點,中序線索二叉樹
Status InOrderThreading(BiThrTree &Thrt,BiThrTree T){
//中序遍歷二叉樹T,並將其中序線索化,Thrt指向頭結點。
if(!(Thrt = (BiThrTree)malloc(sizeof(BiThrNode))))
return ERROR;
Thrt->RTag = Link; //建頭結點
Thrt->rchild = Thrt ; //右指標回指
if(!T){
Thrt->lchild = Thrt;
Thrt->LTag = Link;
}else{
pre = Thrt ;
Thrt->lchild = T;
Thrt->LTag = Link;
InThreading(T);
pre->rchild = Thrt ;
pre->RTag = Thread;
Thrt->rchild = pre;
}
return OK;
}
//中序 遍歷線索二叉樹
Status InOrderTraverse_Thr(BiThrTree T ,Status(* visit)(TElemType e) ){
//T指向頭結點,頭結點的左鏈lchild指向根節點,可參見線索化演算法
//中序遍歷二叉線索樹T的非遞迴演算法,對每個資料元素呼叫函式visit
BiThrTree p ;
p = T->lchild; // p指向根節點
while(p != T){ //空樹 或者遍歷結束時 p == T
while(p->LTag == Link ) // 走到最左結點
p = p->lchild;
visit(p->data);
while(p->RTag == Thread && p->rchild !=T){
p = p->rchild ; // 若有右線索,
visit(p->data);
}
p = p->rchild;
}
return OK;
}
int main()
{
BiThrTree T, inorderT;
printf("建立樹\n");
CreatBiThrTree(T);
printf("\n中序遍歷線索二叉樹\n");
InOrderThreading(inorderT , T);
InOrderTraverse_Thr(inorderT , visit);
printf("\n");
return 0;
}
(2)前序遍歷,線索二叉樹
【前序遍歷二叉樹線索化】
BiThrTree pre; //全域性變數,始終指向剛剛訪問過的結點。
void PreThreading(BiThrTree p){
if(p){
if(!p->lchild){ //沒有左孩子
p->LTag = Thread; //前驅線索
p->lchild = pre; //左孩子指標指向前驅
}
if(!pre->rchild && pre){
pre->RTag = Thread; //後繼線索
pre->rchild = p ; //前驅右孩子指標指向後繼
}
pre = p;
if(p->LTag == Link)
PreThreading(p->lchild); //左子樹線索化
if(p->RTag == Link)
PreThreading(p->rchild); //右子樹線索化
}
}
【建立頭結點】(和中序遍歷一樣)//建立頭結點,前序線索二叉樹
Status PreOrderThreading(BiThrTree &Thrt,BiThrTree T){
//前序遍歷二叉樹T,並將其前序線索化,Thrt指向頭結點。
if(!(Thrt = (BiThrTree)malloc(sizeof(BiThrNode))))
return ERROR;
Thrt->RTag = Thread; //建頭結點
Thrt->rchild = Thrt ; //右指標回指
Thrt->LTag = Link;
if(!T){
Thrt->lchild = Thrt;
}else{
Thrt->lchild = T;
pre = Thrt ;
PreThreading(T);
pre->rchild = Thrt ;
pre->RTag = Thread;
Thrt->rchild = pre;
}
return OK;
}
↓ ← 【 空的頭結點 】 ← ← ← ← ← ← ← ↑
A → B → D → H → I → E → J → C → F → G
1、A的直接前驅
㈠若LTag 的值為1,那麼LChild 所指結點就是直接前驅
㈡若LTag 的值為0,那麼
⒈若A為雙親左兒子,那麼直接前驅就是A的雙親結點
⒉若A為雙親右兒子,那麼直接前驅就是A的雙親左兒子
2、A的直接後繼
㈠若RTag 的值為1,那麼RChild 所指結點就是直接後繼
㈡若RTag 的值為0,那麼
⒈若LTag 的值為0,那麼直接後繼就是其左兒子。
⒉若LTag 的值為1,那麼直接後繼就是其右兒子。
【前序遍歷二叉樹】
//前序 遍歷線索二叉樹
Status PreOrderTraverse_Thr(BiThrTree T ,Status(* visit)(TElemType e) ){
//T指向頭結點,頭結點的左鏈lchild指向根節點,可參見線索化演算法
//前序遍歷二叉線索樹T的非遞迴演算法,對每個資料元素呼叫函式visit
BiThrTree p ;
p = T->lchild; // p指向根節點
while(p != T){ //空樹 或者遍歷結束時 p == T
visit(p->data);
if(p->LTag == Link)
p = p->lchild;
else
p = p->rchild;
}
return OK;
}
【總的前序遍歷線索二叉樹程式碼】
#include <iostream>
#include <string.h>
#include <cstdio>
#include <stdlib.h>
using namespace std;
#define Status int
#define OK 1
#define ERROR 0
#define TElemType char
typedef enum{
Link,Thread
}PointerTag;//Link == 0 :指標 ,Thread == 1: 線索
typedef struct BiThrNode{
TElemType data;
struct BiThrNode *lchild, *rchild; //左右孩子指標
PointerTag LTag , RTag; //左右標誌
}BiThrNode, *BiThrTree;
//char Vexch[20]={'H','D','A','$','$','C','$','B','$','$','G','F','$','E','$','$','$'};
char Vexch[26]={'A','B','D','H','$','$','I','$','$','E','J','$','$','$','C','F','$','$','G','$','$'};
int i=0;
//二叉樹的建立
Status CreatBiThrTree(BiThrTree &T)
{
if(Vexch[i++]=='$') T=NULL;
else
{
T= (BiThrTree)malloc(sizeof(BiThrNode));
if(!T) return 0;
T->data=Vexch[i-1];//生成根節點
printf("%5c",T->data);
T->LTag=Link;
CreatBiThrTree(T->lchild);//建立左子樹
T->RTag=Link;
CreatBiThrTree(T->rchild);//建立右子樹
}
return 1;
}
Status visit(TElemType e){
printf("%5c",e);
return OK;
}
BiThrTree pre; //全域性變數,始終指向剛剛訪問過的結點。
void PreThreading(BiThrTree p){
if(p){
if(!p->lchild){ //沒有左孩子
p->LTag = Thread; //前驅線索
p->lchild = pre; //左孩子指標指向前驅
}
if(!pre->rchild){
pre->RTag = Thread; //後繼線索
pre->rchild = p ; //前驅右孩子指標指向後繼
}
pre = p;
if(p->LTag == Link)
PreThreading(p->lchild); //左子樹線索化
if(p->RTag == Link)
PreThreading(p->rchild); //右子樹線索化
}
}
//建立頭結點,前序線索二叉樹
Status PreOrderThreading(BiThrTree &Thrt,BiThrTree T){
//前序遍歷二叉樹T,並將其前序線索化,Thrt指向頭結點。
if(!(Thrt = (BiThrTree)malloc(sizeof(BiThrNode))))
return ERROR;
Thrt->RTag = Thread; //建頭結點
Thrt->rchild = Thrt ; //右指標回指
Thrt->LTag = Link;
if(!T){
Thrt->lchild = Thrt;
}else{
Thrt->lchild = T;
pre = Thrt ;
PreThreading(T);
pre->rchild = Thrt ;
pre->RTag = Thread;
Thrt->rchild = pre;
}
return OK;
}
//前序 遍歷線索二叉樹
Status PreOrderTraverse_Thr(BiThrTree T ,Status(* visit)(TElemType e) ){
//T指向頭結點,頭結點的左鏈lchild指向根節點,可參見線索化演算法
//前序遍歷二叉線索樹T的非遞迴演算法,對每個資料元素呼叫函式visit
BiThrTree p ;
p = T->lchild; // p指向根節點
while(p != T){ //空樹 或者遍歷結束時 p == T
visit(p->data);
if(p->LTag == Link)
p = p->lchild;
else
p = p->rchild;
}
return OK;
}
int main()
{
BiThrTree T, PreT;
printf("建立樹\n");
CreatBiThrTree(T);
printf("\n前序遍歷線索二叉樹\n");
PreOrderThreading(PreT , T);
PreOrderTraverse_Thr(PreT , visit);
printf("\n");
return 0;
}
(3)後序遍歷,線索二叉樹
【後序遍歷線索二叉樹時,需要一個parent 指標,所以建樹的時候與上面兩個有所不同】
#define TElemType char
typedef enum{
Link,Thread
}PointerTag;//Link == 0 :指標 ,Thread == 1: 線索
typedef struct BiThrNode{
TElemType data;
struct BiThrNode *lchild, *rchild; //左右孩子指標
struct BiThrNode *parent;
PointerTag LTag , RTag; //左右標誌
}BiThrNode, *BiThrTree;
BiThrTree pre; //全域性變數,始終指向剛剛訪問過的結點。
Status visit(TElemType e){
printf("%5c",e);
return OK;
}
//char Vexch[20]={'H','D','A','$','$','C','$','B','$','$','G','F','$','E','$','$','$'};
char Vexch[26]={'A','B','D','H','$','$','I','$','$','E','J','$','$','$','C','F','$','$','G','$','$'};
int i=0;
//二叉樹的建立
Status CreatBiThrTree(BiThrTree &T,BiThrTree &p)
{
if(Vexch[i++]=='$') T=NULL;
else
{
T= (BiThrTree)malloc(sizeof(BiThrNode));
if(!T) return 0;
T->data=Vexch[i-1];//生成根節點
T->parent = p;//指回原來的結點
visit(T->data);
T->LTag=Link;
CreatBiThrTree(T->lchild,T);//建立左子樹
T->RTag=Link;
CreatBiThrTree(T->rchild,T);//建立右子樹
}
return 1;
}
【後序遍歷二叉樹線索化】
void PostThreading(BiThrTree p){
if(p){
PostThreading(p->lchild); //左子樹線索化
PostThreading(p->rchild); //右子樹線索化
if(!p->lchild){ //沒有左孩子
p->LTag = Thread; //前驅線索
p->lchild = pre; //左孩子指標指向前驅
}
if(pre && !pre->rchild){
pre->RTag = Thread; //後繼線索
pre->rchild = p ; //前驅右孩子指標指向後繼
}
pre = p;
}
}
【建立頭結點】(這裡就不建頭結點了,因為入口是總根節點,出口也是總根節點)
1、A的直接前驅
㈠若LTag 的值為1,那麼A的直接前驅為LChild所指結點
㈡若LTag 的值為0,那麼
⒈若有左兒子,那麼直接前驅就是A的左兒子。
⒉若有右兒子,那麼直接前驅就是A的右兒子。
2、A的直接後繼
㈠若結點A是二叉樹的根,則其後繼為空
㈡若結點A是其雙親的右兒子,或是雙親的左孩子且其雙親沒有左子樹沒有右子樹,則其後繼即為雙親結點
㈢若結點A是其雙親的左兒子,且雙親有右子樹,則其後繼為雙親的右子樹上按後序遍歷列出來的第一個結點。
【後序遍歷二叉樹】
//後序 遍歷線索二叉樹
Status PostOrderTraverse_Thr(BiThrTree T ,Status(* visit)(TElemType e) ){
BiThrTree p ;
p = T; // p指向根節點
pre=NULL;
while(p != NULL){ //空樹 或者遍歷結束時 p == T
while(p->LTag == Link ) // 走到最左結點 ||左結點
p = p->lchild;
while(p->RTag == Thread ){ //訪問後繼 ||右結點
visit(p->data);
pre = p;
p = p->rchild ;
}
if(p == T){ //是否是最後根節點
visit(p->data);
break;
}
while(p && p->rchild == pre ){ //訪問根 ||根節點
visit(p->data);
pre = p;
p = p->parent;
}
if(p && p->RTag == Link)
p = p->rchild;
}
return OK;
}
【總的後序遍歷線索二叉樹程式碼】
#include <iostream>
#include <string.h>
#include <cstdio>
#include <stdlib.h>
using namespace std;
#define Status int
#define OK 1
#define ERROR 0
#define TElemType char
typedef enum{
Link,Thread
}PointerTag;//Link == 0 :指標 ,Thread == 1: 線索
typedef struct BiThrNode{
TElemType data;
struct BiThrNode *lchild, *rchild; //左右孩子指標
struct BiThrNode *parent;
PointerTag LTag , RTag; //左右標誌
}BiThrNode, *BiThrTree;
BiThrTree pre; //全域性變數,始終指向剛剛訪問過的結點。
Status visit(TElemType e){
printf("%5c",e);
return OK;
}
//char Vexch[20]={'H','D','A','$','$','C','$','B','$','$','G','F','$','E','$','$','$'};
char Vexch[26]={'A','B','D','H','$','$','I','$','$','E','J','$','$','$','C','F','$','$','G','$','$'};
int i=0;
//二叉樹的建立
Status CreatBiThrTree(BiThrTree &T,BiThrTree &p)
{
if(Vexch[i++]=='$') T=NULL;
else
{
T= (BiThrTree)malloc(sizeof(BiThrNode));
if(!T) return 0;
T->data=Vexch[i-1];//生成根節點
T->parent = p;
visit(T->data);
T->LTag=Link;
CreatBiThrTree(T->lchild,T);//建立左子樹
T->RTag=Link;
CreatBiThrTree(T->rchild,T);//建立右子樹
}
return 1;
}
void PostThreading(BiThrTree p){
if(p){
PostThreading(p->lchild); //左子樹線索化
PostThreading(p->rchild); //右子樹線索化
if(!p->lchild){ //沒有左孩子
p->LTag = Thread; //前驅線索
p->lchild = pre; //左孩子指標指向前驅
}
if(pre && !pre->rchild){
pre->RTag = Thread; //後繼線索
pre->rchild = p ; //前驅右孩子指標指向後繼
}
pre = p;
}
}
//後序 遍歷線索二叉樹
Status PostOrderTraverse_Thr(BiThrTree T ,Status(* visit)(TElemType e) ){
BiThrTree p ;
p = T; // p指向根節點
pre=NULL;
while(p != NULL){ //空樹 或者遍歷結束時 p == T
while(p->LTag == Link ) // 走到最左結點 ||左結點
p = p->lchild;
while(p->RTag == Thread ){ //訪問後繼 ||右結點
visit(p->data);
pre = p;
p = p->rchild ;
}
if(p == T){ //是否是最後根節點
visit(p->data);
break;
}
while(p && p->rchild == pre ){ //訪問根 ||根節點
visit(p->data);
pre = p;
p = p->parent;
}
if(p && p->RTag == Link)
p = p->rchild;
}
return OK;
}
int main()
{
BiThrTree PostT;
printf("建立樹\n");
pre = NULL;
CreatBiThrTree(PostT,pre);
printf("\n後序遍歷線索二叉樹\n");
PostThreading(PostT);
PostOrderTraverse_Thr(PostT , visit);
printf("\n");
return 0;
}
總結:
【問】為什麼用先序遍歷建樹後,可以用來中序遍歷線索化?
【答】先序遍歷建樹,只是一種建樹方式(當然可以用別的方法來建樹,但是數組裡的順序可能就要變化了),建完樹後,跟後面線索化無關。
【問】為什麼中序遍歷,先序遍歷,後序遍歷線上索化的時候,要用不同的線索化?
【答】因為中序,先序,後序,他們的前驅和後繼是不一樣的,根據程式碼也知道是不一樣。
【問】對於做題,畫已知二叉樹的前序、中序、後序線索二叉樹有什麼技巧嗎?
【答】可以先將 二叉樹前序、中序、後序遍歷 順序寫出來。再根據寫出來的順序對二叉樹進行線索化。
【問】接上,線索化的時候這麼亂,不知道線索改連到哪裡?
【答】每個結點左右各有一個指標,除了用於建樹的“藍色”線之外,我們只看紅色的線索這條線。每個結點只要是線索的部分,左邊就是指向排在該結點之前的那個結點,右邊就是指排在該節點之後的那個結點,這也就是為什麼要先把遍歷的順序提前寫好的原因。