1.中序線索二叉樹
數據結構:
typedef struct Node{ struct Node* l=NULL; struct Node* r=NULL; int lt=0,rt=0;//left tag, right tag,如果為 1 表示左(右)結點不存在,為前驅(后繼) ElemType data; Node(ElemType data=0):data(data){ } }Node;
首先理解如何建立中序線索化二叉樹。如果結點的左子樹存在,lt為0 。不存在為1;右子樹同理。直接對根節點進行中序遍歷,在不存在的場合設置標志位和前驅、后繼。
把二叉樹看成中序遍歷序列,序列的第一個結點(最左下結點)的前驅為NULL,最后一個結點(最右下結點)的后繼為NULL。通過設置pre的初始化為NULL和最后對pre(指向最后一個結點)的后繼(右子樹)設置為NULL來實現。
建立中序線索化二叉樹代碼:(注釋為坑點)
void InThread(Node* p,Node* &pre){ if(p) { InThread(p->l,pre); if(p->l==NULL){ //左結點不存在 p->lt=1; //設置 p 的前驅 p->l=pre; } //注意加上pre非空的短路判斷條件,避免空指針操作 if(pre!=NULL && pre->r==NULL){ //右結點不存在 pre->r=p; //設置 pre 的后繼 pre->rt=1; } pre=p; //前驅結點更新 InThread(p->r,pre); } }
然后我們來看如何遍歷。對於一個結點,首先找到他的首結點(最左下結點),然后每次循環后,尋找這個結點的后繼。這個過程可以用一個for循環進行抽象:
void InOrderTraversal(Node* root){ //中序線索化遍歷 for(Node* p=First(root) ; p!=NULL ; p=Next(p)){ visit(p); } }
對於尋找首結點,我們用First函數來完成:
Node* First(Node* p) {//返回 p 結點的最左下結點 (不一定是葉子節點) while(p->lt==0) {//保證p結點一定存在左子樹 p=p->l; } return p; }
注意到p結點是一定要存在左子樹的。如果不存在左子樹,左子樹結點l指向的是p結點的前驅,這樣就會亂套。
對於尋找后繼結點,我們用Next函數來完成:
Node* Next(Node* p){ if(p->rt==0){ //p 節點存在右子樹 return First(p->r); //移動到最左下結點 }else{ //不存在左子樹 return p->r; //移動到后繼結點 } }
翻譯成自然語言就是,如果這個結點的右子樹存在,返回右子樹的最左下結點(中序遍歷首結點),如果不存在,返回這個結點的后繼結點。
完整代碼:

#include<iostream> #define ElemType char #define MaxSize 100 using namespace std; typedef struct Node{ struct Node* l=NULL; struct Node* r=NULL; int lt=0,rt=0;//left tag, right tag,如果為 1 表示左(右)結點不存在,為前驅(后繼) ElemType data; Node(ElemType data=0):data(data){ } }Node; Node* buildSampleTree(){//樣例樹形結構來自天勤 2019 版 142 頁 Node* nd=new Node('A'); nd->l= new Node('B'); nd->r= new Node('G'); nd->l->l= new Node('C'); nd->l->r= new Node('D'); nd->r->l= new Node('H'); nd->l->r->l= new Node('E'); nd->l->r->r= new Node('F'); return nd; } void visit(Node* p){ cout<<p->data<<' '; } void InThread(Node* p,Node* &pre){ if(p) { InThread(p->l,pre); if(p->l==NULL){ //左結點不存在 p->lt=1; //設置 p 的前驅 p->l=pre; } //注意加上pre非空的短路判斷條件,避免空指針操作 if(pre!=NULL && pre->r==NULL){ //右結點不存在 pre->r=p; //設置 pre 的后繼 pre->rt=1; } pre=p; //前驅結點更新 InThread(p->r,pre); } } void createInThread(Node* root){ //對根節點進行中序線索化 Node * pre=NULL; //第一個結點的前驅為 0 if(root!=NULL){ InThread(root,pre); pre->r=NULL;//最后一個結點的后繼為 0 pre->rt=1; } } Node* First(Node* p) {//返回 p 結點的最左下結點 (不一定是葉子節點) while(p->lt==0) {//保證p結點一定存在左子樹 p=p->l; } } Node* Next(Node* p){ if(p->rt==0){ //p 節點存在右子樹 return First(p->r); //移動到最左下結點 }else{ //不存在左子樹 return p->r; //移動到后繼結點 } } void InOrderTraversal(Node* root){ //中序線索化遍歷 for(Node* p=First(root) ; p!=NULL ; p=Next(p)){ visit(p); } } int main(){ Node *root=buildSampleTree(); createInThread(root); InOrderTraversal(root); }
1.前序線索二叉樹
前序線索化代碼:
void PreThread(Node* p,Node* &pre){ if(p) { if(p->l==NULL){ //左結點不存在 p->lt=1; //設置 p 的前驅 p->l=pre; } //注意加上pre非空的短路判斷條件,避免空指針操作 if(pre!=NULL && pre->r==NULL){ //右結點不存在 pre->r=p; //設置 pre 的后繼 pre->rt=1; } pre=p; //前驅結點更新 if(p->lt==0) //左子樹存在 PreThread(p->l,pre); if(p->lt==0) //右子樹存在 PreThread(p->r,pre); } }
注意到遞歸語句加上了判斷條件。這是因為在中序的時候,對左或右子樹不存在的結點進行操作的語句放在中間,對於空結點進行的遞歸操作會被非空判斷退出。而到了前序,則是先對左或右子樹不存在的結點進行操作,再運行遞歸語句。則左右子樹即使是空結點,也會被線索化操作賦值(指向前驅或后繼),這樣就會造成死循環。
前序線索樹遍歷代碼:
void PreOrderTraversal(Node* root){ //中序線索化遍歷 if(root==NULL) return; Node* p=root; while(p){ while(p->lt==0){ visit(p); p=p->l; } visit(p); //此時 p 必為前驅結點的后繼(線索),但還沒有訪問到,訪問。 p=p->r; //指向 p 的右子樹 或 后繼 } }
對於p結點不斷的往左下角遍歷,如果遍歷到一個結點,他的左子樹不存在了,退出循環。退出循環后,先對當前結點進行遍歷(不能漏掉),然后指向其r結點(如果右子樹存在,就是右子樹。如果右子樹不存在,就是后繼)。
完整代碼:

#include<iostream> #define ElemType char #define MaxSize 100 using namespace std; typedef struct Node{ struct Node* l=NULL; struct Node* r=NULL; int lt=0,rt=0;//left tag, right tag,如果為 1 表示左(右)結點不存在,為前驅(后繼) ElemType data; Node(ElemType data=0):data(data){ } }Node; Node* buildSampleTree(){//樣例樹形結構來自天勤 2019 版 142 頁 Node* nd=new Node('A'); nd->l= new Node('B'); nd->r= new Node('G'); nd->l->l= new Node('C'); nd->l->r= new Node('D'); nd->r->l= new Node('H'); nd->l->r->l= new Node('E'); nd->l->r->r= new Node('F'); return nd; } void visit(Node* p){ cout<<p->data<<' '; } void PreThread(Node* p,Node* &pre){ if(p) { if(p->l==NULL){ //左結點不存在 p->lt=1; //設置 p 的前驅 p->l=pre; } //注意加上pre非空的短路判斷條件,避免空指針操作 if(pre!=NULL && pre->r==NULL){ //右結點不存在 pre->r=p; //設置 pre 的后繼 pre->rt=1; } pre=p; //前驅結點更新 if(p->lt==0) //左子樹存在 PreThread(p->l,pre); if(p->lt==0) //右子樹存在 PreThread(p->r,pre); } } void createPreThread(Node* root){ //對根節點進行中序線索化 Node * pre=NULL; //第一個結點的前驅為 0 if(root!=NULL){ PreThread(root,pre); pre->r=NULL;//最后一個結點的后繼為 0 pre->rt=1; } } void PreOrderTraversal(Node* root){ //中序線索化遍歷 if(root==NULL) return; Node* p=root; while(p){ while(p->lt==0){ visit(p); p=p->l; } visit(p); //此時 p 必為前驅結點的后繼(線索),但還沒有訪問到,訪問。 p=p->r; //指向 p 的右子樹 或 后繼 } } int main(){ Node *root=buildSampleTree(); createPreThread(root); PreOrderTraversal(root); }