理论基础 —— 二叉树 —— 线索链表

【概述】

对于一个有 n 个结点的二叉链表,每个结点指向左右孩子的两个指针域,故共有 2n 个指针域,而 n 个结点的二叉树共有 n-1 条分支,即存在 2n-(n-1)=n+1 个空指针域,白白浪费了资源。

另一方面,在二叉链表上,只能知道每个结点的左右孩子结点的地址,而不知道某个结点的前驱和后继,要想知道,必须对二叉树进行遍历,以后每次想要知道时,都要遍历一次,这无疑浪费了时间。

综合以上两个方面,可以考虑利用空地址,存放指向结点在某种遍历次序下的前驱与后继,将指向前驱与后继的指针称为线索,加上线索的二叉链表称为线索链表,相应的二叉树就称为线索二叉树

【实现类】

线索化的实质就是将二叉链表中的空指针改为指向前驱或后继的线索,因此其存储结构相较于二叉链表有了更改

typedef enum{link,thread} Tag;//link=0表示指向左右孩子指针,thead=1表示指向前驱或后继的线索
template<class T>
struct Node{
    T data;//数据域
    Node<T> *lchild,rchild;//指针域
    Tag ltag,rtag;//标志域
};

对二叉树以某种次序遍历使其变为线索二叉树的过程称为线索化,由于二叉树的遍历分为前序、中序、后序、层序四种,那么相应的,在建立线索二叉树时,依据遍历次序的不同,也同样分为四种:

  • 前序线索二叉树
  • 中序线索二叉树
  • 后序线索二叉树
  • 层序线索二叉树

以上四种线索二叉树只是在线索化时的顺序有所不同,以下以中序线索二叉树为例

template<class T>
class ThreadTree{
public:
    ThreadTree();//构造函数,建立中序线索链表
    ~ThreadTree();//析构函数,释放各结点空间
    Node<T> * findNext(Node<T> *bt);//查找结点bt的后继
    void inOrder();//中序遍历
private:
    Node<T> *root;//指向线索链表的头指针
    Node<T> * creat(Node<T> *bt);//构造函数调用
    void inThread(Node<T> *bt,Node<T> *pre);//构造函数调用
};

【构造函数】

构造函数的功能是建立一个中序线索链表,实质上就是将二叉树中的空指针改为指向前驱或后继的线索,而前驱或后继信息只有在遍历该二叉树时才能得到,因此建立线索链表应先建立一个二叉链表,然后在遍历的过程中修改空指针建立线索链表。

template<class T>
ThreadTree<T>::ThreadTree(){//构造函数
    root=creat(root);//建立带线索标志的二叉链表
    Node<T> *pre=NULL;//当前访问结点的前驱结点
    inThread(root,pre);//遍历二叉链表,建立线索
}
template<class T>
Node<T>* ThreadTree<T>::creat(Node<T> *bt){//建立带线索标志的二叉链表
    char ch;
    cin>>ch;
    if(ch=='#')//生成空树
        bt=NULL;
    else{
        bt=new Node<T>;
        bt->data=ch;
        bt->ltag=link;//左标志
        bt->rtag=link;//右标志
        bt->lchild=creat(bt->lchild);//递归建立左子树
        bt->rchild=creat(bt->rchild);//递归建立右子树
    }
}

template<class T>
void ThreadTree<T>::inThread(Node<T> *bt,Node<T> *pre){//中序线索化
    if(root!=NULL){
        inThread(bt->lchild,pre);//递归左子树线索化
        if(bt->lchild!=NULL){//没有左孩子
            bt->ltag=thread;//前驱线索
            bt->lchild=pre;//左孩子指针执行前驱
        }
        if(pre->rchild!=NULL){//没有右孩子
            pre->rtag=thread;//后继线索
            pre->rchild=bt;//右孩子指向后继
        }
        pre=bt;//保持pre指向bt的前驱
        inThread(bt->rchild,pre);//递归右子树线索化
    }
}

【查找后继结点】

首先判断当前结点是否已有线索,若已有,则可直接得到后继,若没有,则要寻找当前结点的右孩子的最左下结点

template<class T>
Node<T> * ThreadTree<T>::findNext(Node<T> *bt){//查找结点bt的后继
    Node<T> *q;//后继
    if(bt->rtag==thread)//已有线索,直接得到后继结点
        q=bt->rchild;
    else{
        q=bt->rchild;//工作指针q指向bt的右孩子
        while(q->ltag==link)//查找最左下结点
            q=q->lchild;
    }
    return q;
}

【遍历操作】

对线索链表进行中序遍历操作时,只要寻找到中序序列的第一个结点,然后依次访问其后继即可

template<class T>
void ThreadTree<T>::inOrder(){//中序遍历
    if(root==NULL)
        return;
    else{
        Node<T> *p;
        p=root;
        while(p->ltag==link)//寻找中序遍历序列的第一个结点
            p=p->lchild;
        cout<<p->data;
        while(p->rchild!=NULL){//当结点p存在后继,依次访问其后继
            p=findNext(p);
            cout<<p->data;
        }
    }
}

 

  • 9
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值