zoukankan      html  css  js  c++  java
  • 二叉树的线索化及其遍历(必会)

    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
  • 相关阅读:
    数据预处理之中心化(零均值化)与标准化(归一化)
    机器学习入门篇——感知器
    JAVA实现QQ聊天气泡
    设计模式学习笔记—策略模式
    JComboBox实现时间控件
    JTree实现QQ好友列表
    JAVA实现带图片的列表——JList
    JAVA与数据库MySQL相连接
    SQL语句
    HTML学习笔记——标签(二)
  • 原文地址:https://www.cnblogs.com/TQCAI/p/8869884.html
Copyright © 2011-2022 走看看