这里主要是想写一下自己对于线索化过程中的递归理解
在讲解之前,先介绍几个概念
- 前驱,通俗的讲,就是在某种遍历条件下,A在B前面输出,所以,B的前驱就是A。
- 后继,同样的道理,A的后继也就是B了。(这里一定要理解清楚,因为后面程序的思路就是根据这里来写出的)
- 头节点,这里可以类比链表中的头节点,可以说是与树中的数据没有什么直接的联系,即用户是不知道有这么一个节点存在的。但是,为了方便后面代码的实现,我们在这里还是引入了这个概念。
- pre,这个是代码中指定的全局指针变量,始终指向刚刚访问过的节点。什么意思捏,就是说,p若刚刚访问过A,然后p就离开了,(这里的离开,指的是去访问别的节点了。若不懂没有关系,之后我们对着代码来讲。)那么,pre在p离开之前,pre也会指向A,然后p离开。注意,这里不是说是在函数内部,不会随着递归函数的进行,而被赋予新值。
进入主题啦,加油!
-
线索化二叉树。(至于,这里怎么定义二叉树的类型,我就不多说了,可以看书本上的定义即可)首先看一下这部分代码:
void InOrderThreading(BiThrTree& Thrt, BiThrTree T) { Thrt = (BiThrTree)malloc(sizeof(BiThrNode)); if (Thrt == NULL) { printf("内存分配失败\n"); exit(-1); } Thrt->data = -1;//头指针数据域为无效值-1 Thrt->RTag = Thread;//头指针有其后继 Thrt->rchild = Thrt;//暂时令其后继为自己 Thrt->LTag = Link;//头指针没有前驱 if (!T)//如果T为空树时,令其左指针指向自己 { Thrt->lchild = Thrt; } else { Thrt->lchild = T; pre = Thrt; InThreading(T); //线索化最后一个节点 pre->rchild = Thrt; pre->RTag = Thread; Thrt->rchild = pre; } } //end** //start** void InThreading(BiThrTree P) { if (P) { InThreading(P->lchild);//先线索化左子树 /* 如果满足该if语句,说明已经到了某个子树的左边的尽头 */ if (!P->lchild) { P->LTag = Thread; P->lchild = pre; } /* 不管上面if语句是否满足,相对P来说,其都是有一个前驱元素X存在的 从另一方面来说,就是X的后继就是P 而pre的定义就是P刚刚访问后的节点,对于中序遍历来说,pre就是P的前驱 所以,下一步应该计算pre的后继元素 */ if (!pre->rchild) { pre->RTag = Thread; pre->rchild = P; } pre = P;//因为P马上就要==P->rchild了,在此之前,用pre来来保存P访问节点 InThreading(P->rchild);//再线索化左子树 } } //end**
我认为难点就在 void InThreading(BiThrTree P)函数中,他是怎么就写出来的,我最近也一直在“研究”递归,被弄得云里雾里的。但是,我还是在这里写一哈自己的想法,一是为了方便之后的复习,二是希望万能的网友能和我一起交流这个问题,共同进步。
-
首先明白一点,我在这里实现的是中序遍历的线索化,所以,第一个要设置为Thread的节点必然是最左边的那个节点,即P->lchild==NULL。对应的代码就是if§这个判断语句,因为只有当该节点不为NULL时才可以继续走下去。InThreading(P->lchild);这条语句明显是一个递归语句,那么什么时候才能执行下面的语句捏。根据递归的定义可知,只有当最后一个InThreading(P->lchild)执行完毕后,就会返回到上一个InThreading(P->lchild)这里,然后接着往下执行。
-
现在的p是什么捏?是根的左子树中第一个没有左孩子的节点。所以,这个是我们要线索化的节点。自然的就会去写
if (!P->lchild) { P->LTag = Thread; P->lchild = pre; } // 这个代码。因为pre的定义可知,所以p->lchild应该赋值为pre。
-
那么,你找了p是否会有前驱(因为在之后的递归中,可能不满足if(!P->lchild)这个条件),那么自然你现在得去找pre的后继。这里就得用到我之前所讲的那个后继这个概念了。
-
所以现在去找pre的后继。执行代码
if (!pre->rchild) { pre->RTag = Thread; pre->rchild = P; }
-
后继线索也寻找完毕后,那么就要去看p有没有右孩子了。但是,在此之前,根据pre的定义可知,pre要始终指向p之前访问过的节点。所以,在p=p->rchild之前,先执行
pre=p;
然后执行
InThreading(P->rchild);
那么,这样这个 void InThreading(BiThree p)函数分析完毕。
可能还不是特别清楚,我现在拿一颗树来做个演示。下面还有我的实例代码,可以参考一下,对着这个图分析一下。(好吧,这个图画的太丑了,大家还是尽力理解吧,可以自己尝试画图,这样才可以深入理解递归)
这个是完整代码,可能对着分析一下
//start**
void InOrderTraverse_Thr(BiThrTree T)
{
BiThrTree P = T->lchild;//P指向根节点
while (T != P)
{
while (P->LTag == Link)
{
P = P->lchild;//中序遍历的老套路
}
printf("%5d", P->data);//所以此时该输出数据了
/*
进入这个while循环,代表该节点左边没有子树了,
右边也没有子树了,并且不是中序遍历的最后一个节点
因为最后一个节点的rchild指向头指针
*/
while (P->RTag == Thread && P->rchild != T)
{
P = P->rchild;
printf("%5d", P->data);
}
P = P->rchild;
}
}
//end**
//start**
void InOrderThreading(BiThrTree& Thrt, BiThrTree T)
{
Thrt = (BiThrTree)malloc(sizeof(BiThrNode));
if (Thrt == NULL)
{
printf("内存分配失败\n");
exit(-1);
}
Thrt->data = -1;//头指针数据域为无效值-1
Thrt->RTag = Thread;//头指针有其后继
Thrt->rchild = Thrt;//暂时令其后继为自己
Thrt->LTag = Link;//头指针没有前驱
if (!T)//如果T为空树时,令其左指针指向自己
{
Thrt->lchild = Thrt;
}
else
{
Thrt->lchild = T;
pre = Thrt;
InThreading(T);
//线索化最后一个节点
pre->rchild = Thrt;
pre->RTag = Thread;
Thrt->rchild = pre;
}
}
//end**
//start**
void InThreading(BiThrTree P)
{
if (P)
{
InThreading(P->lchild);//先线索化左子树
/*
如果满足该if语句,说明已经到了某个子树的左边的尽头
*/
if (!P->lchild)
{
P->LTag = Thread;
P->lchild = pre;
}
/*
不管上面if语句是否满足,相对P来说,其都是有一个前驱元素X存在的
从另一方面来说,就是X的后继就是P
而pre的定义就是P刚刚访问后的节点,对于中序遍历来说,pre就是P的前驱
所以,下一步应该计算pre的后继元素
*/
if (!pre->rchild)
{
pre->RTag = Thread;
pre->rchild = P;
}
pre = P;//因为P马上就要==P->rchild了,在此之前,用pre来来保存P访问节点
InThreading(P->rchild);//再线索化左子树
}
}
//end**