一、定义
前面学习了二叉树,在操作过程中发现了几个问题:
问题一:二叉树如何才能实现从一个指定结点开始遍历呢?
问题二:在二叉树中如何找到指定结点p在遍历序列中的前驱和后继呢?
通过对以上问题的思考发现在二叉树中想要实现以上的功能都离不开从根结点到指定结点的搜索过程,可见在二叉树中实现以上操作是非常不方便的,为了解决这些问题就引出了线索二叉树这个概念。
所谓的线索二叉树就是加上线索的二叉树,我们知道在有n个结点的二叉树中必定存在n+1个空链域(叶子结点的左右孩子指针都为空),由此利用这些空链域来存放结点的前驱和后继的信息,构成线索。
由于进行"线索化"所选择的遍历序列不同,又可将线索二叉树分为中序线索二叉树、先序线索二叉树和后序线索二叉树,本文将较为详细的介绍中序线索二叉树的创建过程。
二、结构
线索化结点结构
若结点有左子树,则其IeftChild域指示其左孩子,否则令leftChild域指示其前驱;若结点有右子树,则其rightChild域指示其右孩子,否则令rightChild域指示其后继。为了避免混淆,尚需改变结点结构,增加两个标志域ltag和rtag。
中序线索二叉树示例
以中序遍历序列CBEDFAGH构成的线索二叉树
代码结构描述
#define ElemType char //元素类型
//标记指针域
typedef enum{LINK,THREAD}Tag_Type;
//线索化结点
typedef struct BinTreeNode
{
ElemType data; //数据域
struct BinTreeNode *leftChild; //左指针域
struct BinTreeNode *rightChild; //右指针域
/*左标记
LINK:leftChild域指示结点的左孩子
THREAD:leftChild域指示结点的前驱
*/
Tag_Type ltag;
/*左标记
LINK:rightChild域指示结点的右孩子
THREAD:rightChild域指示结点的后继
*/
Tag_Type rtag; //右标记
}BinTreeNode;
//二叉树类型
typedef struct BinTree
{
BinTreeNode *root; //指向二叉树根结点
ElemType refvalue; //stop value
}BinTree;
三、常用操作
以下介绍中序线索二叉树的常用操作
初始化
//初始化二叉树
void InitBinTree(BinTree *bt, ElemType ref)
{
bt->root = NULL;
bt->refvalue = ref; //标明结束标记,如#
}
结点申请
BinTreeNode* _Buynode(ElemType x)
{
//申请空间
BinTreeNode *s = (BinTreeNode*)malloc(sizeof(BinTreeNode));
assert(s != NULL);
s->data = x;//传值
s->leftChild = s->rightChild = NULL;
s->ltag = s->rtag = LINK;//线索化之前左右标记都初始为指针链
return s;
}
创建二叉树
//创建二叉树
void CreateBinTree(BinTree *bt, char *str)
{
CreateBinTree(bt,bt->root,str);
}
void CreateBinTree(BinTree *bt, BinTreeNode *&t, char *&str)
{
if(*str == bt->refvalue)
t = NULL;
else
{
t = _Buynode(*str);//获取一个二叉树结点
//创建左子树
CreateBinTree(bt,t->leftChild,++str);
//创建右子树
CreateBinTree(bt,t->rightChild,++str);
}
}
对二叉树进行中序线索化
//创建中序线索化
void CreateInThread(BinTree *bt)
{
BinTreeNode *pre = NULL;//第一个线索结点的前驱为空
CreateInThread(bt->root,pre);
pre->rightChild = NULL;//最后一个线索结点的后继为空
pre->rtag = THREAD;//将右指针类型设置为后继类型
}
void CreateInThread(BinTreeNode *&t, BinTreeNode *&pre)
{
if(t == NULL)
return;
//对左树进行线索化
CreateInThread(t->leftChild,pre);
if(t->leftChild == NULL)//判断是否到达左树最左端(叶子结点)
{//是 结点前序化
/*
注:起始时叶子结点的左右指针都为空,
所以我们就利用叶子结点的左右指针域来建立线索
*/
t->ltag = THREAD; //改变指针域指向类型,改为指向前驱结点
t->leftChild = pre; //将左指针域指向其前驱结点
}
if(pre!=NULL && pre->rightChild==NULL)
{//前驱结点的后序化------>
pre->rtag = THREAD; //将右指针域类型改成后继指针
pre->rightChild = t; //将前驱结点的右指针指向后继
}
//左子树线索化创建完成,pre移动到此时子树的根结点
pre = t;
//对右子树进行线索化
CreateInThread(t->rightChild,pre);
}
获取中序线索二叉树第一个访问的结点
//获取中序线索树的第一个要访问的结点
BinTreeNode* First(BinTree *bt)
{
//传入根结点
return First(bt->root);
}
BinTreeNode* First(BinTreeNode *t)
{
if(t == NULL)
return NULL;
BinTreeNode *p = t;
while(p->ltag == LINK) //移动到第一个结点处
p = p->leftChild;
return p; //返回
}
获取中序线索二叉树最后一个访问的结点
//获取中序线索树的最后一个要访问的结点
BinTreeNode* Last(BinTree *bt)
{
return Last(bt->root);
}
BinTreeNode* Last(BinTreeNode *t)
{
if(t == NULL)
return NULL;
BinTreeNode *p = t;
while(p->rtag == LINK) //移动到最后一个结点处
p = p->rightChild;
return p; //返回
}
获取某结点的后继
//获取某结点的后继结点
BinTreeNode* Next(BinTree *bt, BinTreeNode *cur)
{
return Next(bt->root,cur);
}
BinTreeNode* Next(BinTreeNode *t, BinTreeNode *cur)
{
if(t==NULL || cur==NULL)
return NULL;
if(cur->rtag == THREAD) //判断当前结点的有标记是否指向后继
return cur->rightChild;//是 返回
return First(cur->rightChild);//否 获取右子树的第一个要访问的结点
}
获取某结点的前驱
//获取某结点的前驱
BinTreeNode* Prio(BinTree *bt, BinTreeNode *cur)
{
return Prio(bt->root,cur);
}
BinTreeNode* Prio(BinTreeNode *t, BinTreeNode *cur)
{
if(t==NULL || cur==NULL)
return NULL;
if(cur->ltag == THREAD)//判断当前结点的有标记是否指向前驱
return cur->leftChild;//是 返回
return Last(cur->leftChild);//否 获取左子树最后一个访问的结点
}
中序线索二叉树的中序遍历
//线索二叉树的中序遍历
void InOrder(BinTree *bt)
{
InOrder(bt->root);
}
void InOrder(BinTreeNode *t)
{
BinTreeNode *p;
//按照访问顺序依次获取结点输出
for(p=First(t); p!=NULL; p=Next(t,p))
{
printf("%c ",p->data);
}
printf("\n");
}
查找某个结点
//查找某一个结点
BinTreeNode* Search(BinTree *bt, ElemType key)
{
return Search(bt->root,key);
}
BinTreeNode* Search(BinTreeNode *t, ElemType key)
{
if(t == NULL)
return NULL;
if(t->data == key)
return t;
//按照中序遍历的形式进行线索二叉树的结点查找
BinTreeNode *p;
for(p=First(t); p!=NULL; p=Next(t,p))
{
if(p->data == key)
return p;
}
return NULL;
}
获取某结点的父结点
//获取某结点的父结点
BinTreeNode* Parent(BinTree *bt, BinTreeNode *cur)
{
return Parent(bt->root,cur);
}
BinTreeNode* Parent(BinTreeNode *t, BinTreeNode *cur)
{
if(t==NULL || cur==NULL)
return NULL;
if(t == cur)
return NULL;
BinTreeNode *p;
if(cur->ltag == THREAD) //判断当前结点的左指针是否指向前驱
{//是
p = cur->leftChild; //获取前驱结点
if(p->rightChild == cur) //判断前驱结点的右孩子是否就是当前结点
return p;
}
if(cur->rtag == THREAD) //判断右标记是否为后继结点
{
p = cur->rightChild; //获取后继结点
if(p->leftChild == cur)//判断后继结点的左孩子是否为当前结点
return p;
}
//如果不是以上的情况,那么就获取左子树中的第一个前驱结点
p = First(cur->leftChild);
p = p->leftChild;
if(p!=NULL && p->rightChild == cur) //判断这个前驱结点的右孩子是否为当前结点
return p;
//如果顺着前驱结点没找到,那么就获取右子树的最后一个后继结点,利用后继结点查找父结点
p = Last(cur->rightChild);
return p->rightChild;
}
结语
对线索二叉树的介绍就到这里啦,希望这篇文章能给予你一些帮助,感谢各位人才的:点赞、收藏和评论,我们下次见。
附录
测试代码:线索二叉树详解(C语言版)