当前位置: 首页 > article >正文

【数据结构】10.线索二叉树

一、线索二叉树的产生

采用先序、中序、后序三种方法遍历二叉树后都可以得到一个线性序列,序列上的每一个结点(除了第一个和最后一个)都有一个前驱和一个后继,但是,这个线性序列只是逻辑的概念,不是物理结构。
在这里插入图片描述
二叉链体现的是父子关系,不一定是结点在遍历序列中的前驱和后继。

在有n个结点的二叉链表中,有n+1个空指针 。那么能否利用这些空指针来存放前驱和后继结点的地址呢?然后可以像遍历链表一样方便的遍历二叉树序列呢?
这也就是线索二叉树产生的背景。线索二叉树可以解决部分的上述问题,加快在序列中查找前驱和后继节点的速度,但同样的也增加了在树中插入和删除节点的难度。

二、二叉树线索化

二叉树线索化是将二叉链表中的空指针改为指向前驱或后继结点。而前驱结点和后继结点只要在遍历时才能拿到,所有二叉树线索化分为先序线索二叉树、中序线索二叉树和后序线索二叉树

如果当前节点没有左子树,那么该节点的左指针指向遍历序列中它的前驱结点。
如果当前节点没有右子树,那么该节点的右指针指向遍历序列中它的后继结点。

在这里插入图片描述

2.1 线索二叉树的定义

typedef int ThreadedBinaryTreeDataType;

typedef struct ThreadedBinaryTree
{
	ThreadedBinaryTreeDataType _data;	
	struct ThreadedBinaryTree* _left;	
	struct ThreadedBinaryTree* _right;
	int _LeftFlag, _RightFlag;	//左右指针类型:0表示非线索指针,1表示线索指针
}TBT,*pTBT;

2.2 先序线索二叉树

在这里插入图片描述
将二叉树进行先序遍历得到的序列就是先序序列,我们需要将他们进行连接。很显然,我们需要找到每一个结点的后继结点。

如果当前结点的右指针存放的是线索,右指针指向的结点即为后续节点。
如果当前结点的右指针存放的是结点,取左子结点,如果左子结点为空,取右子结点。

void _ThreadedPrevOrder(TBT* root)
{
	if (root == NULL)
		return;

	if (root->left == NULL)
	{
		root->left = prev;
		root->LeftFlag = 1;
	}
	if (prev && prev->right == NULL)
	{
		prev->right = root;
		prev->RightFlag = 1;
	}
	prev = root;

	if(root->LeftFlag==0)
		_ThreadedPrevOrder(root->left);
	if(root->RightFlag==0)
		_ThreadedPrevOrder(root->right);
}

void ThreadedPrevOrder(TBT* root)
{
	if (root == NULL)
		return;
	_ThreadedPrevOrder(root);

	prev->right = NULL;
	prev->RightFlag = 1;
}

2.3 后序线索二叉树

在这里插入图片描述
同样的,将二叉树进行后序遍历得到的序列就是后序序列,我们需要将他们进行连接。很显然,我们需要找到每一个结点的前驱结点。

如果当前结点的左指针存放的是线索,左指针指向的结点即为前驱节点。
如果当前结点的左指针存放的是结点,取右子结点,如果右子结点为空,取左子结点。

void _ThreadedPostOrder(TBT* root)
{
	if (root == NULL)
		return;

	_ThreadedPostOrder(root->left);
	_ThreadedPostOrder(root->right);
	if (root->left == NULL)
	{
		root->left = prev;
		root->LeftFlag = 1;
	}
	if (prev && prev->right == NULL)
	{
		prev->right = root;
		prev->RightFlag = 1;
	}
	prev = root;
}

void ThreadedPostOrder(TBT* root)
{
	if (root == NULL)
		return;
	_ThreadedPostOrder(root);
}

2.4 中序线索二叉树

在这里插入图片描述
将二叉树进行中序遍历得到的序列就是中序序列,我们需要将他们进行连接。很显然,我们需要找到每一个结点的前驱结点和后续结点。

如果当前结点的右指针存放的是线索,右指针指向的结点即为后续节点。
如果当前结点的右指针存放的是结点,右子树中序遍历序列的第一个结点(最左)就是后续节点。
如果当前结点的左指针存放的是线索,左指针指向的结点即为前驱节点。
如果当前结点的左指针存放的是结点,左子树中序遍历序列的最后一个结点(最右)就是后续节点。

void _ThreadedInOrder(TBT* root)
{
	if (root == NULL)
		return;

	_ThreadedInOrder(root->left);

	if (root->left == NULL)
	{
		root->left = prev;
		root->LeftFlag = 1;
	}
	if (prev && prev->right == NULL)
	{
		prev->right = root;
		prev->RightFlag = 1;
	}
	prev = root;
	_ThreadedInOrder(root->right);
}

void ThreadedInOrder(TBT* root)
{
	if (root == NULL)
		return;
	_ThreadedInOrder(root);

	prev->right = NULL;
	prev->RightFlag = 1;
}

三、遍历线索二叉树

3.1 遍历中序线索二叉树

void inOrderTraverseThTree(TBT* root)
{
	//找到初始节点
	TBT* head = root;
	while (head->left)
		head = head->left;

	while (head)
	{
		printf("%d ", head->data);

		//找下一个访问的结点
		if (head->right && head->RightFlag == 1)
			head = head->right;
		else if (head->right)
		{
			head = head->right;
			while (head->left && head->LeftFlag == 0)
				head = head->left;
		}
		else if (head->right == NULL)
			break;
	}
}

3.2 遍历先序线索二叉树

void PrevOrderTraverseThTree(TBT* root)
{
	TBT* head = root;
	while (head)
	{
		printf("%d ", head->data);
		if (head->LeftFlag == 0)
			head = head->left;
		else
			head = head->right;
	}
}

3.3 遍历后序线索二叉树

对于后序线索二叉树的遍历是要复杂一点的,对于根节点的后继结点是不好定位的,最好使用到三叉链,当然也可以迭代找到,这里就不演示了


http://www.kler.cn/a/392854.html

相关文章:

  • 每日小题打卡
  • DP之背包基础
  • hiprint结合vue2项目实现静默打印详细使用步骤
  • 【Rust自学】7.1. Package、Crate和定义Module
  • 操作002:HelloWorld
  • 华为管理变革之道:奋斗文化与活力
  • 【Verilog】case、casex、casez的区别
  • MySQL中的事务与锁
  • opencv入门学习总结
  • 游戏服务器和普通服务器的区别
  • Shell编程之正则表达式与文本处理器
  • 游程编码 (Run-length Encoding)详细解读
  • 【go从零单排】Logging
  • uniapp中多角色导致tabbar过多的解决方式
  • 基于Python的自然语言处理系列(59):MultiRetrievalQAChain 实现
  • 基于SSM的“汽车销售分析与管理系统”的设计与实现(源码+数据库+文档+PPT)
  • 笔记本电脑定期保养
  • SwiftUI开发教程系列 - 第十二章:本地化与多语言支持
  • 贪心算法入门(二)
  • 【ROS的Navigation导航系统】
  • (附项目源码)Java开发语言,监督管家APP的设计与实现 58,计算机毕设程序开发+文案(LW+PPT)
  • 传奇996_19——常用函数
  • redis 原理篇 30 redis内存回收 过期key处理
  • 前端框架大比拼:React.js, Vue.js 及 Angular 的优势与适用场景探讨
  • linux,源码编译安装、rsync本地同步、rsync远程同步、inotifywaite实时同步、数据库服务基础、邮件的收发
  • LuaRocks如何安装数据库驱动?