我这么讲线索二叉树,我三岁大的表弟笑了笑

目录
1.线索二叉树的由来
2.线索二叉树的概念和结构
3.二叉树的线索化
4.中序线索二叉树的代码实现
5.遍历线索二叉树

1.线索二叉树的由来

我问表弟下面的一个问题:

在n个结点一个二叉链表中,有多少个空指针域?

表弟一脸蒙蔽,于是我解释道:既然有n个结点,那么总共应该有2n个指针域,我们的根结点是无结点指向的,其他的结点肯定都有一个结点指向,也就是说一共用了n-1个指针域(根结点无结点指向所以需要减1),那么也就是说还有n+1个指针域没有使用,由此我们可以得出一个结论:

任何一颗含n个结点的二叉树都有n+1个指针域是空指针域

那么这么多的空指针域造成浪费 ,表弟你心疼不,表弟:管我屁事
我:
在这里插入图片描述

为了解决这些浪费的空指针域,我们引进线索二叉树

2.线索二叉树的概念和结构

2.1线索二叉树的概念

表弟:你给我讲讲线索二叉树呗
我:你以为我和你搞着玩?讲就讲come on baby

线索二叉树实际上就是使用这些空指针域来存储结点之间前趋和后继关系的一种特殊的二叉树。线索二叉树中,如果结点有左子树,则lchild 指针域指向左孩子,否则lchild 指针域指向该结点的直接前趋;同样,如果结点有右子树,则rchild 指针域指向右孩子,否则rchild 指针域指向该结点的直接后继。
在这里插入图片描述

LTag 和RTag 为标志域。实际上就是两个布尔类型的变量:
LTag 值为0 时,表示lchild 指针域指向的是该结点的左孩子;为1 时,表示指向的是该结点的直接前趋结点;
RTag 值为0 时,表示rchild 指针域指向的是该结点的右孩子;为1 时,表示指向的是该结点的直接后继结点。

2.2线索二叉树的结构代码实现

表弟:你这讲的花里胡哨的,裸敲个线索二叉树的结构代码我看看
我:你这是质疑你表哥我的实力啊,上DEVC++
在这里插入图片描述

#define TElemType int//定义元素的数据类型
typedef struct  BiThrNode{//线索二叉树那稳的是链式结构
	TElemType data;
	struct BiThrNode *lchild,*rchild;
	int LTag;
	int Rtag;
}BiThrNode,*BiThrTree;

3.二叉树的线索化

表弟:那到底怎样建立一个线索二叉树呢
我:别急啊,听我慢慢说

将二叉树转化为线索二叉树,实质上是在遍历二叉树的过程中,将二叉链表中的空指针改为指向直接前趋或者直接后继的线索
  线索化的过程即为在遍历的过程中修改空指针的过程。在遍历过程中,如果当前结点没有左孩子,需要将该结点的lchild 指针指向遍历过程中的前一个结点,所以在遍历过程中,设置一个指针(名为pre ),时刻指向当前访问结点的前一个结点。

表弟:空指针域里存放这些前驱和后继有啥用,仅仅就是为了不浪费,但是存放了没有用的东西和浪费不是差不多吗
我:这个问题问的好,其实存放前驱和后继的意义是:

线索二叉树的建立过程其实就是提高遍历二叉树效率的过程

表弟:这话我TM听不懂,说人话
我:卧槽你个**,多大点说对表哥我说脏话,你这是上天啊
在这里插入图片描述
表弟:可不是要上天,你赶紧讲讲刚那句话啥意思
我:其实你想,我们根据普通的二叉树的某一个结点是不是只能找到它的左孩子和右孩子,我们想要知道某一个结点的前驱和后继,是不是就要重新遍历整个二叉树,也就是说我们每一次想要知道某一个结点的前驱和后继,都要遍历一遍整个二叉树,但是我们如果创建的是线索二叉树,那么它的空指针域里就存放它的前驱和后继的信息,遍历起来就特别的方便,就不需要遍历整棵二叉树,就大大提高了遍历的效率。

注意我们以不同的方式遍历二叉树它的前驱和后继也就不同,所以线索二叉树又分为前序线索二叉树,中序线索二叉树,后续线索二叉树

其实我们构建一颗线索二叉树的过程其实就是,等同是把一颗二叉树变成一个双向链表的过程,这怎么理解呢,下面是一颗二叉树的模型
在这里插入图片描述

我们中序遍历利用指向右孩子的空指针(指向后继)
在这里插入图片描述

再利用指向左孩子的空指针(指向前驱)
在这里插入图片描述
联合起来就变成如下图(是不是类似一个双向链表)
在这里插入图片描述

在这里插入图片描述

4.详解中序线索二叉树

表弟:那怎么用代码实现遍历过程中让一颗二叉树变成线索二叉树呢
我:我教你用遍历中序二叉树的方法构造一个中序线索二叉树,看下面一段代码

BiThrTree pre;//所以在遍历过程中,设置一个全局变量pre,时刻指向当前访问结点的前一个结点。
void InThreading(BiThrTree p)//以p为根的子树中序线索化 
{
    //如果当前结点存在
    if (p)
    {
        //递归当前结点的左子树,进行线索化
        InThreading(p->lchild); 
        //如果当前结点没有左孩子,左标志位设为1,左指针域指向上一结点pre
        if (!p->lchild)//p的左孩子为空 
        {
             //前驱线索
            p->Ltag =1;
             //左孩子指针指向前驱
            p->lchild = pre; 
        }
        else
		p->LTag=0; 
        //如果pre 没有右孩子,右标志位设为1,右指针域指向当前结点。
        if (!pre->rchild)
        {
            //后继线索
            pre->Rtag =1;
            //前驱右孩子指针指向后继(当前结点p)
            pre->rchild =p;
        }
        else 
        pre->RTag=0;
        pre = p;                //pre 指向当前结点
        InThreading(p->rchild); //递归右子树进行线索化
    }
}

还有一种情况,那就是带头结点的中序线索二叉树的构建方法,那么什么是带头结点的二叉树呢
我们还拿这颗二叉树来说:
在这里插入图片描述

把上述二叉树转变成线索二叉树后会发现H结点无前驱,G结点无后继,这时候引入一个头结点

在这里插入图片描述

那么我们怎样线索化带头结点的线索二叉树呢,看下面这一段代码

//建立头指针,使其左指针指向根结点,右指针指向遍历的最后一个结点
void InOrder_Thr(BiThrTree &Thr,BiThrTree T)//中序遍历二叉树T,并将其中序线索化,Thr为头指针指向头结点 
{
    Thr=new BiThrNode; //建立一个头结点 
    Thr->LTag=0;//头结点有左孩子若树非空那么左孩子指向根结点 
    Thr->RTag=1;//右孩子指针为右索引 
    Thr->rchild=Thr;    //初始化的时候右指针指向自己 
    if(!T)//如果树为空那么初始化的时候指针也指向自己 
        Thr->lchild=Thr;
    else
    {
        Thr->lchild=T;//头结点左孩子指向根,pre初始值为头结点 
        InThreading(T);//对以T为根的二叉树进行线索化 
        pre->rchild=Thr;//中序线索化后pre为遍历的最后结点 ,让最后的结点的右结点指向头结点 
	    pre->RTag=1;
        Thr->rchild=pre;//头结点的右索引指向pre 
    }
}

5.遍历线索二叉树

下图中是一个按照中序遍历建立的线索二叉树。其中,实线表示指针,指向的是左孩子或者右孩子。虚线表示线索,指向的是该结点的直接前趋或者直接后继。
在这里插入图片描述
  使用线索二叉树时,会经常遇到一个问题,如上图中,结点8的直接后继直接通过指针域获得,为结点5;而由于结点5的度为2 ,无法利用指针域指向后继结点,整个链表断掉了。当在遍历过程,遇到这种问题是解决的办法就是:寻找先序、中序、后序遍历的规律,找到下一个结点。

   在先序遍历过程中,如果结点因为有右孩子导致无法找到其后继结点,如果结点有左孩子,则后继结点是其左孩子;否则,就一定是右孩子。拿上图举例,结点2的后继结点是其左孩子结点4 ,如果结点4不存在的话,就是结点5 。
  在中序遍历过程中,结点的后继是遍历其右子树时访问的第一个结点,也就是右子树中位于最左下的结点。例如上图中结点5,后继结点为结点11,是其右子树中位于最左边的结点。反之,结点的前趋是左子树最后访问的那个结点。
  后序遍历中找后继结点需要分为3 种情况:
  1. 如果该结点是二叉树的根,后继结点为空;
  2. 如果该结点是父结点的右孩子(或者是左孩子,但是父结点没有右孩子),后继结点是父结点;
  3. 如果该结点是父结点的左孩子,且父结点有右子树,后继结点为父结点的右子树在后序遍历列出的第一个结点。
  使用后序遍历建立的线索二叉树,在真正使用过程中遇到链表的断点时,需要访问父结点,所以在初步建立二叉树时,宜采用三叉链表做存储结构。

表弟:这么多,劝退?
我:我们重点掌握中序的就行,下面我实现一下中序线索二叉树的遍历的非递归代码你看看

void InOrderTraverse_Thr(BiThrTree T)
{
	//T为头指针,头结点的左链lchild指向根结点
    BiThrTree p=T->lchild;//p指向根结点 
	while(p!=T)//空树或者遍历结束时p==T 
	{
		while(p->Ltag==0)
		p=p->lchild;//沿着左孩子向下 
		cout<<p->data;//访问左子树为空的结点 
		while(p->Rtag==1&&p->rchild!=T)
		{
			p=p->rchild;//沿着右线索访问后继结点 
			cout<<p->data;
		}
		p=p->rchild;//转向p的右子树 
		
	} 
}

结合下面图一起看,下面图的中序遍历结果时4 2 8 5 11 9 12 1 6 3 7
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_45737068/article/details/106648025