线索二叉树详解及代码实现

在二叉树的结点上加上线索的二叉树称为线索二叉树(Threaded BinaryTree),对二叉树以某种遍历方式(如先序、中序、后序或层次等)进行遍历,使其变为线索二叉树的过程称为对二叉树进行线索化,从而使得每个结点(除了第一个和最后一个结点以外)都有一个直接前驱和直接后继。

原理

ni表示度数为i的结点个数,很明显,i=0,1,2

首先对于一个含有n个结点的二叉树,空指针的总数为2n0+n1

又因为n=n0+n1+n2=n1+2n2+1,从而得到n0=n2+1,

因此空指针的总数为2n0+n1=n0+n1+n2+1=n+1。
而总指针的数量有2n个,很明显造成了很大的浪费,利用率非常低,因此为了提高利用率,也为了遍历的方便,我们可以利用这些空指针将二叉树线索化,使得每个结点的前驱后继都非常清晰明了,容易查找。

准备工作

这里使用的是C++实现。

首先这里使用的树结构如下所示,可以看到,这是一颗二叉排序树:

在这里插入图片描述

其次,因为每个结点的左右指针可能指向前驱后继,也可能指向左右子树,因此需要加上标志位来确定:
l t a g = { 0 , l C h i l d 指 向 结 点 的 左 孩 子 1 , l C h i l d 指 向 结 点 的 前 驱 ltag=\begin{cases} 0,lChild指向结点的左孩子\\ 1, lChild指向结点的前驱\end{cases} ltag={0lChild1lChild

r t a g = { 0 , r C h i l d 指 向 结 点 的 右 孩 子 1 , r C h i l d 指 向 结 点 的 后 继 rtag=\begin{cases} 0,rChild指向结点的右孩子\\ 1, rChild指向结点的后继\end{cases} rtag={0rChild1rChild

相应的代码如下所示:

typedef struct BiTNode {
	int data;
	struct BiTNode* lChild, * rChild;  //左右孩子指针
	int ltag, rtag;  //左右线索标志
}BiTNode;

typedef struct {
	BiTNode* root;
}BiTree;

void insert(BiTree& tree, int data) {  //根据二叉排序树的思想插入结点
	BiTNode* node = new(BiTNode);
	node->data = data;
	node->lChild = node->rChild = nullptr;
	node->ltag = node->rtag = 0;
	if (tree.root == nullptr) {
		tree.root = node;
	} else {
		BiTNode* temp = tree.root;
		while (temp != nullptr) {
			if (node->data < temp->data) {
				if (temp->lChild == nullptr) {
					temp->lChild = node;
					return;
				} else {
					temp = temp->lChild;
				}
			} else {
				if (temp->rChild == nullptr) {
					temp->rChild = node;
					return;
				} else {
					temp = temp->rChild;
				}
			}
		}
	}
}

然后在main()函数中添加上这段代码:

int data[10] = { 5, 3, 9, 2, 4, 1, 7, 10, 6, 8 };
BiTree tree;
tree.root = nullptr;
for (int i = 0; i < 10; i++)  insert(tree, data[i]);

那么接下来就进入到正题了。

代码实现

中序遍历线索化二叉树:

void inThread(BiTNode* &p, BiTNode* &pre) {
	if (p) {
		inThread(p->lChild, pre);  //递归,线索化左子树
		if (p->lChild == nullptr) {  //p左子树为空则指向前驱
			p->lChild = pre;
			p->ltag = 1;
		}
		if (pre && pre->rChild == nullptr) {  //pre右子树为空则指向后继
			pre->rChild = p;
			pre->rtag = 1;
		}
		pre = p;  //标记当前结点为刚刚访问过的结点
		inThread(p->rChild, pre);  //递归线索化右子树
	}
}

void createInThread(BiTree& tree) {
	BiTNode* pre = nullptr;
	BiTNode* p = tree.root;
	if (tree.root) {  //对非空二叉树线索化
		inThread(p, pre);
		pre->rChild = nullptr;  //遍历的最后一个结点的右孩子指向nullptr,没有后继
		pre->rtag = 1;
	}
}

这里注意inThread()函数的参数要用引用类型,否则很可能pre和p会一不小心就指向nullptr,亲测,血的教训!

线索化之后的中序遍历:

BiTNode* firstNode(BiTNode* p) {
	while (p->ltag == 0) p = p->lChild;  //找到最左下结点,不一定是叶子结点
	return p;
}

BiTNode* nextNode(BiTNode* p) {
	if (p->rtag == 0) return firstNode(p->rChild);
	else return p->rChild;  //若rtag为1,直接返回后继线索
}

void inOrder(BiTNode* root) {
	for (BiTNode* p = firstNode(root); p; p = nextNode(p)) {
		cout << p->data << " ";
	}
}

运行结果如下:

在这里插入图片描述

可以看到,输出的结果为中序遍历的结果。
前序遍历和后序遍历的线索化也和中序遍历线索化差不多,这里就不再赘述了。

完整代码

#include<iostream>
using namespace std;

typedef struct BiTNode {
	int data;
	struct BiTNode* lChild, * rChild;  //左右孩子指针
	int ltag, rtag;  //左右线索标志
}BiTNode;

typedef struct {
	BiTNode* root;
}BiTree;

void insert(BiTree& tree, int data) {  //根据二叉排序树的思想插入结点
	BiTNode* node = new(BiTNode);
	node->data = data;
	node->lChild = node->rChild = nullptr;
	node->ltag = node->rtag = 0;
	if (tree.root == nullptr) {
		tree.root = node;
	} else {
		BiTNode* temp = tree.root;
		while (temp != nullptr) {
			if (node->data < temp->data) {
				if (temp->lChild == nullptr) {
					temp->lChild = node;
					return;
				} else {
					temp = temp->lChild;
				}
			} else {
				if (temp->rChild == nullptr) {
					temp->rChild = node;
					return;
				} else {
					temp = temp->rChild;
				}
			}
		}
	}
}

void inThread(BiTNode* &p, BiTNode* &pre) {
	if (p) {
		inThread(p->lChild, pre);  //递归,线索化左子树
		if (p->lChild == nullptr) {  //p左子树为空则指向前驱
			p->lChild = pre;
			p->ltag = 1;
		}
		if (pre && pre->rChild == nullptr) {  //pre右子树为空则指向后继
			pre->rChild = p;
			pre->rtag = 1;
		}
		pre = p;  //标记当前结点为刚刚访问过的结点
		inThread(p->rChild, pre);  //递归线索化右子树
	}
}

void createInThread(BiTree& tree) {
	BiTNode* pre = nullptr;
	BiTNode* p = tree.root;
	if (tree.root) {  //对非空二叉树线索化
		inThread(p, pre);
		pre->rChild = nullptr;  //遍历的最后一个结点的右孩子指向nullptr,没有后继
		pre->rtag = 1;
	}
}

BiTNode* firstNode(BiTNode* p) {
	while (p->ltag == 0) p = p->lChild;  //找到最左下结点,不一定是叶子结点
	return p;
}

BiTNode* nextNode(BiTNode* p) {
	if (p->rtag == 0) return firstNode(p->rChild);
	else return p->rChild;  //若rtag为1,直接返回后继线索
}

void inOrder(BiTNode* root) {
	for (BiTNode* p = firstNode(root); p; p = nextNode(p)) {
		cout << p->data << " ";
	}cout << endl;
}

void delTree(BiTNode* root) {
	BiTNode* pre = nullptr;
	for (BiTNode* p = firstNode(root); p; ) {
		pre = p;
		p = nextNode(p);
		delete(pre);
	}
}

int main() {
	int data[10] = { 5, 3, 9, 2, 4, 1, 7, 10, 6, 8 };
	BiTree tree;
	tree.root = nullptr;
	for (int i = 0; i < 10; i++)  insert(tree, data[i]);

	createInThread(tree);
	inOrder(tree.root);

	delTree(tree.root);
	cout << "释放成功!" << endl;
	return 0;
}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

花无凋零之时

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值