详解线索二叉树

在阅读这篇文章之前,你要知道如何求给定二叉树的先序序列、中序序列以及后序序列。如果对这部分内容还有疑惑,请阅读我之前写的有关二叉树遍历的文章:二叉树的遍历(先序、中序、后序、层次)

一、线索二叉树的概念

遍历二叉树是以一定的规则将二叉树中的结点排列成一个线性序列,从而得到几种遍历序列,使得该序列中的每个结点(第一个和最后一个除外)都有一个直接前驱和直接后继。

如果将一棵含有n个结点的二叉树存储在二叉链表中,那么该链表总共有2n个链域,实际上,只有n-1条链域被使用(n个结点的二叉树有n-1条边),因此会存在n+1个空指针。那么能否利用这些空指针来存放指向其遍历序列中前驱或后继的指针?引入线索二叉树正是为了加快查找结点在遍历序列中的前驱和后继的速度。


简记为“0孩1驱”

线索二叉树的存储结构描述如下:

typedef struct ThreadNode{
    ElemType data;//数据元素
    struct ThreadNode *leftChild, *rightChild;//左右孩子指针
    int ltag, rtag;//左右线索标志
}ThreadNode, *ThreadTree;

以这种结点结构构成的二叉链表作为二叉树的存储结构,称为线索链表,其中指向结点前驱和后继的指针称为线索。加上线索的二叉树称为线索二叉树。二叉树的线索化是将二叉链表中的空指针改为指向前驱或后继的线索,而前驱或后继的信息只有在遍历时才能得到,因此线索化的实质就是遍历一次二叉树。

例题:

A

C

C

C

二、中序线索二叉树

1、中序线索二叉树的构造:

以上图的二叉树的建立为例,设置一个指针pre指向中序遍历过程中刚刚访问过的结点,指针p指向中序遍历过程中正在访问的结点,即pre指向p的中序前驱。此时,检查p的左指针是否为空,若为空就将它指向pre,检查pre的右指针是否为空,若为空就将它指向p,如下图所示。

通过中序遍历构造中序线索二叉树的代码如下:

void InThread(ThreadTree &p, ThreadTree &pre){//通过中序序列对二叉树进行线索化(递归)
    if(p != NULL){
        InThread(p->leftChild, pre);//线索化左子树(递归)
        if(p->leftChild == NULL){//如果当前结点的左子树为空
            p->leftChild = pre;//建立当前结点的前驱线索
            p->ltag = 1;
        }
        if(pre != NULL && pre->rightChild == NULL){//如果前驱结点非空且其右子树为空
            pre->rightChild = p;//建立前驱结点的后继线索
            pre->rtag = 1;
        }
        pre = p;//标记当前结点成为刚刚访问过的结点
        InThread(p->rightChild, pre);//线索化右子树(递归)
    }
}

void CreateInThread(ThreadTree T){//构造中序线索二叉树的主过程
    ThreadTree pre = NULL;
    if(T != NULL){//若二叉树非空
        InThread(T, pre);//线索化二叉树
        pre->rightChild = NULL;//处理遍历的最后一个结点的后继指针
        pre->rtag = 1;
    }
}

为了方便,可以在二叉树的线索链表上添加一个头指针,令其leftChild域的指针指向二叉树的根结点,其rightChild域的指针指向中序遍历时访问的最后一个结点;令二叉树中序序列中的第一个结点的leftChild域的指针和最后一个结点的rightChild域的指针均指向头结点。这好比为二叉树建立了一个双向线索链表,方便从前往后或从后往前对线索二叉树进行遍历,如下图所示。

2、中序线索二叉树的遍历:

中序线索二叉树的结点中隐含了线索二叉树的前驱和后继信息。在对其进行遍历时,只要先找到序列中的第一个结点,然后依次找结点的后继,直至其后继为空。

在中序线索二叉树中找结点后继的规律是:若其rtag=1,则右链为线索,指示其后继,否则遍历右子树中第一个访问的结点(右子树中最左下的结点)为其后继。

在中序线索二叉树中找结点前驱的规律是:若其ltag=1,则左链为线索,指示其前驱,否则遍历左子树中最后一个访问的结点(左子树中最右下的结点)为其前驱。

不含头结点的中序线索二叉树的遍历算法如下

1)求中序线索二叉树的中序序列下的第一个结点:

ThreadNode *FirstNode(ThreadNode *p){
    while(p->ltag == 0){
        p = p->leftChild;//最左下结点(不一定是叶结点)
    }
    return p;
}

2)求中序线索二叉树的中序序列下的最后一个结点:

ThreadNode *LastNode(ThreadNode *p){
    while(p->rtag == 0){
        p = p->rightChild;//最右下结点(不一定是叶结点)
    }
    return p;
}

3)求中序线索二叉树中结点p在中序序列下的后继结点:

ThreadNode *NextNode(ThreadNode *p){
    if(p->rtag == 0){
        return FirstNode(p->rightChild);//右子树中最左下结点
    }
    else{//若rtag==1则直接返回后继线索
        return p->rightChild;
    }
}

4)求中序线索二叉树中结点p在中序序列下的前驱结点:

ThreadNode *PreNode(ThreadNode *p){
    if(p->ltag == 0){
        return FirstNode(p->leftChild);//左子树中最右下结点
    }
    else{//若ltag==1则直接返回后继线索
        return p->leftChild;
    }
}

5)不含头结点的中序线索二叉树的中序遍历算法:

void InOrder(ThreadNode *T){
    for(ThreadNode *p = FirstNode(T); p != NULL; p = NextNode(p)){
        visit(p);
    }
}

6)中序线索二叉树的逆向中序遍历算法:

void RevInOrder(ThreadNode *T){
    for(ThreadNode *p = LastNode(T); p != NULL; p = PreNode(p)){
        visit(p);
    }
}

3、例题:

D

C

D【2014统考真题】

三、先序线索二叉树

1、先序线索二叉树的构造:

以上图的二叉树的建立为例,设置一个指针pre指向先序遍历过程中刚刚访问过的结点,指针p指向先序遍历过程中正在访问的结点,即pre指向p的先序前驱。此时,检查p的左指针是否为空,若为空就将它指向pre,检查pre的右指针是否为空,若为空就将它指向p,如下图所示。

通过先序遍历构造先序线索二叉树的代码如下:

void PreThread(ThreadTree &p, ThreadTree &pre){//通过先序序列对二叉树进行线索化(递归)
    if(p != NULL){
        if(p->leftChild == NULL){//如果当前结点的左子树为空
            p->leftChild = pre;//建立当前结点的前驱线索
            p->ltag = 1;
        }
        if(pre != NULL && pre->rightChild == NULL){//如果前驱结点非空且其右子树为空
            pre->rightChild = p;//建立前驱结点的后继线索
            pre->rtag = 1;
        }
        pre = p;//标记当前结点成为刚刚访问过的结点
        PreThread(p->leftChild, pre);//线索化左子树(递归)
        PreThread(p->rightChild, pre);//线索化右子树(递归)
    }
}

void CreatePreThread(ThreadTree T){//构造先序线索二叉树的主过程
    ThreadTree pre = NULL;
    if(T != NULL){//若二叉树非空
        PreThread(T, pre);//线索化二叉树
        pre->rightChild = NULL;//处理遍历的最后一个结点的后继指针
        pre->rtag = 1;
    }
}

2、先序线索二叉树的遍历:

先序线索二叉树的结点中隐含了线索二叉树的前驱和后继信息。在对其进行遍历时,只要先找到序列中的第一个结点,然后依次找结点的后继,直至其后继为空。

在先序线索二叉树中找结点后继的规律是:当rtag=1时,右链为线索,指示其后继;当rtag=0时(必有右孩子),若结点有左孩子,则左孩子为先序后继,若结点没有左孩子,则右孩子为先序后继。

在先序线索二叉树中找结点前驱的规律是:当ltag=1时,左链为线索,指示其前驱;当ltag=0时(必有左孩子),则有以下四种情况。

在先序遍历中,左右子树中的结点只可能是根的后继,不可能是根的前驱。

3、例题:

D

四、后序线索二叉树

1、后序线索二叉树的构造:

以上图的二叉树的建立为例,设置一个指针pre指向后序遍历过程中刚刚访问过的结点,指针p指向后序遍历过程中正在访问的结点,即pre指向p的后序前驱。此时,检查p的左指针是否为空,若为空就将它指向pre,检查pre的右指针是否为空,若为空就将它指向p,如下图所示。

通过后序遍历构造后序线索二叉树的代码如下:

void PostThread(ThreadTree &p, ThreadTree &pre){//通过后序序列对二叉树进行线索化(递归)
    if(p != NULL){
        PostThread(p->leftChild, pre);//线索化左子树(递归)
        PostThread(p->rightChild, pre);//线索化右子树(递归)
        if(p->leftChild == NULL){//如果当前结点的左子树为空
            p->leftChild = pre;//建立当前结点的前驱线索
            p->ltag = 1;
        }
        if(pre != NULL && pre->rightChild == NULL){//如果前驱结点非空且其右子树为空
            pre->rightChild = p;//建立前驱结点的后继线索
            pre->rtag = 1;
        }
        pre = p;//标记当前结点成为刚刚访问过的结点
    }
}

void CreatePostThread(ThreadTree T){//构造后序线索二叉树的主过程
    ThreadTree pre = NULL;
    if(T != NULL){//若二叉树非空
        PostThread(T, pre);//线索化二叉树
        pre->rightChild = NULL;//处理遍历的最后一个结点的后继指针
        pre->rtag = 1;
    }
}

2、后序线索二叉树的遍历:

后序线索二叉树的结点中隐含了线索二叉树的前驱和后继信息。在对其进行遍历时,只要先找到序列中的第一个结点,然后依次找结点的后继,直至其后继为空。

在后序线索二叉树中找结点前驱的规律是:当ltag=1时,左链为线索,指示其前驱;当ltag=0时(必有左孩子),若结点有右孩子,则右孩子为后序前驱,若结点没有右孩子,则左孩子为后序前驱。

在后序线索二叉树中找结点后继的规律是:当rtag=1时,右链为线索,指示其后继;当rtag=0时(必有右孩子),则有以下四种情况。

在后序遍历中,左右子树中的结点只可能是根的前驱,不可能是根的后继。

可见在后序线索二叉树上找后继时需要知道结点双亲,即需要采用带标志域的三叉链表作为存储结构。

3、例题:

D

C

A

D【2010统考真题】

  • 10
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值