目录
一.线索二叉树的定义和由来
我们都知道,二叉树的数据结构一般由节点数据,左孩子和右孩子组成:
typedef struct BiNode{
char data;
BiThrNode *lchild, *rchild;
} BiThrNode, *BiThrTree;
因为叶子节点的左孩子和右孩子为空(NULL),会造成内存的浪费,因此前辈发明了线索二叉树,它与二叉树的区别是当左孩子为空是,就设左孩子指向前驱节点;当右孩子为空时,就设右孩子指向后继节点;同时为了指明什么时候左右孩子指向的是前驱后继,什么时候指向的是左右孩子,就加入了两个新成员变量,分别说明左右孩子指向的是前驱后继还是就是左右孩子:
enum PointerNag{Link, Thread};
typedef struct BiThrNode{
char data;
BiThrNode *lchild, *rchild;
PointerNag ltag, rtag;
} BiThrNode, *BiThrTree;
线索二叉树优点:1.充分利用内存空间;2.线索二叉树的出现,使得遍历二叉树不需要使用递归这种需要多次重复调用递归函数造成时间和空间的浪费,因为递归的每次调用需要保存参数,返回地址以及临时变量,而且往栈里面压入数据和弹出都需要时间。
二.线索二叉树的构建
我们先用一般的前序或者中序等法则建立一个普通二叉树(叶子节点的左右孩子直接设为NULL,线索二叉树构建阶段可以重新设置)。
将先前建立好的二叉树改成线索二叉树,为了方便讲解,直接上代码(一般用的是中序遍历):
void InThreading(BiThrTree T)
{
if (T)
{
InThreading(T->lchild);//步骤1
if (T->lchild == NULL) //步骤2
{
T->ltag = Thread; //a
T->lchild = pre; //b
}
if (pre->rchild == NULL)
{
pre->rchild = T; //c
pre->rtag = Thread;//d
}
pre = T;
InThreading(T->rchild);//步骤3
}
}
步骤1和步骤3是中序遍历的一般递归过程。
对于步骤2,是线索二叉树构建过程中最重要的过程:首先,判断该节点的左孩子是否为空,当为空时,(a)设置该节点的左孩子类型为指向前驱后继节点;(b)紧接着设置左孩子指向前驱节点。其次,判断前驱节点的右孩子是否为空(因为在遍历前驱节点的时候,还不知道要访问的后继节点是什么,所以一般是在后继节点中设置前驱节点的右孩子指向),当为空时,(c)设置前驱节点的右孩子类型为指向前驱后继节点;(d)紧接着设置右孩子指向后继节点。最后,再设置全局变量pre指向当前节点,以便后继节点知道它前驱节点是谁。
三.循环遍历线索二叉树
为了中序循环遍历线索二叉树,我们注意到,以上线索二叉树的建立过程对于最后一个节点的后继节点(右节点)并未进行设置,所以为空,因此我们可以以是否为空来判断线索二叉树是否到达末尾(解释见注释):
void InOrderTraverse(BiThrTree &p)
{
BiThrTree T = p;
while (T != NULL)//循环停止条件
{
//由于是中序遍历,因此一直调用左节点直到左节点为前驱节点停止
while (T->ltag != Thread)
T = T->lchild;
cout << T->data;//输出当前节点
//如果右节点是后继节点,则直接输出右节点再访问右节点的后继节点,直到右节点指向的不是后继节点
while (T->rtag == Thread&&T->rchild != NULL)
{
T = T->rchild;
cout << T->data;
}
T = T->rchild;//调用右节点(为NULL或者右节点不是后继节点)
}
}
四.运行结果
本程序使用前序遍历建立二叉树,其中二叉树结构如下图所示。
运行结果如下所示(其中'&'表示左右孩子节点为NULL):