线索二叉树(C语言简单实现)

本文详细介绍了线索二叉树的概念,通过利用二叉链表的空指针域存储前驱和后继信息,实现二叉树的线索化。文章探讨了线索二叉树的结构、线索化过程,以及中序遍历的线索化递归函数,并提供了示例说明其应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线索二叉树

本文参考自《大话数据结构》

原理

​ 对于一个有n个结点的二叉链表,每个结点有指向左右孩子的指针域,所以一共有2n个指针域。而n个结点的二叉树一共有n-1条分支数,也就是说,其实是存在2n-(n-1)=n+1个空指针域,这些空指针域不存储任何东西,白白浪费着内存的资源。

​ 我们做遍历的时候,比如中序遍历,我们可以知道任意一个结点的前驱和后继是谁;但是在二叉链表中给你,我们只能知道每个结点指向其左右孩子结点的地址,而不知道某个结点的前驱和后继。

​ 为了充分利用这些空地址,可以用来存放指向结点在某种遍历次序下的前驱和后继结点的地址。我们把这种指向前驱和后继的指针称为线索,加上线索的二叉链表称为线索链表,相应的二叉树就称为线索二叉树

线索二叉树示例1
线索二叉树示例2

​ 我们可以把所有的空指针域中的rchild改为指向它的后继结点,把所有空指针域中的lchild,改为指向当前结点的前驱,这样就变成了一个双向链表,对我们的插入删除、查找某个结点带来了方便。我们对二叉树以某种次序遍历使其变为线索二叉树的过程称作是线索化

二叉链表线索化

结构

​ 为了分辨出某一结点的lchild是指向左孩子还是前驱;rchild是指向右孩子还是后继,我们在每个结点再增设两个布尔型变量的标志域ltagrtag,其占用内存空间要小于像lchildrchild的指针变量。

二叉链表线索化

  • ltag为0时指向结点左孩子,为1时指向结点前驱;
  • rtag为0时指向结点右孩子,为1时指向结点后继;
/* 二叉树的二叉线索存储结构定义 */
typedef enum	{Link, Thread} PointerTag;	//Link == 0表示指向左右孩子,Thread == 1表示指向前驱后继
/* 二叉线索存储结点结构 */
typedef struct BithrNode{
	TElemType data;		//结点数据
	struct BiThrNode *lchild, *rchild;	//左右孩子指针
	PointerTag LTag;
	PointerTag RTag;	//左右标志
}biThrNode, *BiThrTree;

线索化

​ 线索化的实质就是将二叉链表中的空指针改为指向前驱或后继的线索。由于前驱和后继的信息只有在遍历二叉树时才能得到,所以线索化的过程就是在遍历的过程中修改空指针的过程

​ 中序遍历线索化的递归函数代码:

BiThrTree pre;	//全局变量,始终指向刚刚访问过的结点
/* 中序遍历进行中序线索化 */
void InThreading(BiThrTree p){
	if(p){
		InThreading(p->lchild);	 //递归左子树线索化
		if(!p->lchild){			//没有左孩子	
			p->LTag = Thread;	//前驱线索
			p->lchild = pre;	//左孩子指针指向前驱
		}
		if(!pre->rchild){		//前驱没有右孩子
			pre->RTag = Thread;	//后继线索
			pre->rchild = p;	//前驱右孩子指针指向后继(当前结点p)
		}
		pre = p;	//保持pre指向p的前驱
		InThreading(p->rchild);		//递归右子树线索化
	}
}

​ 有了线索二叉树,我们对它进行遍历,就等于是操作一个双向链表结构。

​ 和双向链表结构一样,在二叉线索链表上添加一个头结点如图6-10-6所示,并令其lchild域的指针指向二叉树的根结点,其rchild域的指针指向中序遍历时访问的最后一个结点。反之,令二叉树的中序序列中的第一个结点中,lchild域指针和最后一个结点的rchild域指针均指向头结点。这样定义的好处是:我们既可以从第一个结点起顺后继进行遍历,也可以从最后一个结点起顺前驱进行遍历。

线索二叉树示例3

/* T指向头结点,头结点左链lchild指向根结点,头结点右键rchild指向中序遍历的最后一个结点。中序遍历二叉线索链表表示的二叉树T */
Status InOrderTraverse(BiThrTree T){
	BiThrTree p;
	p = T->lchild;	//p指向根结点
	while(p != T){	//空树或遍历结束时,p == T
		while(p->LTag == Link)	//当LTag == 0时循环到中序序列第一个结点
			p = p->lchild;
		printf("%c",p->data);	//显示结点数据,可以更改为其他对结点操作
		while(p->RTag == Thread && p->rchild != T){
			p = p->rchild;
			printf("%c", p->data);
		}
		p = p->rchild;		//p进至其右子树根
	}
	return OK;
}

​ 这段代码,相当于一个链表的扫描,所以时间复杂度为O(n)
​ 由于它充分利用了空指针域的空间(这等于节省了空间),又保证了创建时的一次遍历就可以终生受用前驱后继的信息(这意味着节省了时间)。所以在实际问题中,如果所用的二叉树需经常遍历或查找结点时需要某种遍历序列中的前驱和后继,那么采用线索二叉链表的存储结构是非常不错的选择

示例

线索二叉树示例图

输入序列(前序输入):1,2,4,65535,65535,5,65535,65535,3,6,65535,65535,7,65535,65535

#include <stdio.h>
#include <stdlib.h>
typedef int TElemType;
typedef enum{
	Link, Thread
}PointerTag;

struct BithrNode{
	TElemType data;	//数据域 
	BithrNode *lchild, *rchild;	//左右孩子 
	PointerTag LTag;	//标志 
	PointerTag RTag;
};

BithrNode* pre;	//指向前一个访问的结点,为了方便拿到前驱 

/* 中序线索化,其实就是修改空指针为前驱后继的过程 */
void InThreading(BithrNode* b){
	if(b != NULL){
		//中序遍历,所以先递归访问叶子结点 
		InThreading(b->lchild);
		if(b->lchild == NULL){	//没有左孩子 
			b->LTag = Thread;	//线索点 
			b->lchild = pre;	//左孩子指向前驱 
		}
		if(pre->rchild == NULL){	//前面访问的一个结点没有右孩子,那么,当前结点就为前一个结点的后继 
			pre->RTag = Thread;	//线索点
			pre->rchild = b;	//pre结点的后继就是当前结点 
		}
		pre = b;	//指向前一个访问的结点 
		InThreading(b->rchild);	//线索化右子树
	}
}

/* 初始化二叉树 */
BithrNode* InitBithree(){
	int input;
	printf("请输入值,65535表示NULL:\n");
	scanf("%d", &input);
	if(input != 65535){
		//新结点
		BithrNode* newNode = (BithrNode*)malloc(sizeof(BithrNode));
		newNode->data = input;
		newNode->LTag = newNode->RTag = Link;
		newNode->lchild = InitBithree();
		newNode->rchild = InitBithree();
		return newNode;
	}
	return NULL;
}

/* 中序遍历线索二叉树 */
void InOrderTraverse(BithrNode* b){
	
	BithrNode* p = b;
	while(p){
		//从中序遍历的第一个结点开始遍历 
		while(p->LTag == Link){
			p = p->lchild;
		} 
		printf("%d ", p->data);
		while(p->RTag == Thread){	//后继标志生效
			p = p->rchild;	//当前结点指向后继结点
			printf("%d ",p->data); 
		}
		//找到右子树下最左结点,循环遍历
		p = p->rchild;
	}
}

int main(){
	
	BithrNode* root = InitBithree();	//初始化二叉树,得到根结点
	pre = root;	//指向根节点
	InThreading(root);
	InOrderTraverse(root);
	return 0;
} 

示例结果

线索二叉树图解

线索二叉树(Threaded Binary Tree),又称为双亲链接树,是一种数据结构,它在二叉树的基础上添加了一些特殊的指针,使得查找、插入和删除操作更加高效。通常,线索二叉树会通过修改节点的前驱(left)和后继(right)指针来指示查找路径的方向。 以下是一个简单线索二叉树节点结构体和基本的操作函数的C语言实现: ```c // 节点结构 typedef struct Node { int data; struct Node* left, *right, *prev, *next; // 添加 prev 和 next 指针 } Node; // 初始化新节点 Node* createNode(int value) { Node* newNode = (Node*)malloc(sizeof(Node)); if (!newNode) { printf("Memory allocation failed.\n"); return NULL; } newNode->data = value; newNode->left = newNode->right = newNode->prev = newNode->next = NULL; return newNode; } // 插入节点的基本步骤:如果节点为空,创建新节点;否则沿着线索找到合适的位置并更新线索 void insert(Node** root, int value) { Node* newNode = createNode(value); if (*root == NULL) { // 根节点为空,直接作为根 *root = newNode; } else { Node* current = *root; while (true) { if (value < current->data) { if (current->left == NULL) { // 插入左侧,设置线索 current->left = newNode; newNode->prev = current->prev; newNode->next = current; if (current->prev != NULL) current->prev->next = newNode; else *root = newNode; break; } current = current->left; } else { if (current->right == NULL) { // 插入右侧,设置线索 current->right = newNode; newNode->prev = current; newNode->next = current->next; if (current->next != NULL) current->next->prev = newNode; break; } current = current->right; } } } } // 示例性的查找过程,实际应用需要结合线索更新 void search(Node* root, int value) { Node* current = root; while (current != NULL) { if (value == current->data) { // 找到了,处理后续操作... break; } else if (value < current->data) { current = current->left; } else { current = current->right; } } } // 更复杂的操作如删除和遍历都需要额外处理线索 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值