二叉树的中序线索化,并通过线索化后遍历二叉树

(一).为什么二叉树可以线索化?二叉树中,每个节点都有两个指针,一个右孩子指针,一个左孩子指针,n个节点的二叉树中有2n个左右孩子的指针,但是二叉树中只有n-1个节点被父节点所链接,头节点没有父节点,所以为了利用这多余的n+1个指针,线索化就非常有必要,线索化就是当一个节点没有左孩子节点,就将这个节点左孩子节点的指针链接当前按照何种遍历,遍历过后的前驱,这里讨论的是中序线索遍历,所以当前如果没有没有右孩子,就将这个右指针指向前驱节点,

一个二叉树是这个样子,按照中序遍历(左根右)应该是425136,5的后继节点就是1,5的前驱节点就是2,在这个时候5没有左右孩子,所以如果这个二叉树线索化以后5节点的右孩子(右边孩子指针链接后继节点)会链接1节点,5节点的左孩子(左孩子链接前驱节点)会链接2节点,这样就使得二叉树中的空闲左右指针利用起来了,再进行遍历的时候就可以采用线索化的遍历,可以使用迭代的遍历,空间复杂度从O(n)降低到常数级别O(1)。

(二).如何进行线索化?取决于你将要进行什么遍历的线索化,这里讨论中序遍历线索化,那么需要中序遍历的函数,在访问根节点的是进行节点的线索化,线索化以后怎么辨别出这个指针是指向节点的还是线索的,需要两个标签rtag和ltag,用来辨别是指向线索指针还是指向节点指针,要有一个变量用来记录当前节点的前一个节点,pre用来记录当前节点的前一个节点。初始时候pre等于空指针,另一个指针指向要被线索化的根节点,然后p走一步,pre跟着走一步,这里可是递归,如何才能够使得pre的值更改呢,需要创建一个全局变量,全局变量会随着递归的改变而改变,当p指向的节点的左边孩子是空的时候,就需要将左孩子的指针指向pre,左孩子的指针被线索化后是指向前驱节点的,此时的ltag应该被修改。

第一步.创建一个线索化函数,进行判断根节点是不是为空,还需要判断最后一个节点的右孩子是不是空指针,如果是需要将其线索化,最后一个节点是没有后继节点的,应该指向空指针。

第二步,将这个二叉树进行中序遍历,遍历的过程中进行线索化

第三步.判断这个当前节点的有没有左孩子,如果没有就将这个指针指向前驱节点,再进行后继处理的时候,应该判断pre是不是空指针,如果是第一个节点是没有前驱节点的,然后再判断pre的右孩子是不是空的,如果是就将其链接上p指针,p是比pre多走一步的指针

复制代码
//中序遍历线索化
ThreadNode* pre = NULL;//全局变量是放在静态区的
void visit(ThreadNode** p)//二级指针接收
{
	if ((*p)->lchild == NULL)//说明左边是空的
	{
		(*p)->lchild = pre;//左边的孩子用来指向前驱节点
		(*p)->ltag = 1;
	}
	if (pre != NULL && pre->rchild == NULL)//必须先判断是不是空,再进行判断右孩子
	{
		pre->rchild = (*p);//右边的指针用来指向后继节点
		pre->rtag = 1;//说明被线索化了,线索化以后就可以不使用递归也可以遍历整个二叉树
	}
	pre = (*p);//每次结束以后都需要吧pre的值赋值为p
}
void InOrder(ThreadTree* root)//线索化这里需要传递指针,因为线索化会改变值
{
	if ((*root) == NULL)//终止条件
		return;
	InOrder(&(*root)->lchild);
	visit(root);//访问根
	InOrder(&(*root)->rchild);//左根右
}
void InFoundThread(ThreadTree* root)//传递的是二级指针,因为是要修改树中的值,需要将这个值带回去
{
	if ((*root) == NULL)//空树就返回
		return;
	InOrder(root);//进行中序遍历
	pre->rchild = NULL;
	pre->rtag = 1;//此时p和pre指向最后一个节点,最后一个节点的right是没有左孩子的,所以需要将这个节点设置为NULL
}

(三).为什么线索化?线索化后可以不使用递归就可以使得遍历整个数组,空间复杂度是下降了,线索化以后怎么遍历二叉树,这里说一种中序线索化后的后继遍历方法,如果这个节点是被线索化以后右孩子就是后继节点(右指针代表的是后继节点),中序遍历是左根右,先遍历的是左边所以这个节点的前驱可能在左子树,但是后继肯定是在右子树,如果右子树只有一个节点那肯定就是右子树的根节点,如果不是一个节点呢,那么根据左根右这个遍历的规则,右边遍历的第一个肯定是最左边的这个节点。所以需要设计一个函数一直找右子树的左孩子,直到找到左孩子的ltag被线索化以后才可以,不能判断左边孩子的指针是不是空,因为这个时候已经被线索化了,左孩子的指针已经被修改了。找到最左边的孩子以后进行判断,这两种情况,rtag==1和不等于1,将这个节点的下一个节点返回,如果知道了某个节点的下一个节点,是不是就可以从头节点开始遍历整个二叉树

复制代码
ThreadNode* FirstNode(ThreadNode* p)
{
	while (p->ltag != 1)//被线索化就停下来
		p = p->lchild;//一直找左孩子
	return p;
}
ThreadNode* NextNode(ThreadNode* p)
{
	if (p->rtag == 1)//右边的肯定是他的后继,线索化的时候就是这样规定的,前面的都是正确的。
		return p->rchild;
	else
		return FirstNode(p->rchild);
}

void InThreadOrder(ThreadTree root)
{        //左边的第一个,左根右,肯定是最左边的是遍历过后的第一个节点
	for (ThreadNode* p = FirstNode(root); p != NULL; p = NextNode(p))//注意起始位置
	{
		printf("%d ", p->data);
	}
}

(四).整体代码

复制代码
#define _CRT_SECURE_NO_WARNINGS
//二叉树的前序中序后序线索化
#include<stdio.h>
#include<stdlib.h>
typedef struct ThreadNode//线索化的节点类型
{
	int data;//数据域
	struct ThreadNode* rchild, * lchild;//左右两个孩子
	int rtag, ltag;//左右两个标签,用来记录当前节点有没有被线索化,1是被线索化的
}ThreadNode,*ThreadTree;//第一个是线索化结构名称,第二个是线索树的树名字,是一个指针

ThreadNode* GetNode(int n)
{
	ThreadNode* p = (ThreadNode*)malloc(sizeof(ThreadNode));
	if (p == NULL)
	{
		perror("GetNode");
		return NULL;
	}
	p->lchild = NULL;
	p->rchild = NULL;
	p->rtag = p->ltag = 0;//左右都是零
	p->data = n;
	return p;//用动态内存开辟的不会随着程序的结束而销毁
}

ThreadNode* FoundThreadTree()
{
	ThreadNode* n1 = GetNode(1);
	ThreadNode* n2 = GetNode(2);
	ThreadNode* n3 = GetNode(3);
	ThreadNode* n4 = GetNode(4);
	ThreadNode* n5 = GetNode(5);
	ThreadNode* n6 = GetNode(6);

	n1->lchild = n2;
	n1->rchild = n3;
	n2->lchild = n4;
	n2->rchild = n5;
	n3->lchild = n6;
	return n1;
}
//void PreOrder(ThreadTree root)
//{
//	if (root == NULL)//终止条件
//		return;
//	printf("%d ", root->data);
//	PreOrder(root->lchild);
//	PreOrder(root->rchild);
//}
//中序遍历线索化,线索化就是如果一个根节点没有左子树就将左子树链接上前驱节点,将
//右子树链接上后继节点
//先中序遍历
//中序遍历线索化
ThreadNode* pre = NULL;//全局变量是放在静态区的
void visit(ThreadNode** p)//二级指针接收
{
	if ((*p)->lchild == NULL)//说明左边是空的
	{
		(*p)->lchild = pre;//左边的孩子用来指向前驱节点
		(*p)->ltag = 1;
	}
	if (pre != NULL && pre->rchild == NULL)//必须先判断是不是空,再进行判断右孩子
	{
		pre->rchild = (*p);//右边的指针用来指向后继节点
		pre->rtag = 1;//说明被线索化了,线索化以后就可以不使用递归也可以遍历整个二叉树
	}
	pre = (*p);//每次结束以后都需要吧pre的值赋值为p
}
void InOrder(ThreadTree* root)//线索化这里需要传递指针,因为线索化会改变值
{
	if ((*root) == NULL)//终止条件
		return;
	InOrder(&(*root)->lchild);
	visit(root);//访问根
	InOrder(&(*root)->rchild);//左根右
}
void InFoundThread(ThreadTree* root)//传递的是二级指针,因为是要修改树中的值,需要将这个值带回去
{
	if ((*root) == NULL)//空树就返回
		return;
	InOrder(root);//进行中序遍历
	pre->rchild = NULL;
	pre->rtag = 1;//此时p和pre指向最后一个节点,最后一个节点的right是没有左孩子的,所以需要将这个节点设置为NULL
}

ThreadNode* FirstNode(ThreadNode* p)
{
	while (p->ltag != 1)//被线索化就停下来
		p = p->lchild;//一直找左孩子
	return p;
}
ThreadNode* NextNode(ThreadNode* p)
{
	if (p->rtag == 1)//右边的肯定是他的后继,线索化的时候就是这样规定的,前面的都是正确的。
		return p->rchild;
	else
		return FirstNode(p->rchild);
}

void InThreadOrder(ThreadTree root)
{        //左边的第一个,左根右,肯定是最左边的是遍历过后的第一个节点
	for (ThreadNode* p = FirstNode(root); p != NULL; p = NextNode(p))//注意起始位置
	{
		printf("%d ", p->data);
	}
}
int main()
{
	ThreadTree root=FoundThreadTree();//定义一个树
	//PreOrder(root);
	InFoundThread(&root);//线索化以后一定要引用
	InThreadOrder(root);//遍历
	return 0;
}
相关推荐
C雨后彩虹2 小时前
5G网络建设
java·数据结构·算法·华为·面试
酸菜牛肉汤面2 小时前
5、索引的数据结构(b+树,hash)
数据结构·b树·哈希算法
爱学习的小仙女!4 小时前
顺序表定义、特点和基本操作(含C代码详细讲解)及时间复杂度
数据结构·算法
TechPioneer_lp4 小时前
27届暑期实习内推:网易美团京东快手等
数据结构·c++·人工智能·笔记·机器学习·面试
月明长歌5 小时前
【码道初阶】Leetcode136:只出现一次的数字:异或一把梭 vs HashMap 计数(两种解法完整复盘)
java·数据结构·算法·leetcode·哈希算法
夏乌_Wx5 小时前
练题100天——DAY34:错误的集合+图片平滑器+最长连续递增序列
数据结构
AuroraWanderll6 小时前
类和对象(四):默认成员函数详解与运算符重载(下)
c语言·数据结构·c++·算法·stl
2401_841495646 小时前
【LeetCode刷题】杨辉三角
数据结构·python·算法·leetcode·杨辉三角·时间复杂度·空间复杂度
Cinema KI6 小时前
二叉搜索树的那些事儿
数据结构·c++