线索二叉树

原文章:

线索二叉树 - 知乎

此文章为作者本人搬运至该网站。


文章推荐:

线索二叉树(图解+完整代码)-CSDN博客

我看了一个多小时终于看懂了。。。。。

在此之前,我们需要知道:此之前,我们需要知道:


1.为什么要用线索二叉树:

我们从之前内容中可以了解到二叉树的定义:

//----二叉树的二又链表存储表示----
typedef struct BiTNode {
	TElemType data;
	struct BiTNode *lchild,*rchild;	//左右孩子指针
} BiTNode,*BiTree;

那么每一个结点都分别有一个左孩子指针和右孩子指针,用来指向它们的左右孩子。

但是如果对于那种没有左右孩子的结点(叶子结点),那么这两个指针就没有用处了,同时还占用空间。

那么有没有一种法子,让这两个指针也发挥用处呢?

于是就有了线索二叉树。

2.线索二叉树是什么:

从课本内容我们可以看出:

线索二叉树的定义是比原先的二叉树加了两个东西,LTag, RTag。

这一点从线索二叉树的定义也可以看出。

//------二叉树的二叉线索存储表示-------
typedef enum PointerTag { Link, Thread }; //Link == 0:指针,Thread == 1:线索
typedef struct BiThrNode{
  TElemType data;
  struct BiThrNode *lchild, *rchild; // 左右孩子指针
  PointerTag LTag, RTag;  // 左右标志
}BiThrNode, *BiThrTree;

对于LTag,当值为0时,lchild指向的是该结点的左孩子,也就是正常往左指向;

当值为1时,lchild指向的是该结点的前驱,或者是NULL。

对于RTag,当值为0时,rchild指向的是该结点的又孩子;

当值为0时,rchild指向的是该结点的后继。

------

这是课本的定义,不过估计大家和我一样刚看的时候也是懵b的,不过大家需要先记住,这个是原本就有的定义,不需要知道为什么,记住就行了~是课本的定义,不过估计大家和我一样刚看的时候也是懵b的,不过大家需要先记住,这个是原本就有的定义,不需要知道为什么,记住就行了~

其次,我们需要理解当值为1的时候指向的前驱和后继到底是什么意思。

------

其实,很简单,我们需要知道的是,线索二叉树和之前内容的二叉树的遍历是息息相关的。

更进一步地说,线索二叉树是包括了二叉树的遍历,

具体来说,就是线索二叉树的内容会在进行二叉树的遍历的同时,对结点进行各种判断从而确定LTag和RTag的数值,

而我们知道的是,二叉树的遍历最终会得到一个遍历结果,也就是所有结点都排成了一横队,那么这样就很明显了,这个结点的前驱,其实就是遍历结果里面这个结点的前一个结点。以下面这个图为例:

这个二叉树的中序遍历结果是:(突击检查,前序中序后序遍历还记不记得咋写?)

D B E A C

那么,对于D结点,它没有左孩子,那么LTag就为1,那么左孩子指针就会指向它的前驱,但是由于遍历结果中,D本来就是第一个,那么D的左孩子指向就是NULL,

而对于右孩子,D也没有,那么RTag就为1,那么右孩子指针就会指向它的后继,在中序遍历结果中,就是B,也就是说,D的右孩子结点会指向B结点。

那么这样的话,对所有结点,画出它们的前驱和后继结果如下:(前驱为红色,后继为蓝色)

从这里就可以看出,线索二叉树,其实就是利用了没有左孩子或者右孩子指针指向的结点,又给予它们结点的指向,从而利用了本来没有用到的空间。

这样我们就可以看:

3.线索二叉树的遍历代码:

线索二叉树的中序遍历:(为什么不是先序?因为我没找到相关代码。。。)

//------二叉树的二叉线索存储表示-------
typedef enum PointerTag { Link, Thread }; //Link == 0:指针,Thread == 1:线索
typedef struct BiThrNode{
  TElemType data;
  struct BiThrNode *lchild, *rchild; // 左右孩子指针
  PointerTag LTag, RTag;  // 左右标志
}BiThrNode, *BiThrTree;

BiThrNode *pre = NULL;

void inOrderThreadTree(BiThrNode* node)
{
	//如果当前结点为NULL 直接返回
	if (node == NULL) {
		return;
	}
	//先处理左子树
	inOrderThreadTree(node->left_node);
	if (node->left_node == NULL)
	{
		//设置前驱结点
		node->left_type = 1;
		node->left_node = pre;
	}
	//如果结点的右子节点为NULL 处理前驱的右指针
	if (pre !=NULL && pre->right_node == NULL)
	{
		//设置后继
		pre->right_node = node;
		pre->right_type = 1;
	}
	//每处理一个节点 当前结点是下一个节点的前驱
	pre = node;
	//最后处理右子树
	inOrderThreadTree(node->right_node);
}

这个代码是我从那个文章里面找的,我认为还是挺清晰的,只要你跟着代码一点一点走,就能看明白代码的用处。

同时我们也可以看出来,这个代码的形式其实和中序遍历的代码差不多,只是把访问根结点的内容换成了对RTag和LTag的判断。

不过有几点我需要表明,pre也就是结点的前驱,一开始赋值为NULL,而在第二个if中,pre和node的关系又反过来,pre成了结点,node就是结点的后继,这样就成功实现了结点的前驱和后继的判断。

-----

在遍历后,其实线索二叉树也方便了对这个树的再次遍历。

void inOrderTraverse(Node* root)
{
	//从根节点开始先找到最左边
	if (root == NULL)
	{
		return;
	}
	Node* temp = root;
	//先找到最左边结点 然后根据线索化直接向右遍历
	while (temp != NULL && temp->left_type == 0)
	{
		temp = temp->left_node;
	}
	while (temp != NULL)
	{
		//输出
		temp = temp->right_node;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值