为什么需要线索二叉树
当用二叉链表作为二叉树的存储结构时,可以很方便的找到某个结点的左右孩子;但一般情况下,无法直接找到该结点在某种遍历序列中的前驱和后继结点。
解决方法:
- 通过遍历查找–浪费时间
- 增加前驱,后继指针域–浪费空间
- 利用二叉链表中的空指针域
具有 n
个结点的二叉链表中,一共有 2n
个指针域;因为 n
个结点中有 n-1
个孩子,即 2n
个指针域中,有 n-1
个用来指示结点的左右孩子,其余 n+1
个指针域为空。
线索二叉树的定义
如果某个结点的左孩子为空,则将空的左孩子指针域改为 指向其前驱
;如果某结点的右孩子为空,这将空的右孩子指针域改为 指向其后继
这种改变指向的指针称为 线索
加上了线索的二叉树称为 线索二叉树(Threaded Binary Tree)
对二叉树按某种遍历次序使其变为线索二叉树的过程叫 线索化
为了区分 lchild
和 rchild
指针到底是指向孩子的指针,还是指向前驱或者后继的指针,对二叉链表中 每个结点增设两个标识域ltag 和 rtag
,并约定
- ltag = 0 lchild 指向该结点的左孩子
- ltag = 1 lchild 指向该结点的
前驱
- rtag = 0 rchild 指向该结点的右孩子
- rtag = 1 rchild 指向该结点的
后继
所以这样的结点结构为
struct BiThrNode
{
int data;
int ltag;
int rtag;
struct BiThrNode *lchild;
struct BiThrNode *rchild;
}BiThrNode,*BiThrTree;
先序线索二叉树
中序线索二叉树
后续线索二叉树
增加头结点的线索二叉树
本着不能有指针域空着的原则, 避免悬空态,增设一个头结点
头结点的特征
- ltag = 0 lchild 指向根结点
- rtag = 1 rchild 指向遍历序列中的最后一个结点
- 遍历序列中第一个结点的 lc 域 和最后一个结点的 rc 域都指向头结点
线索二叉树的程序实现
main.c
/*
* Change Logs:
* Date Author Notes
* 2021-07-20 tyustli first version
*/
#include "tree.h"
void visitT(TElemType e)
{
printf("%d ", e);
}
int main(int argc, char *argv[])
{
printf("this bitree\r\n");
BiThrTree H, T;
printf("请按先序输入二叉树(如:1 2 0 0 0表示1为根结点,2为左子树的二叉树)\n");
CreateBiThrTree(&T); // 按先序产生二叉树
InOrderThreading(&H, T); // 在中序遍历的过程中,中序线索化二叉树
printf("中序遍历(输出)线索二叉树:\n");
InOrderTraverse_Thr(H, visitT); // 中序遍历(输出)线索二叉树
printf("\n");
DestroyBiThrTree(&H); // 销毁线索二叉树
}
/***************** end of file ******************/
tree.c
#include "tree.h"
TElemType Nil = 0; // 设整型以0为空
// 按先序输入线索二叉树中结点的值,构造线索二叉树T。0(整型)/空格(字符型)表示空结点
void CreateBiThrTree(BiThrTree *T)
{
TElemType ch;
scanf("%d ", &ch);
if (ch == Nil)
(*T) = NULL;
else
{
(*T) = (BiThrTree)malloc(sizeof(struct BiThrNode)); // 生成根结点(先序)
if (!(*T))
exit(-1);
(*T)->data = ch; // 给根结点赋植
CreateBiThrTree(&(*T)->lchild); // 递归构造左子树
if ((*T)->lchild) // 有左孩子
(*T)->LTag = Link; // 给左标志赋值(指针)
CreateBiThrTree(&(*T)->rchild); // 递归构造右子树
if ((*T)->rchild) // 有右孩子
(*T)->RTag = Link; // 给右标志赋值(指针)
}
}
BiThrTree pre; // 全局变量,始终指向刚刚访问过的结点
// 通过中序遍历进行中序线索化,线索化之后pre指向最后一个结点。算法6.7
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); // 递归右子树线索化
}
}
// 中序遍历二叉树T,并将其中序线索化,Thrt指向头结点
void InOrderThreading(BiThrTree *Thrt, BiThrTree T)
{
if (!(*Thrt = (BiThrTree)malloc(sizeof(struct BiThrNode)))) // 生成头结点不成功
exit(-1);
(*Thrt)->LTag = Link; // 建头结点,左标志为指针
(*Thrt)->RTag = Thread; // 右标志为线索
(*Thrt)->rchild = *Thrt; // 右指针回指
if (!T) // 若二叉树空,则左指针回指
{
(*Thrt)->lchild = *Thrt;
}
else
{
(*Thrt)->lchild = T; // 头结点的左指针指向根结点
pre = *Thrt; // pre(前驱)的初值指向头结点
InThreading(T); // 中序遍历进行中序线索化,pre指向中序遍历的最后一个结点
pre->rchild = *Thrt; // 最后一个结点的右指针指向头结点
pre->RTag = Thread; // 最后一个结点的右标志为线索
(*Thrt)->rchild = pre; // 头结点的右指针指向中序遍历的最后一个结点
}
}
// 中序遍历线索二叉树T(头结点)的非递归算法。
void InOrderTraverse_Thr(BiThrTree T, void (*Visit)(TElemType))
{
BiThrTree p;
p = T->lchild; // p指向根结点
while (p != T)
{ // 空树或遍历结束时,p==T
while (p->LTag == Link) // 由根结点一直找到二叉树的最左结点
p = p->lchild;
Visit(p->data); // 访问此结点
while (p->RTag == Thread && p->rchild != T) // p->rchild是线索(后继),且不是遍历的最后一个结点
{
p = p->rchild;
Visit(p->data); // 访问后继结点
}
p = p->rchild; // 若p->rchild不是线索(是右孩子),p指向右孩子,返回循环,
} // 找这棵子树中序遍历的第1个结点
}
// PreOrderThreading()调用的递归函数
void PreThreading(BiThrTree p)
{
if (!pre->rchild) // p的前驱没有右孩子
{
pre->rchild = p; // p前驱的后继指向p
pre->RTag = Thread; // pre的右孩子为线索
}
if (!p->lchild) // p没有左孩子
{
p->LTag = Thread; // p的左孩子为线索
p->lchild = pre; // p的左孩子指向前驱
}
pre = p; // 移动前驱
if (p->LTag == Link) // p有左孩子
PreThreading(p->lchild); // 对p的左孩子递归调用preThreading()
if (p->RTag == Link) // p有右孩子
PreThreading(p->rchild); // 对p的右孩子递归调用preThreading()
}
// 先序线索化二叉树T,头结点的右指针指向先序遍历的最后1个结点
void PreOrderThreading(BiThrTree *Thrt, BiThrTree T)
{
if (!(*Thrt = (BiThrTree)malloc(sizeof(struct BiThrNode)))) // 生成头结点
exit(-1);
(*Thrt)->LTag = Link; // 头结点的左指针为孩子
(*Thrt)->RTag = Thread; // 头结点的右指针为线索
(*Thrt)->rchild = *Thrt; // 头结点的右指针指向自身
if (!T) // 空树
(*Thrt)->lchild = *Thrt; // 头结点的左指针也指向自身
else
{ // 非空树
(*Thrt)->lchild = T; // 头结点的左指针指向根结点
pre = *Thrt; // 前驱为头结点
PreThreading(T); // 从头结点开始先序递归线索化
pre->rchild = *Thrt; // 最后一个结点的后继指向头结点
pre->RTag = Thread;
(*Thrt)->rchild = pre; // 头结点的后继指向最后一个结点
}
}
// 先序遍历线索二叉树T(头结点)的非递归算法
void PreOrderTraverse_Thr(BiThrTree T, void (*Visit)(TElemType))
{
BiThrTree p = T->lchild; // p指向根结点
while (p != T) // p没指向头结点(遍历的最后1个结点的后继指向头结点)
{
Visit(p->data); // 访问根结点
if (p->LTag == Link) // p有左孩子
p = p->lchild; // p指向左孩子(后继)
else // p无左孩子
p = p->rchild; // p指向右孩子或后继
}
}
// PostOrderThreading()调用的递归函数
void PostThreading(BiThrTree p)
{
if (p) // p不空
{
PostThreading(p->lchild); // 对p的左孩子递归调用PostThreading()
PostThreading(p->rchild); // 对p的右孩子递归调用PostThreading()
if (!p->lchild) // p没有左孩子
{
p->LTag = Thread; // p的左孩子为线索
p->lchild = pre; // p的左孩子指向前驱
}
if (!pre->rchild) // p的前驱没有右孩子
{
pre->RTag = Thread; // p前驱的右孩子为线索
pre->rchild = p; // p前驱的后继指向p
}
pre = p; // 移动前驱
}
}
// 后序递归线索化二叉树
void PostOrderThreading(BiThrTree *Thrt, BiThrTree T)
{
if (!(*Thrt = (BiThrTree)malloc(sizeof(struct BiThrNode)))) // 生成头结点
exit(-1);
(*Thrt)->LTag = Link; // 头结点的左指针为孩子
(*Thrt)->RTag = Thread; // 头结点的右指针为线索
if (!T) // 空树
{
(*Thrt)->lchild = (*Thrt)->rchild = (*Thrt); // 头结点的左右指针指向自身
}
else
{ // 非空树
(*Thrt)->lchild = (*Thrt)->rchild = T; // 头结点的左右指针指向根结点(最后一个结点)
pre = (*Thrt); // 前驱为头结点
PostThreading(T); // 从头结点开始后序递归线索化
if (pre->RTag != Link) // 最后一个结点没有右孩子
{
pre->rchild = (*Thrt); // 最后一个结点的后继指向头结点
pre->RTag = Thread;
}
}
}
// DestroyBiThrTree调用的递归函数,T指向根结点
void DestroyBiTree(BiThrTree *T)
{
if (*T) // 非空树
{
if ((*T)->LTag == 0) // 有左孩子
DestroyBiTree(&(*T)->lchild); // 销毁左孩子子树
if ((*T)->RTag == 0) // 有右孩子
DestroyBiTree(&(*T)->rchild); // 销毁右孩子子树
free((*T)); // 释放根结点
(*T) = NULL; // 空指针赋0
}
}
// 初始条件:线索二叉树Thrt存在。操作结果:销毁线索二叉树Thrt
void DestroyBiThrTree(BiThrTree *Thrt)
{
if (*Thrt) // 头结点存在
{
if ((*Thrt)->lchild) // 根结点存在
DestroyBiTree(&(*Thrt)->lchild); // 递归销毁头结点lchild所指二叉树
free((*Thrt)); // 释放头结点
(*Thrt) = NULL; // 线索二叉树Thrt指针赋0
}
}
/***************** end of file ******************/
tree.h
/*
* Change Logs:
* Date Author Notes
* 2021-07-20 tyustli first version
*/
#include <string.h>
#include <ctype.h>
#include <malloc.h> // malloc()等
#include <limits.h> // INT_MAX等
#include <stdio.h> // EOF(=^Z或F6),NULL
#include <stdlib.h> // atoi()
#include <math.h> // floor(),ceil(),abs()
// 函数结果状态代码
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
// #define OVERFLOW -2 因为在math.h中已定义OVERFLOW的值为3,故去掉此行
typedef int Status; // Status是函数的类型,其值是函数结果状态代码,如OK等
typedef int Boolean; // Boolean是布尔类型,其值是TRUE或FALSE
typedef int TElemType;
// 二叉树的二叉线索存储表示
enum PointerTag // 枚举
{
Link,
Thread
}; // Link(0):指针,Thread(1):线索
struct BiThrNode
{
TElemType data;
struct BiThrNode *lchild, *rchild; // 左右孩子指针
enum PointerTag LTag, RTag; // 左右标志
};
typedef struct BiThrNode *BiThrTree;
void CreateBiThrTree(BiThrTree *T);
void InThreading(BiThrTree p);
void InOrderThreading(BiThrTree *Thrt, BiThrTree T);
void InOrderTraverse_Thr(BiThrTree T, void (*Visit)(TElemType));
void PreThreading(BiThrTree p);
void PreOrderThreading(BiThrTree *Thrt, BiThrTree T);
void PreOrderTraverse_Thr(BiThrTree T, void (*Visit)(TElemType));
void PostThreading(BiThrTree p);
void PostOrderThreading(BiThrTree *Thrt, BiThrTree T);
void DestroyBiTree(BiThrTree *T);
void DestroyBiThrTree(BiThrTree *Thrt);
/***************** end of file ******************/
makefile
objects = main.o tree.o
obj: $(objects)
cc -o obj $(objects) -lm
main.o : tree.h
tree.o : tree.h
.PHONY : clean
clean :
-rm obj $(objects)