一、线索二叉树的概念
在之前实现二叉树类中求结点的前驱和后继需要进行遍历操作,缺点是时间复杂度过高,为O(n),这对于基本操作而言效率太低。所以我们想到了可以利用之前二叉链表中空的指针域,指向前驱和后继,这就是线索二叉树。
二、线索二叉树的结点结构
因为孩子指针域需要存储两种指针,为了区分,在结点结构中增加了左标记域ltype和右标记域rtype。
ltype | lchild | data | rchild | rtype |
---|
标记域中只能取两种值,LINK和THREAD。分别表示存储的是左右孩子指针和前驱后继指针。
enum BiThrNodeType { LINK, THREAD };
template <class T>
struct BiThrNode
{
BiThrNodeType ltype, rtype;
T data;
BiThrNode<T>* lchild, * rchild;
};
三、二叉树的线索化算法
1.构造二叉树
要构造线索二叉树,首先要构造出二叉树,这里可以用单个遍历序列构造,也可以使用两个不同的遍历序列进行构造,具体的实现方法在实现二叉树类的博客中有提到过。
2.二叉树的线索化
构造好二叉树后需要将二叉树进行线索化,这里举得是中序线索化算法的例子,具体过程为:
①如果二叉树链表p为空,那么空操作返回。
②对p的左子树建立线索。
③对p所指结点建立线索。
· 若p没有左孩子,则为p加上前驱线索;
· 若p没有右孩子,则将p右标志置为1;
· 若结点prenode右标志为1,则为prenode加上后继线索;
· 令prenode指向刚刚访问的结点p。
④对p的右子树建立线索。
注:1代表着THREAD,0代表着LINK,prenode指刚刚访问过的结点。
void InBiThrTree<T>::InThreaded(BiThrNode<T>*& p) //prenode
{
if (p == NULL)
return;
BiThrNode<T>* prenode = NULL;
InThreaded(p->lchild);
if (p->lchild == NULL)
{
p->ltype = THREAD;
p->lchild = prenode;
}
if (p->rchild == NULL)
p->rtype = THREAD;
if (prenode != NULL)
{
if (prenode->rtype == THREAD)
prenode->rchild = p;
}
prenode = p;
InThreaded(p->rchild);
}
四、线索二叉树的其他算法
1.求前驱结点和后继结点的算法
①前驱结点的求法:
若结点的ltype是THREAD,则lchild域中存储的就是前驱指针。若结点的ltype是LINK,则需要在该结点的左子树中寻找,其前驱结点是左子树中最右下方结点。
②后继结点的求法
若结点的rtype是THREAD,则rchild域中存储的就是后继指针。若结点的rtype是LINK,则需要在该结点的右子树中寻找,其后继结点是右子树中最左下方结点。
BiThrNode<T>* InBiThrTree<T>::GetNext(BiThrNode<T>* p)
{
if (p->rtype == THREAD)
return p->rchild;
p = p->rchild;
while (p->ltype == LINK)
p = p->lchild;
return p;
}
BiThrNode<T>* InBiThrTree<T>::GetPrev(BiThrNode<T>* p)
{
if (p->ltype == THREAD)
return p->lchild;
p = p->lchild;
while (p->rtype == LINK)
p = p->rchild;
return p;
}
2.遍历算法
在原本二叉链表中实现遍历算法需要大量的进栈和出栈操作,会造成时间和空间的低效。在线索二叉树中,则可以利用求后继指针的算法,从而不使用栈空间实现更快的遍历。具体过程为:
①找到中序遍历的起点。即二叉树最左下方的结点,将其作为当前结点;
②访问当前结点;
③找到当前结点的后继结点,将其置为当前结点;
④若当前结点指针不为空,转②;否则,遍历结束。
void InBiThrTree<T>::Travese()
{
BiThrNode<T>* p = root;
while (p->ltype == LINK)
p = p->lchild;
while (p != NULL)
{
cout << p->data << " ";
p = GetNext(p);
}
}
3.求父结点的算法
线索二叉树中结点p和父结点parent有如下关系:
①若p是parent的左孩子,则p的最右下方结点的后继结点一定是p的父结点。
②若p是parent的右孩子,则p的最左下方结点的前驱结点一定是p的父结点。
BiThrNode<T>* InBiThrTree<T>::GetParent(BiThrNode<T>* p)
{
if (p == NULL)
return NULL;
BiThrNode<T>* parent;
parent = p;
while (parent->rtype = LINK)
parent = parent->rchild;
parent = parent->rchild;
if (parent && parent->lchild == p)
return parent;
parent = p;
while (parent->ltype == LINK)
parent = parent->lchild;
parent = parent->lchild;
return parent;
}
4.析构算法
先用指针p指向中序遍历的起始结点,利用GetNext函数获取结点的后继结点后,删除p所指结点,再将后继结点赋值给p指针,直到p指向NULL,完成析构操作。
InBiThrTree<T>::~InBiThrTree()
{
BiThrNode<T>* p = root;
BiThrNode<T>* q = NULL;
while (p->ltype == LINK)
p = p->lchild;
while (p != NULL)
{
q = p;
p = GetNext(p);
delete q;
}
}
五、总结
线索二叉树充分利用了二叉树中空结点,使得我们能够更加方便的求结点的前驱和后继,同时也为遍历和求父结点提供了一定的帮助,总的来说充分利用了每一份空间,同时减少了时间复杂度,实现了高效率。