线索二叉树

一、线索二叉树的概念

在之前实现二叉树类中求结点的前驱和后继需要进行遍历操作,缺点是时间复杂度过高,为O(n),这对于基本操作而言效率太低。所以我们想到了可以利用之前二叉链表中空的指针域,指向前驱和后继,这就是线索二叉树。

二、线索二叉树的结点结构

因为孩子指针域需要存储两种指针,为了区分,在结点结构中增加了左标记域ltype和右标记域rtype。

ltypelchilddatarchildrtype

标记域中只能取两种值,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;
	}
}

五、总结

线索二叉树充分利用了二叉树中空结点,使得我们能够更加方便的求结点的前驱和后继,同时也为遍历和求父结点提供了一定的帮助,总的来说充分利用了每一份空间,同时减少了时间复杂度,实现了高效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值