二叉樹的線索化及其遍歷(必會)


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);
}
View Code

 


 

 

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);
}
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM