- 二叉树的创建及初始化
- 线索化
- Next函数(根据线索找后继)
- Prior函数(根据线索找前驱)
- 非递归中序遍历(根据线索遍历)
为什么要将二叉树线索化?
- 首先就是我们在建立二叉树的时候提到的存储空间浪费的问题,建立n个结点的二叉树,总共有2n个指针域(左右孩子),被浪费的足足有n+1的节点是空节点,我们怎么能利用上这些空间呢?
- 还有一个问题是,在创建二叉树的时候,大部分算法都是用递归实现,实现起来简单,然而系统运行起来却很吃力,很占用系统资源。当我们在建立好二叉树之后我们需要频繁的查找某个单元,就得频繁的调用遍历递归去查找,这显然并不高效,那么我们如何去优化这个问题呢?
- 结合这两个问题,我们的解决办法是:用这些空指针域,存放一些有用的指向信息,从而尽量的将一棵二叉树线性化成双向链表,我们把这些指向信息称之为线索,也就是说线索二叉树等效成双向链表。
- 那么我们在将一颗二叉树线索化了之后,就可以根据线索来找到每一个结点,就不必调用递归算法,大大提升了效率,同时还能高效的利用了别浪费的空指针。
如何实现线索化?
1. 结点的定义:
- 为了实现线索化,我们将原来的结点空间由三个域扩充到五个域,多了左、右标志(ltag,rtag)。
- 定义方法:
typedef struct node
{
dataType data; //根节点的值
struct node *lchild; //左孩子
struct node *rchild; //右孩子
int ltag; //左标记
int rtag; //右标记
}BiTree;
2. 创建二叉树并初始化:
BiTree *creat() //二叉树的创建及初始化(初始化左右标记为0)
{
dataType value;
BiTree *t;
scanf("%c",&value);
if(value=='#')
{
t=NULL;
}
else
{
t=(BiTree *)malloc(sizeof(BiTree));
t->data=value;
t->ltag=0;//初始化左标记为0
t->rtag=0;//初始化右标记为0
t->lchild=creat();
t->rchild=creat();
}
return t;
}
- 与二叉树的创建方法一致,只需要加入对左、右标志初始化为0。
3. 加入线索:
- 利用两个指针p和pre,让其在中序遍历的过程中分别指向一前一后,也就是说始终保持p是pre的后继,pre是p的前驱;那么在处理到p结点的时候,我们可以根据p节点是否有左孩子来判断,p->lchild是否要指向后继,若p指向的节点没有左孩子,则直接让p->lchild=pre;并将p->ltag赋值为1,即可完成将结点的左孩子指向前驱结点。pre也一个道理,若pre所指向的节点没有右孩子,将pre->rchild=p;并即将pre->rtag赋值为1,即可完成该结点的右孩子指向后继节点。
- 整体思路就是:通过p来处理左孩子指向前驱(左孩子为空的情况下),通过pre来处理右孩子指向后继(右孩子为空的情况下)。
pre如何定义?
- pre与p指针功能相同,但定义却不相同,p是线索化函数内的局部变量,而pre为了保证在每一次递归序列中能够正确返回当前值,必须定义成: ①全局变量 ②静态变量
- 因为我们是以中序遍历的顺序给二叉树填上线索,所以,整个线索化的函数是套用在中序遍历的框架下的。
注意:
- if(pre&&pre->rchild) 中,判断pre不为空的条件一定不能少,否则第一个结点pre指向空到此处会出错!
(<数据结构C语言第二版(严蔚敏,吴伟民)> 中算法5.7 没有判断pre不空是因为有设置头结点,pre一开始就指向头结点,也一直不为空,所以不需要)
5. Next与Prior函数
- Next函数是已知结点t找t的后继节点。若rtag的的值为1,则说明rchild指向后继结点,那我们即可直接找到;如果rtag的值为0,则不能直接找到,此时需要查找到右子树下的最左端的结点。
- Prior函数也是一个道理,已知结点t找t的前驱节点,若ltag的的值为1,则说明lchild指向前驱结点,那我们即可直接找到;如果ltag的值为0,则不能直接找到,此时需要查找到左子树下的最右端的结点。
6.根据线索实现遍历
- 有了线索之后,我们就可以像链表一样找到每一个结点,实现遍历(中序线索化,中序遍历)。
- 但是需要注意的一点是,我们creat函数中输入是以先序的次序输入端,故p一开始指向的节点是先序的第一个节点(a),而不是中序的第一个结点(d),所以要先查找第一个节点,在用Next中的线索查找后续结点。
完整源代码:
#include <stdio.h>
#include <stdlib.h>
typedef char dataType;
typedef struct node
{
dataType data; //根节点的值
struct node *lchild; //左孩子
struct node *rchild; //右孩子
int ltag; //左标记,“ltag=0”表示当前节点有左孩子,“ltag=1”表示当前节点没有左孩子
int rtag; //右标记,“rtag=0”表示当前节点有右孩子,“rtag=1”表示当前节点没有右孩子
}BiTree;
BiTree *creat() //二叉树的创建及初始化(初始化左右标记为0)
{
dataType value;
BiTree *t;
scanf("%c",&value);
if(value=='#')
{
t=NULL;
}
else
{
t=(BiTree *)malloc(sizeof(BiTree));
t->data=value;
t->ltag=0;//初始化左标记为0
t->rtag=0;//初始化右标记为0
t->lchild=creat();
t->rchild=creat();
}
return t;
}
//BiTree *pre=NULL; //1.定义全局变量pre
void InThreaded(BiTree *p)
{
static BiTree *pre=NULL;//2.定义静态变量
if(p)
{
InThreaded(p->lchild);
if(!p->lchild)
{
p->ltag=1;
p->lchild=pre;
}
if(pre&&!pre->rchild)
{
pre->rtag=1;
pre->rchild=p;
}
pre=p;
InThreaded(p->rchild);
}
}
BiTree *Next(BiTree *t) //已知节点t找t的"后继"结点位置
{
if(t->rtag==1) //右标志为1,可以直接得到"后继"结点
{
t=t->rchild;
}
else /*右标志为0,不能直接的到"后继"结点,
则需要找到右子树最左下角的节点*/
{
t=t->rchild;
while(t->ltag==0)
{
t=t->lchild;
} //while
}//else
return t;
}
BiTree *Prior(BiTree *t)//已知节点t找t的"前驱"结点位置
{
if(t->ltag==1)//左标志为1,可以直接找到"前驱"结点的位置
{
t=t->lchild;
}
else /*右标志为0,不能直接的到"前驱"结点,
则需要找到左子树最右下角的节点*/
{
t=t->lchild;
while(t->rtag==0)
{
t=t->rchild;
} //while
} //else
return t;
}
void InorderTraverse(BiTree *t)//利用线索实现中序遍历
{
if(!t)
{
return;
}
while(t->ltag==0)//查找第一个节点
{ //因为二叉树的创建creat是以先序遍历序列创建,所以t所指向的第一个结点并不是中序遍历所要访问的第一个结点
t=t->lchild;
}
printf("%c ",t->data);//访问第一个结
while(t->rchild)// 此处以"t的右孩子不为空"为循环条件,是因为,先前设定了最后一个结点的"后继"为空,表示结束
{ //根据线索访问后续结点
t=Next(t);
printf("%c ",t->data);
}
}
int main()
{
BiTree *root;
printf("Input:");
root=creat();
printf("\n");
printf("Threading Binary Tree!\n");
InThreaded(root);
printf("\n");
printf("Inorder traverse:");
InorderTraverse(root);
printf("\n");
return 0;
}
执行结果:
补充:
- pre的定义问题:除了 ①全局变量 ②静态变量;你可能还会想到用以下方法定义:
void InThread(BiTree *p,BiTree *pre)
{
if(p!=NULL)
{
InThread(p->lchild,pre);
if(p->lchild==NULL)
{
p->lchild=pre;
p->ltag=1;
}
if(pre!=NULL&&pre->rchild==NULL)
{
pre->rchild=p;
pre->rtag=1;
}
pre=p;
InThread(p->rchild,pre);
}
}//错误算法!!
或者是这样:
void InThread(BiTree *p,BiTree *pre)
{
if(p!=NULL)
{
InThread(p->lchild,p);
if(p->lchild==NULL)
{
p->lchild=pre;
p->ltag=1;
}
if(pre!=NULL&&pre->rchild==NULL)
{
pre->rchild=p;
pre->rtag=1;
}
InThread(p->rchild,p);
}
}//错误算法!!
以上两种算法均错误!在递归的过程中均不能正常运行!
参考:
- 数据结构C语言第二版(严蔚敏,吴伟民)
- 懒猫老师