二叉树的线索化及其遍历(必会)

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
原文地址:https://www.cnblogs.com/TQCAI/p/8869884.html