背景
传统的二叉链表存储仅能体现一种父子关系,不能直接得到结点在遍历中的前驱或后继。并且在含n个结点的二叉树中,有n+1个空指针。(叶子结点有2个空指针,度为1的结点有1一个空指针,故有个空指针,又因,所以空指针总数为)。为了提高利用率,引入线索二叉树,同时也能加快查找结点的前驱和后继的速度。
定义
二叉树的结点结构
以这种结点结构构成的二叉链表作为二叉树的存储结构,叫做线索链表,其中指向结点前驱和后继的指针,叫做线索。加上线索的二叉函数称之为线索二叉树(Thread Binary Tree)。对二叉树以某种次序遍历使其变为线索二叉树的过程叫做线索化。
图例
中序线索二叉树
先序线索二叉树
后序线索二叉树
代码
线索二叉树的存储结构
typedef struct ThreadNode{
struct ThreadNode *lchild,*rchild;//左子树指针,右子树指针
bool ltag,rtag;
int val;//根节点的值
}ThreadNode,*ThreadTree;
线索二叉树的构造
按照先序顺序进行构造,使用‘-1’来表示叶结点的孩子,可唯一确定一棵二叉树。
//二叉树的构造函数
ThreadNode *CreateTree(){
int val;//获得结点的值
scanf("%d",&val);
if(val==-1){//如果是-1则表示叶子结点
return NULL;
}
ThreadNode *T=(ThreadNode *)malloc(sizeof(ThreadNode));//分配结点空间
T->val=val;
T->rtag=0;
T->ltag=0;
T->lchild=CreateTree();//构造左子树
T->rchild=CreateTree();//构造右子树
return T;
}
线索化当前结点
主要思想:在遍历的过程中
- 检查p的左指针是否为空,若为空就将它指向pre
- 检查pre的右指针是否为空,若为空就将它指向p
//对当前结点进行线索化:固定步骤,当公式记忆
void visit(ThreadNode *&p){
if(p->lchild==NULL){//当前结点左指针为空
p->lchild=pre;//指向前驱
p->ltag=1;
}
if(pre!=NULL&&pre->rchild ==NULL){//pre的右指针为空
pre->rchild =p;//指向当前结点
pre->rtag =1;
}
pre=p;
}
中序线索二叉树
构造中序线索二叉树
-
InThread()
函数是中序线索化的具体实现,通过中序遍历的方式,调用visit()
对每个结点进行线索化。 -
CreateThreadTree()
函数用于构造中序线索二叉树,调用InThread()
实现中序线索化,并将最后一个结点的右孩子设置为NULL。
//按照中序遍历的顺序构造中序线索二叉树
void InThread(ThreadNode *&p){
if(p){
InThread(p->lchild );
visit(p);
InThread(p->rchild );
}
}
void CreateThreadTree(ThreadNode *&T){
pre==NULL;
InThread(T);
if(pre!=NULL){//最后一步,由于当前结点已经为NULL,但前驱结点是最后一个结点,故需要手动设置右孩子为NULL
pre->rchild =NULL;
pre->rtag =1;
}
}
中序线索化遍历
-
First()
函数用于找到中序线索二叉树中的第一个结点。 -
Next()
函数用于找到中序线索二叉树中给定结点的后继结点。 -
InOrderByThread()
函数实现了中序线索化遍历,从第一个结点开始,通过Next()
找到后继结点进行遍历输出。
//求第一个结点
ThreadNode *First(ThreadNode *T){
ThreadNode *p=T;
while(p->ltag ==0){
p=p->lchild ;
}
return p;
}
//寻找下一个结点
ThreadNode *Next(ThreadNode *p){
if(p->rtag ==0){//假如有右子树
return First(p->rchild );//返回右子树中序遍历的第一个节点
}
return p->rchild ;//没有右子树,即代表线索指针,直接返回rchild
}
//中序线索化遍历
void InOrderByThread(ThreadNode *T){
for(ThreadNode *p=First(T);p;p=Next(p)){
printf("%d ",p->val );
}
}
全部代码
#include<bits/stdc++.h>
using namespace std;
typedef struct ThreadNode{
struct ThreadNode *lchild,*rchild;//左子树指针,右子树指针
bool ltag,rtag;
int val;//根节点的值
}ThreadNode,*ThreadTree;
ThreadNode *pre=NULL;
//二叉树的构造函数
ThreadNode *CreateTree(){
int val;//获得结点的值
scanf("%d",&val);
if(val==-1){//如果是-1则表示叶子结点
return NULL;
}
ThreadNode *T=(ThreadNode *)malloc(sizeof(ThreadNode));//分配结点空间
T->val=val;
T->rtag=0;
T->ltag=0;
T->lchild=CreateTree();//构造左子树
T->rchild=CreateTree();//构造右子树
return T;
}
//对当前结点进行线索化:固定步骤,当公式记忆
void visit(ThreadNode *&p){
if(p->lchild==NULL){//当前结点左指针为空
p->lchild=pre;//指向前驱
p->ltag=1;
}
if(pre!=NULL&&pre->rchild ==NULL){//pre的右指针为空
pre->rchild =p;//指向当前结点
pre->rtag =1;
}
pre=p;
}
//构造中序线索二叉树
void InThread(ThreadNode *&p){
if(p){
InThread(p->lchild );
visit(p);
InThread(p->rchild );
}
}
void CreateThreadTree(ThreadNode *&T){
pre==NULL;
InThread(T);
if(pre!=NULL){//最后一步,由于当前结点已经为NULL,无法进入39行,但前驱结点是最后一个结点,故需要手动设置右孩子为NULL
pre->rchild =NULL;
pre->rtag =1;
}
}
//求第一个结点
ThreadNode *First(ThreadNode *T){
ThreadNode *p=T;
while(p->ltag ==0){
p=p->lchild ;
}
return p;
}
//寻找下一个结点
ThreadNode *Next(ThreadNode *p){
if(p->rtag ==0){//假如有右子树
return First(p->rchild );//返回右子树中序遍历的第一个节点
}
return p->rchild ;//没有右子树,即代表线索指针,直接返回rchild
}
//中序线索化遍历
void InOrderByThread(ThreadNode *T){
for(ThreadNode *p=First(T);p;p=Next(p)){
printf("%d ",p->val );
}
}
int main(){
ThreadNode *T=CreateTree();
CreateThreadTree(T);
InOrderByThread(T);
return 0;
}
//二叉树构造:1 2 4 8 -1 -1 9 -1 -1 5 10 -1 -1 -1 3 6 -1 -1 7 -1 -1
//中序线索化遍历:8 4 9 2 10 5 1 6 3 7
先序线索二叉树
构造先序线索二叉树
-
PreThread()
函数是先序线索化的具体实现,通过先序遍历的方式,调用visit()
对每个结点进行线索化。(注意这里与中序线索二叉树的区别:这里为避免先序线索化左孩子之后返回到根节点又对左孩子进行线索化产生循环,故加上tag==0的条件判断。右孩子没有这种情况,为了代码对称美写上的) -
CreateThreadTree()
函数用于构造先序线索二叉树,调用PreThread()
实现先序线索化,并将最后一个结点的右孩子设置为NULL。
//构造先序线索二叉树
void PreThread(ThreadNode *&p){
if(p){
visit(p);
if(p->ltag ==0){//避免访问左孩子但是左孩子指向自己产生循环
PreThread(p->lchild );
}
if(p->rtag ==0){//对称
PreThread(p->rchild );
}
}
}
void CreateThreadTree(ThreadNode *&T){
pre==NULL;
PreThread(T);
if(pre!=NULL){//最后一步,由于当前结点已经为NULL,无法进入39行,但前驱结点是最后一个结点,故需要手动设置右孩子为NULL
pre->rchild =NULL;
pre->rtag =1;
}
}
先序线索化遍历
-
First()
函数用于找到先序线索二叉树中的第一个结点。 -
Next()
函数用于找到先序线索二叉树中给定结点的后继结点。 -
InOrderByThread()
函数实现了先序线索化遍历,从第一个结点开始,通过Next()
找到后继结点进行遍历输出。
注意先序线索化遍历的代码逻辑,尤其是Next()函数,与中序线索化遍历区别开来。
//求第一个结点
ThreadNode *First(ThreadNode *T){
return T;
}
//寻找当前结点的下一个结点
ThreadNode *Next(ThreadNode *p){
if(p->rtag ==0){//没有线索
if(p->ltag ==0){//有左子树
return First(p->lchild );//返回左子树的第一个结点
}
else{//没有左子树
return First(p->rchild );//返回右子树的第一个结点
}
}
return p->rchild ;//rtag=1,即代表线索指针,直接返回rchild
}
//先序线索化遍历
void PreOrderByThread(ThreadNode *T){
for(ThreadNode *p=First(T);p;p=Next(p)){
printf("%d ",p->val );
}
}
全部代码
#include<bits/stdc++.h>
using namespace std;
typedef struct ThreadNode{
struct ThreadNode *lchild,*rchild;//左子树指针,右子树指针
bool ltag,rtag;
int val;//根节点的值
}ThreadNode,*ThreadTree;
ThreadNode *pre=NULL;
//二叉树的构造函数
ThreadNode *CreateTree(){
int val;//获得结点的值
scanf("%d",&val);
if(val==-1){//如果是-1则表示叶子结点
return NULL;
}
ThreadNode *T=(ThreadNode *)malloc(sizeof(ThreadNode));//分配结点空间
T->val=val;
T->rtag=0;
T->ltag=0;
T->lchild=CreateTree();//构造左子树
T->rchild=CreateTree();//构造右子树
return T;
}
//对当前结点进行线索化:固定步骤,当公式记忆
void visit(ThreadNode *&p){
if(p->lchild==NULL){//当前结点左指针为空
p->lchild=pre;//指向前驱
p->ltag=1;
}
if(pre!=NULL&&pre->rchild ==NULL){//pre的右指针为空
pre->rchild =p;//指向当前结点
pre->rtag =1;
}
pre=p;
}
//构造先序线索二叉树
void PreThread(ThreadNode *&p){
if(p){
visit(p);
if(p->ltag ==0){//避免访问左孩子但是左孩子指向自己产生循环
PreThread(p->lchild );
}
if(p->rtag ==0){//对称
PreThread(p->rchild );
}
}
}
void CreateThreadTree(ThreadNode *&T){
pre==NULL;
PreThread(T);
if(pre!=NULL){//最后一步,由于当前结点已经为NULL,无法进入39行,但前驱结点是最后一个结点,故需要手动设置右孩子为NULL
pre->rchild =NULL;
pre->rtag =1;
}
}
//求第一个结点
ThreadNode *First(ThreadNode *T){
return T;
}
//寻找当前结点的下一个结点
ThreadNode *Next(ThreadNode *p){
if(p->rtag ==0){//没有线索
if(p->ltag ==0){//有左子树
return First(p->lchild );//返回左子树的第一个结点
}
else{//没有左子树
return First(p->rchild );//返回右子树的第一个结点
}
}
return p->rchild ;//rtag=1,即代表线索指针,直接返回rchild
}
//先序线索化遍历
void PreOrderByThread(ThreadNode *T){
for(ThreadNode *p=First(T);p;p=Next(p)){
printf("%d ",p->val );
}
}
int main(){
ThreadNode *T=CreateTree();
CreateThreadTree(T);
PreOrderByThread(T);
return 0;
}
//二叉树构造:1 2 4 8 -1 -1 9 -1 -1 5 10 -1 -1 -1 3 6 -1 -1 7 -1 -1
//先序线索化遍历:1 2 4 8 9 5 10 3 6 7
后序线索二叉树
构造后序线索二叉树
-
PostThread()
函数是后序线索化的具体实现,通过后序遍历的方式,调用visit()
对每个结点进行线索化。 -
CreateThreadTree()
函数用于构造后序线索二叉树,调用PostThread()
实现后序线索化,并将最后一个结点的右孩子设置为NULL。
注意最后一个结点的判断条件,最后一个结点的pre是整棵树的根节点,需要判断这棵树是否有右子树,然后将标记置1。
//构造后序线索二叉树
void PostThread(ThreadNode *&p){
if(p){
PostThread(p->lchild );
PostThread(p->rchild );
visit(p);
}
}
void CreateThreadTree(ThreadNode *&T){
pre==NULL;
PostThread(T);
if(pre->rchild ==NULL){//最后一步,最后一个结点是根结点,如果根节点没有右子树,则置标记为空
pre->rtag =1;
}
}
后序线索化遍历
-
Last()
函数用于找到树的最后一个结点,因为后序遍历得到的最后一个结点是根结点。 -
Previous()
函数用于找到当前结点的前驱结点,根据左右子树是否存在来确定前驱结点。 -
PostOrderByThread()
函数实现了后序线索化遍历,从根节点开始,通过Previous()
找到前驱结点进行逆序遍历输出。
注意后序线索化遍历的逆向思维。
//求树的最后一个结点,因为后序遍历得到的最后一个结点肯定是该树的根结点,故直接返回
ThreadNode *Last(ThreadNode *p){
return p;
}
//求当前结点的前驱结点
ThreadNode *Previous(ThreadNode *p){
if(p->ltag ==0){//有左子树
if(p->rtag ==0){//有右子树
return Last(p->rchild );//返回右子树最后一个结点
}
else{//没有右子树
return Last(p->lchild );//返回左子树最后一个结点
}
}
return p->lchild ;//是线索,直接返回
}
//后序线索化遍历
void PostOrderByThread(ThreadNode *T){
int a[105];
memset(a,0,sizeof(a));
int cnt;
for(ThreadNode *p=Last(T);p;p=Previous(p)){
a[cnt++]=p->val ;
}//a数组里存的是逆序序列
while(cnt--){
printf("%d ",a[cnt]);
}
}
全部代码
#include<bits/stdc++.h>
using namespace std;
typedef struct ThreadNode{
struct ThreadNode *lchild,*rchild;//左子树指针,右子树指针
bool ltag,rtag;
int val;//根节点的值
}ThreadNode,*ThreadTree;
ThreadNode *pre=NULL;
//二叉树的构造函数
ThreadNode *CreateTree(){
int val;//获得结点的值
scanf("%d",&val);
if(val==-1){//如果是-1则表示叶子结点
return NULL;
}
ThreadNode *T=(ThreadNode *)malloc(sizeof(ThreadNode));//分配结点空间
T->val=val;
T->rtag=0;
T->ltag=0;
T->lchild=CreateTree();//构造左子树
T->rchild=CreateTree();//构造右子树
return T;
}
//对当前结点进行线索化:固定步骤,当公式记忆
void visit(ThreadNode *&p){
if(p->lchild==NULL){//当前结点左指针为空
p->lchild=pre;//指向前驱
p->ltag=1;
}
if(pre!=NULL&&pre->rchild ==NULL){//pre的右指针为空
pre->rchild =p;//指向当前结点
pre->rtag =1;
}
pre=p;
}
//构造后序线索二叉树
void PostThread(ThreadNode *&p){
if(p){
PostThread(p->lchild );
PostThread(p->rchild );
visit(p);
}
}
void CreateThreadTree(ThreadNode *&T){
pre==NULL;
PostThread(T);
if(pre->rchild ==NULL){//最后一步,最后一个结点是根结点,如果根节点没有右子树,则置标记为空
pre->rtag =1;
}
}
//求树的最后一个结点,因为后序遍历得到的最后一个结点肯定是该树的根结点,故直接返回
ThreadNode *Last(ThreadNode *p){
return p;
}
//求当前结点的前驱结点
ThreadNode *Previous(ThreadNode *p){
if(p->ltag ==0){//有左子树
if(p->rtag ==0){//有右子树
return Last(p->rchild );//返回右子树最后一个结点
}
else{//没有右子树
return Last(p->lchild );//返回左子树最后一个结点
}
}
return p->lchild ;//是线索,直接返回
}
//后序线索化遍历
void PostOrderByThread(ThreadNode *T){
int a[105];
memset(a,0,sizeof(a));
int cnt;
for(ThreadNode *p=Last(T);p;p=Previous(p)){
a[cnt++]=p->val ;
}//a数组里存的是逆序序列
while(cnt--){
printf("%d ",a[cnt]);
}
}
int main(){
ThreadNode *T=CreateTree();
CreateThreadTree(T);
PostOrderByThread(T);
return 0;
}
//二叉树构造:1 2 4 8 -1 -1 9 -1 -1 5 10 -1 -1 -1 3 6 -1 -1 7 -1 -1
//后序线索化遍历:8 9 4 10 5 2 6 7 3 1