有趣的数据结构算法16——线索二叉树的构建

深度遍历不仅仅有递归的方法噢,还有通过建立线索二叉树进行遍历的方法!
在这里插入图片描述

什么是线索二叉树

在二叉树的结点上加上线索的二叉树称为线索二叉树,本文所讲述的线索二叉树主要用于中序遍历。
定义二叉树的时候,我们定义的每一个结点都是属于同一个结构体的,对于二叉树中的叶子结点来说,它的两个子结点都是指向NULL的,这样就造成了结构体的资源浪费。如果能将这些为空的子结点利用起来,将会大大增强二叉树的资源利用率!
常规的二叉树如图所示:
在这里插入图片描述
可以看到二叉树的许多结点的子结点指向的都是NULL,这些资源是没有被利用的。如果能将其利用起来,将大大增强二叉树的资源利用率。
线索二叉树的遍历本质上是将一个复杂的非线性结构转换为线性结构,使每个结点都有了唯一前驱和后继。
线索二叉树是利用二叉树的空链指针完成前驱和后继的添加,利用前驱后继完成中序遍历。

线索二叉树的实现形式

本文使用树的生成方式可以参照我的blog,其具体结点状况如下:
https://blog.csdn.net/weixin_44791964/article/details/98229328
在这里插入图片描述
线索二叉树相比于普通二叉树增加了一个结点p,该结点实际上并不属于真正的树,该结点的存在有助于线索二叉树的生成与遍历。
在最初状态下,p结点左子树指向根节点,右子树指向自身。
在这里插入图片描述
根据每个结点的定义可知,每个结点接有两个子树,分别为左子树和右子树,左子树所连接的结点是要进行遍历的上一个结点,右子树所连接的结点是要进行遍历的下一个结点。
但是对于左下角的结点c而言,其为中序遍历的第一个结点,无上一个结点,因此将其左结点定义为结点p。
但是对于右下角的结点d而言,其为中序遍历的最后一个结点,无下一个结点,因此将其右结点定义为结点p。
此时原子树可以形成如下图所示的闭环。
在这里插入图片描述

线索二叉树的代码实现

线索二叉树的初始化

与普通二叉树不同的是,线索二叉树的结构体增加了两个元素,分别是ltag和rtag,分别用于指示左子树与右子树此时指向的是子结点还是线索
其具体定义如下:

//Link代表0,表示此时指向左右孩子
//Thread代表1,表示此时指向前驱后继的指针
typedef enum {Link,Thread} PointerTag;

struct Node{
	Ele data;		//data用于存储信息
	struct Node* LeftChild;
	struct Node* RightChild;
	PointerTag ltag;
	PointerTag rtag;
};

typedef struct Node Node, *Tree;

在完成结构体的定义后,要进行线索二叉树的初始化,在初始化的时候,我们先假设所有结点的子树都指向左右孩子。

void init(Tree* t){	//当输入的字符为空格的时候,代表叶子结点
	//输入其它的字符代表当前结点的data值
	//输入顺序为当前结点、左结点、右结点。
	Ele c;
	scanf("%c", &c);
	if (' ' == c){
		*t = NULL;
	}
	else{
		*t = (Tree)malloc(sizeof(Node));
		if (*t == NULL){
			printf("内存分配失败");
			exit(1);
		}
		(*t)->data = c;
		(*t)->ltag = Link;
		(*t)->rtag = Link;
		init(&(*t)->LeftChild);		//生成左结点
		init(&(*t)->RightChild);	//生成右结点
	}
}

此时输入合适的字符即可生成二叉树。

线索的串联

在进行所有的结点串联前,应当定义全局变量。

typedef struct Node Node, *Tree;
Tree pre;

该结点用于在进行结点串联时保存上一个结点的地址。
与此同时,由于线索二叉树相对于普通二叉树添加了一个p结点,需要在main函数中进行定义。

int main(){
	Tree p,BiTree = NULL;
	init(&BiTree);
	InitThreading(&p,BiTree);
	printf("中序遍历的结果为:");
	scan(p);
	printf("\n");
	return 0;
}

在main函数中,p元素被传入InitThreading函数进行初始化并完成线索的串联过程。
InitThreading函数的代码如下:

void InitThreading(Tree *p, Tree T){
	*p = (Tree)malloc(sizeof(Node));
	(*p)->ltag = Link;
	(*p)->rtag = Thread;		
	(*p)->RightChild = *p;		//p结点右子树指向自身
	if (!T){
		(*p)->LeftChild = *p;	//当树为空时,p结点的左右子树都指向自身
	}
	else{
		(*p)->LeftChild = T;	//p结点左子树指向根节点
		pre = *p;				//在最初情况下pre指向p
		Threading(T);
		pre->RightChild = *p;
		pre->rtag = Thread;		//使得中序遍历最后一个结点的右子树指向结点p
		(*p)->RightChild = pre;		//使得结点p的右子树指向中序遍历最后一个结点。

	}  
}

在InitThreading函数中,Threading函数用于线索的串联。
具体代码如下:

void Threading(Tree t){
	if (t != NULL){
		Threading(t->LeftChild);
		if (!t->LeftChild){			//如果没有左孩子,则将左孩子指向上一个访问的结点
			t->ltag = Thread;
			t->LeftChild = pre;
		}
		if (!pre->RightChild){			//如果没有左孩子,则将左孩子指向上一个访问的结点
			pre->rtag = Thread;
			pre->RightChild = t;
		}
		pre = t;

		Threading(t->RightChild);
	}
}

全部实现代码

#include <stdio.h>
#include <stdlib.h>

typedef char Ele;

//Link代表0,表示此时指向左右孩子
//Thread代表1,表示此时指向前驱后继的指针
typedef enum {Link,Thread} PointerTag;

struct Node{
	Ele data;		//data用于存储信息
	struct Node* LeftChild;
	struct Node* RightChild;
	PointerTag ltag;
	PointerTag rtag;
};

typedef struct Node Node, *Tree;

Tree pre;

void init(Tree* t){	//当输入的字符为空格的时候,代表叶子结点
	//输入其它的字符代表当前结点的data值
	//输入顺序为当前结点、左结点、右结点。
	Ele c;
	scanf("%c", &c);
	if (' ' == c){
		*t = NULL;
	}
	else{
		*t = (Tree)malloc(sizeof(Node));
		if (*t == NULL){
			printf("内存分配失败");
			exit(1);
		}
		(*t)->data = c;
		(*t)->ltag = Link;
		(*t)->rtag = Link;
		init(&(*t)->LeftChild);		//生成左结点
		init(&(*t)->RightChild);	//生成右结点
	}
}

void Threading(Tree t){
	if (t != NULL){
		Threading(t->LeftChild);
		if (!t->LeftChild){			//如果没有左孩子,则将左孩子指向上一个访问的结点
			t->ltag = Thread;
			t->LeftChild = pre;
		}
		if (!pre->RightChild){			//如果没有左孩子,则将左孩子指向上一个访问的结点
			pre->rtag = Thread;
			pre->RightChild = t;
		}
		pre = t;

		Threading(t->RightChild);
	}
}

void InitThreading(Tree *p, Tree T){
	*p = (Tree)malloc(sizeof(Node));
	(*p)->ltag = Link;
	(*p)->rtag = Thread;		
	(*p)->RightChild = *p;		//p结点右子树指向自身
	if (!T){
		(*p)->LeftChild = *p;	//当树为空时,p结点的左右子树都指向自身
	}
	else{
		(*p)->LeftChild = T;	//p结点左子树指向根节点
		pre = *p;				//在最初情况下pre指向p
		Threading(T);
		pre->RightChild = *p;
		pre->rtag = Thread;		//使得中序遍历最后一个结点的右子树指向结点p
		(*p)->RightChild = pre;		//使得结点p的右子树指向中序遍历最后一个结点。

	}  
}
void visit(Ele e){
	printf("%c", e);
}

void scan(Tree p){
	Tree temp;
	temp = p->LeftChild;		//取得根节点
	while (temp != p){
		while (temp->ltag == Link){		//取得线索二叉树遍历的第一个结点
			temp = temp->LeftChild;
		}
		visit(temp->data);
		while (temp->rtag == Thread && temp->RightChild != p){
			temp = temp->RightChild;

			visit(temp->data);
		}
		temp = temp->RightChild;
	}
}
int main(){
	Tree p,BiTree = NULL;
	init(&BiTree);
	InitThreading(&p,BiTree);
	printf("中序遍历的结果为:");
	scan(p);
	printf("\n");
	return 0;
}

实现结果为:
在这里插入图片描述

GITHUB下载连接

https://github.com/bubbliiiing/Data-Structure-and-Algorithm

希望得到朋友们的喜欢。
有问题的朋友可以提问噢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Bubbliiiing

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

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

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

打赏作者

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

抵扣说明:

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

余额充值