线索二叉树
按照某种遍历方式对二叉树进行遍历时,可以把二叉树中的所有节点排列成为一个线性序列,在该序列中,除了第一个节点外,每个节点有且仅有一个直接前驱(直接后继), 但是 ! 当以二叉链表作为存储结构时,只能得到节点的左右孩子信息,而不能直接得到节点在某种遍历序列中的前驱和后继节点。这种信息只有在对二叉树进行动态遍历的过程中才能得到。
为了保存节点在某种遍历序列中直接前驱(后继)信息,可以利用二叉树的空指针域来存放。一个具有n个节点的二叉树(二叉链表),
在 2n 个指针域中只有 n-1 个指针域用来存放节点孩子的地址,另外n+1个指针域都为空。这些空的指针域就可以用来存放某种前驱后继。这种指向前驱后继的指针成为线索,加了线索的二叉树
叫做线索二叉树。以某种次序遍历使其变成线索二叉树的过程称为
线索化。ltag和rtag只是区分0或1数字的布尔型变量(用枚举常量定义)
- 一个节点的五个域:
lchild ltag data rtag rchild
– ltag 为0时指向该节点的左孩子,为1时指向该节点的前驱
–rtag 为0时指向该节点的右孩子,为1时指向该节点的后继
–要设置一个全局变量 pre 始终指向刚刚访问过的节点,即:若:指针p指向当前正在访问的节点,则pre 指向p的前驱,p指向pre的后继。这样便于增设线索。
要点:
1、为了方便,我们对二叉树线索化的时候要增加一个头节点。
2、头节点的lchild 指向二叉树 根节点,
其rchild 指向指向二叉树中序遍历时最后一个结点。
3、中序二叉树第一个结点的线索指针指向头节点,
4、初始化时,头节点的lchild、rchild 都指向头节点(自己),并将头节点的 ltag 置为Link :0,rtag置为Thread:1
–在访问p所指的节点时,所做的处理如下:
1):建立p的前驱检索 :
若p->lchild为空,则将其左标志域置为1,并令p->lchild指向其中序前驱pre
2):建立pre的后继检索
若pre->rchild为空,则将其右标志域置1,并令pre->rchild
指向其中序后继p
3):将pre指向p刚刚访问过的节点, 即 pre = p;这样,在p访问一个新节点时,pre为其前驱结点。
// 中序遍历线索化二叉树:
/* thread n. 线索,蛛丝马迹*/
typedef char ElemType ; //千万千万别忘了“;”啊!!!
typedef enum{Link, Thread} PointerTag ; 注意分号!!
typedef struct BiNode {
ElemType data;
struct BiNode *lchild,*rchild;
PointerTag ltag,rtag;
}*BiTree , BiNode ;
BiTree pre; //全局变量
/* BiTree p;和 BiNode * p是等价的(确定)*/
//**pre 是我们增加定义的头节点:在上一个函数里已经定义好了。
void In_Thread(BiTree p) {
if ( p ) {
In_Thread( p->lchild); //不断向左下查找
if ( !p->lchild ) { //没有左子树 p->ltag = Thread ; //=1.,加个线索
p->lchild = pre ; //看:要点2!
}
if ( !pre->rchild) { //没有右子树
pre->rtag = Thread;
pre->rchild = p;
}
pre = p;
In_Thread(p->rchild); //右子树线索化
}
}
/*整个脉络很清晰,复习数据结构也能很容易的看懂了,整个函数 严格按照In_Thread( p->lchild);
....... pre = p;
In_Thread(p->rchild);
*/
/*1、不断向左查找,直到程序中的if(p)即现实中的if(p->lchild)不成立(此时的p指向最底层、最左边的那个叶节点),结束这层递归。程序继续向下执行if(!p->lchild) 语句(程序中加粗部分)。
给这个最左最下的叶节点的ltag 赋值1,pre指向刚才访问过的节点,(p->lchild指向其中序前驱pre)。 程序继续向下执行if(!p->rchild) 语句,给rtag赋值为1,将p的地址赋给pre->rchild
中间部分代码做了这样的事情:
因为此时p结点的后继还没有访问到,因此只能对它的前驱结点pre的右指针rchild做判断,if(!pre->rchild)表示如果为空,则p就是pre的后继,于是pre->rchild = p,并且设置pre->rtag = Thread,完成后继结点的线索化。
T->lchild = pre 的理解:
因为是中序遍历,而pre是前驱,也就是通过pre能访问到 现在所访问的节点的前一个(中序)。当T是叶节点时,T->lchild是NULL,所以要把pre的地址赋给T->child.
但是。在执行左边的函数表达式时,编译通过,但发生了未知错误。为啥呢?? 在pre = T(第一次真正的赋值)之前,就已经要调用pre->rchild了( p->rchild为空),虽然在前面BiTree pre;但pre并没有一个具体的所指,所以我们需要一个头指针。*/
/*中序遍历线索化*/ /*这一段是重复的,可略过*/
/*不过要注意,In_Thread函数要写在InOrderThread前面*/
void In_Thread(BiTree p) {
if ( p ) {
In_Thread( p->lchild); //1、 //左子树线索化
if ( !p->lchild ) { //没有左孩子 p->ltag = Thread ; //=1.,加个线索
p->lchild = pre ;
}
if ( !pre->rchild) { //没有右孩子
pre->rtag = Thread;
pre->rchild = p;
}
pre = p;
In_Thread(p->rchild); //右子树线索化
}
int InOrderThreading(BiTree *Thrt,BiTree T)
{ /*Thrt是指向头节点的指针 */
*Thrt = (BiTree)malloc(sizeof(BiNode));
if(!(*Thrt)) exit (-1);
/*线索化头节点*/
(*Thrt)->ltag = Link ; (*Thrt)->rtag = Thread ;
(*Thrt)->rchild = *Thrt ;
if(T==NULL)
(*Thrt)->rchild = *Thrt ;
else{
(*Thrt)->lchild = T;//将头节点的左孩子指针指向根节点
pre = *Thrt; /*重要:将pre指向线索化的头节点*/
In_Thread(T); /*调用函数中序遍历线索化*/
/*将最后一个节点线索化*/
pre->rtag = Thread; /*修改标志域*/
pre->rchild = *Thrt; /*最后一个节点的后继指向头指针*/
(*Thrt) ->rchild = pre; /*将pre节点设为头节点的右孩子*/
}
return 1;
}
/**Thrt所指的头节点 的左孩子时根节点,右孩子指向自己,
ltag = 0 表示有左孩子,rtag = 1表示没有右孩子。
当二叉树为空时,把左孩子也指向自己;不为空时左孩子是头节点。
并令 pre 指向 *Thrt 这样pre就有所指啦~~~
为什么 *Thrt一定要把右孩子指向自己呢?因为上文提到,在
pre = T 之前,我们就调用了 pre->rchild, 因为pre没有所指,所以程序出现地址保护 从而不允许我们访问。
这里将*Thrt(也就是一开始pre所指向的)的右孩子指向自己,
从而保证整个程序的顺利运行。
总结程序,相当于时一个链表的扫描,所以时间复杂度是O(n)
当需要经常 遍历查找 某种 遍历序列 的前驱后继时,十分方便。
*/
/*下面是应用:
【例一】:
查找某节点(*p)的直接前驱/后继。*/
BiTree SearchPreOrder(BiNode *p) // <-【前驱】
{
BiNode *ptr;
if(p->ltag == Thread)
return p->lchild; /*如果p存在标志域,说明他没有左节点,lchild里面存放的是p的前驱*/
else{ //1、
ptr = p->lchild; //2、
while(ptr->rtag == Link) //3、
ptr = ptr->rchild; /*向下寻找*/
return ptr; /*pre就是最右下端节点*/
}
//1、 说明有左孩子 ,如果有左孩子,那就向下查找p的左子树的最右下端的节点
//2、 将左孩子指针赋给ptr,寻找左子树的最右下端节点
//3、 使劲向下寻找右孩子,直到 pre->rtag ==1说明没有右 孩子了
BiTree SearchPostOrder ( BiNode *p) //<-【后继】
{
if(p->rtag == Thread) //没有右孩子
return p->rchild; //rchild存放的是p的后继
else
{ //寻找右子树的最左下节点
p = p->rchild;
while(p->ltag == 0)
p = p->lchild;
return p;
}
}
/*线索二叉树的遍历*/
/*在线索树上进行遍历,只要先找到根节点,不断地向左向下找叶子,之后以叶子作为SearchPostOrder 的参数,不断地寻找后继即可。*/
//遍历线索树
void InOrderDispaly(BiTree T)
{
BiTree ptr = T ;
if ( ptr )
{
while(ptr->ltag==0)
ptr = ptr->lchild; //直到寻找到最左下节点
}
do{
printf("%3c",ptr->data);
ptr = SearchPostOrder(ptr);
}while(ptr);
}