5.11.在线索二叉树中找前驱和后继


一.中序线索二叉树找中序后继:

中序线索二叉树中找指定结点的中序后继(注:此时的二叉树已经被线索化,而且是中序线索化)->

1.如果指定结点的右孩子指针已经被线索化:

p结点代表指定结点,如果指定结点的右孩子指针已经被线索化,那么指定结点的右孩子指针所指向的结点就是指定结点的中序后继,此时找指定结点的中序后继就会方便很多:

2.如果指定结点的右孩子指针还没有被线索化:

p结点代表指定结点,如果指定结点即p结点的右孩子指针并没有被线索化,那么就意味着该结点即p结点的右孩子指针指向一个结点即存在真正的右孩子(右子树非空),既然是找的中序后继即按照中序遍历来找一个结点的后继结点,中序遍历的访问顺序是左、根、右,现在已经知道指定结点即p结点有右孩子,所以按照中序遍历的规则,访问完当前结点即p结点后,需要再中序遍历右孩子,按照中序遍历的规则,在这个右子树中第一个被中序遍历的结点就是当前结点即p结点的后继结点,现在假设p结点只有一个右孩子,并且p结点的右孩子结点就是最终结点,也是叶子结点,显然这个右孩子结点就是p结点的后继结点:

但如果假设p结点的右孩子结点并不是叶子结点即p结点的右孩子结点下还有左/右孩子结点,根据中序遍历访问顺序左、根、右的规则来遍历p结点的右子树,最左下角的结点就是p结点的右子树中最先被访问的结点即p结点之后第一个被访问的结点:

紧接着上述图片,如果继续增加结点如下述图片,可知最左下角的结点就是p结点的右子树中最先被访问的结点即p结点之后第一个被访问的结点:

由此可知规律就是在中序线索二叉树找中序后继中p结点即当前结点的右子树当中最左下角的结点就是p结点即当前结点的后继结点:

3.中序线索二叉树找中序后继的代码:

注:此时的二叉树已经被线索化

  • ThreadNode是二叉树的结点类型,*p代表指定结点,ltag和rtag分别代表左线索标志和右线索标志,lchild和rchild分别代表左孩子指针和右孩子指针

  • *Firstnode函数是用来找以指定结点p为根结点的左、右子树中,第一个被中序遍历的结点->首先要知道中序遍历的顺序是"左根右",详情见"5.6.二叉树的先,中,后序遍历",因此中序遍历到的第一个结点一定是在左子树中,因此要不断地往左子树上找->在函数体中,while函数的循环条件是如果指定结点的左线索标志为0即指定结点的左孩子指针没有被线索化,意味着存在左子树,于是开始不断的访问指定结点左孩子指针上的结点,并赋值为指定结点,之后继续判断是否循环,如果指定结点的左线索标志不为0时即被线索化,意味着不存在左子树了,跳出循环,最终找到最左下角的结点

  • 二叉树进行中序遍历后可知最左下角的结点就是该二叉树被中序遍历后所访问的第一个结点

  • *Firstnode函数的核心就是不断地往左子树上找,因为以p结点为根结点的左、右子树中,如果采用中序遍历,根据中序遍历的规则"左、根、右"可知第一个被中序遍历的结点一定在p结点的左子树上

  • ThreadNode是二叉树的结点类型,*p代表指定结点,ltag和rtag分别代表左线索标志和右线索标志,lchild和rchild分别代表左孩子指针和右孩子指针

  • *Nextnode函数是用来在中序线索二叉树中找到指定结点p的中序后继结点(中序后继就是指定节结点的右子树上的结点)->在函数体中如果指定结点p的右线索标志为0即指定结点p的右孩子指针没有被线索化,意味着存在右子树,就调用Firstnode函数,其中把指定结点p的右子树传入Firstnode函数,最终就可以找到指定结点p的右子树中最左下角的结点(也就是找到指定结点的右子树中第一个被中序遍历的结点)即指定结点p的后继结点;如果不满足if条件,代表此时指定结点p的右线索标志为1,意味着p结点的右孩子指针被线索化,直接返回p结点的右孩子指针所指向的结点即后继线索(也就是p结点在遍历序列中下一个被访问的结点,详情见"5.10.二叉树的线索化"中的"中序线索化实例")

  • *Nextnode函数核心就是如果指定结点p有右子树,就在右子树中找第一个被中序遍历的结点(找到的第一个被中序遍历的结点就是p结点在遍历序列中下一个被访问的结点);如果指定结点p没有右子树,直接返回p结点的右孩子指针指向的后继线索即p结点在遍历序列中下一个被访问的结点->总之,*Nextnode函数可用来找到指定结点p在遍历序列中下一个被访问的结点

  • ThreadNode是二叉树的结点类型,*T是要被遍历的二叉树的根结点指针

  • Inorder函数用来对中序线索二叉树进行中序遍历(利用线索实现的非递归算法),函数体用到了一个for循环,起始值是ThreadNode *p=Firstnode(T),这里调用Firstnode函数,传入要被遍历的二叉树的根结点指针T,返回二叉树T第一个被中序遍历的结点(符合中序遍历中第一个被遍历的结点是最左下角的结点),并赋值给p,开始循环的条件是p不为NULL,循环体visit函数用来访问当前结点p,进入下一次循环时调用Nextnode(p),这里调用Nextnode函数,传入当前结点p,得出p结点在中序遍历的后继结点即中序遍历中p结点之后被访问的结点

  • Inorder函数并不需要递归调用函数,所以空间复杂度为O(1)


二.中序线索二叉树找中序前驱:

1.如果指定结点的左孩子指针已经被线索化:

p结点代表指定结点,如果指定结点的左孩子指针已经被线索化,那么指定结点的左孩子指针所指向的结点就是指定结点的中序前驱,此时找指定结点的中序前驱就会方便很多:

2.如果指定结点的左孩子指针还没有被线索化:

p结点代表指定结点,如果指定结点即p结点的左孩子指针并没有被线索化,那么就意味着该结点即p结点的左孩子指针指向一个结点即存在真正的左孩子(左子树非空),既然是找的中序前驱即按照中序遍历来找一个结点的前驱结点,中序遍历的访问顺序是左、根、右,指定结点即p结点此时处于根的位置,按照中序遍历的访问顺序可知指定结点即p结点的中序前驱是指定结点即p结点的左子树中按照中序遍历最后一个被访问的结点,假设p结点的左子树只有一个结点即叶子结点,那么左子树的这个结点就是p结点的前驱结点:

但如果假设p结点的左孩子结点并不是叶子结点即p结点的左孩子结点下还有左/右孩子结点,根据中序遍历访问顺序左、根、右的规则来遍历p结点的左子树,最右下角的结点就是p结点的左子树中最先被访问的结点即p结点的前驱结点:

紧接着上述图片,如果继续增加结点如下述图片,可知最右下角的结点就是p结点的左子树中最先被访问的结点即p结点的前驱结点:

由此可知规律就是在中序线索二叉树找中序前驱中p结点即当前结点的左子树当中最右下角的结点就是p结点即当前结点的前驱结点。

3.中序线索二叉树找中序前驱的代码:

注:此时的二叉树已经被线索化

  • ThreadNode是二叉树的结点类型,*p代表指定结点,ltag和rtag分别代表左线索标志和右线索标志,lchild和rchild分别代表左孩子指针和右孩子指针

  • *Lastnode函数是用来找以指定结点p为根结点的左、右子树中,最后一个被中序遍历到的结点->首先要知道中序遍历的顺序是"左根右",详情见"5.6.二叉树的先,中,后序遍历",因此中序遍历到的最后一个结点一定是在右子树中,因此要不断地往右子树上找->在函数体中,while函数的循环条件是如果指定结点p的右线索标志为0即指定结点p的右孩子指针没有被线索化,意味着指定结点p存在右子树,于是开始不断的访问指定结点p右孩子指针上的结点,并赋值为指定结点p,之后继续判断是否循环,如果指定结点的右线索标志不为0时即被线索化,意味着当前结点不存在右子树了,跳出循环,最终找到最右下角的结点

  • 二叉树进行中序遍历后可知最右下角的结点就是该二叉树被中序遍历后所访问的最后一个结点

  • *Lastnode函数的核心就是不断地往右子树上找,因为以p结点为根结点的左、右子树中,如果采用中序遍历,根据中序遍历的规则"左、根、右"可知最后一个被中序遍历的结点一定在p结点的右子树上

  • ThreadNode是二叉树的结点类型,*p代表指定结点,ltag和rtag分别代表左线索标志和右线索标志,lchild和rchild分别代表左孩子指针和右孩子指针

  • *Prenode函数是用来在中序线索二叉树中找到指定结点p的中序前驱结点(中序前驱就是指定节结点的左子树上的结点)->在函数体中如果指定结点p的左线索标志为0即指定结点p的左孩子指针没有被线索化,意味着存在左子树,就调用Lastnode函数,其中把指定结点p的左子树传入Lastnode函数,最终就可以找到指定结点p的左子树中最右下角的结点(也就是找到指定结点的左子树中最后一个被中序遍历的结点)即指定结点p的前驱结点;如果不满足if条件,代表此时指定结点p的左线索标志为1,意味着p结点的左孩子指针被线索化,直接返回p结点的左孩子指针所指向的结点即前驱线索(也就是p结点在遍历序列中前一个被访问的结点,详情见"5.10.二叉树的线索化"中的"中序线索化实例")

  • *Prenode函数核心就是如果指定结点p有左子树,就在左子树中找最后一个被中序遍历的结点(找到的最后一个被中序遍历的结点就是p结点在遍历序列中前一个被访问的结点);如果指定结点p没有左子树,直接返回p结点的左孩子指针指向的前驱线索即p结点在遍历序列中前一个被访问的结点->总之,*Prenode函数可用来找到指定结点p在遍历序列中前一个被访问的结点

  • ThreadNode是二叉树的结点类型,*T是要被遍历的二叉树的根结点指针

  • RevInorder函数用来对中序线索二叉树进行逆向中序遍历(利用线索实现的非递归算法),函数体用到了一个for循环,起始值是ThreadNode *p=Lastnode(T),这里调用Lastnode函数,传入要被遍历的二叉树的根结点指针T,返回二叉树T最后一个被中序遍历的结点(符合逆向中序遍历中第一个被遍历的结点是最右下角的结点),并赋值给p,开始循环的条件是p不为NULL,循环体visit函数用来访问当前结点p,进入下一次循环时调用Prenode(p),这里调用Prenode函数,传入当前结点p,得出p结点在中序遍历的前驱结点即中序遍历中p结点之前被访问的结点

  • RevInorder函数并不需要递归调用函数,所以空间复杂度为O(1)


三.先序线索二叉树找先序后继:

1.如果指定结点的右孩子指针已经被线索化:

p结点代表指定结点,如果指定结点的右孩子指针已经被线索化,那么指定结点的右孩子指针所指向的结点就是指定结点的先序后继,此时找指定结点的先序后继就会方便很多:

2.如果指定结点的右孩子指针还没有被线索化:

p结点代表指定结点,如果指定结点即p结点的右孩子指针并没有被线索化,那么就意味着该结点即p结点的右孩子指针指向一个结点即存在真正的右孩子(右子树非空),既然是找的先序后继即按照先序遍历来找一个结点的后继结点,先序遍历的访问顺序是根、左、右,现在已经知道指定结点即p结点有右孩子,但p结点是否有左孩子还不能确定,现在假设p结点有左孩子,按照先序遍历的规则来看,p结点的后继结点就是p结点的左子树中第一个被先序遍历的结点,因此如果p结点有左孩子的话,那么p结点的先序后继就是p结点的左孩子:

如果p结点没有左孩子的话,根据先序遍历的规则根、左、右,此时没有左孩子,就只会依次访问根、右,所以p结点的后继结点就是p结点的右子树当中第一个被先序遍历(访问)的结点:

显然,p结点的右孩子无论是是否是叶子结点,p结点的右子树的根结点就是p结点的右子树当中第一个被先序遍历(访问)的结点,因为先序遍历永远都是先访问根结点,因此,如果p结点没有左孩子,则p结点的先序后继为p结点的右子树的根结点:


四.先序线索二叉树找先序前驱:

1.如果指定结点的左孩子指针已经被线索化:

p结点代表指定结点,如果指定结点的左孩子指针已经被线索化,那么指定结点的左孩子指针所指向的结点就是指定结点的先序前驱,此时找指定结点的先序前驱就会方便很多:

2.如果指定结点的左孩子指针还没有被线索化:此时要想找到指定结点的先序前驱,就必须给结点增加一个父结点指针,指向父结点

p结点代表指定结点,如果指定结点即p结点的左孩子指针并没有被线索化,那么就意味着该结点即p结点的左孩子指针指向一个结点即存在真正的左孩子(左子树非空),既然是找的先序前驱即按照先序遍历来找一个结点的前驱结点,先序遍历的访问顺序是根、左、右,p结点的左子树和右子树当中的所有结点只可能是p结点的后继结点,而不可能是p结点的前驱结点,因此不可能在p结点的左子树和右子树当中找到p结点的前驱结点:(除非用之前的土办法,再次先序遍历该二叉树,遍历过程中记录当前结点和当前结点的前驱结点pre,如果当前结点等于指定结点,那么pre就是指定结点的前驱结点)

此时找不到p结点的前驱结点是因为每个结点只有指向它的左孩子指针和右孩子指针,如果发散思维,把二叉树改为三叉链表,也就是给各个结点设置一个指向它的父结点的指针,如果能找到p结点的父结点,那么能否找到p结点的先序前驱呢?

情况一:如果能找到指定结点即p结点的父结点,且p结点是p结点的父结点的左孩子结点

由于先序遍历的遍历顺序为根、左、右且p结点是p结点的父结点的左孩子结点,所以p结点是在p结点的父结点之后就被访问,因此p结点的父结点就是p结点的先序前驱即前驱结点:

情况二:如果能找到指定结点即p结点的父结点,且p结点是p结点的父结点的右孩子结点,该父结点的左孩子结点为空

根据先序遍历的规则,由于此时p结点的父结点没有左孩子结点,但p结点的父结点有右孩子结点,所以遍历顺序为根、右,且p结点是p结点的父结点的右孩子结点,所以p结点是在p结点的父结点之后就被访问,因此p结点的父结点就是p结点的先序前驱即前驱结点:

情况三:如果能找到指定结点即p结点的父结点,且p结点是p结点的父结点的右孩子结点,该父结点的左孩子结点不为空

由于先序遍历的遍历顺序为根、左、右且p结点是p结点的父结点的右孩子结点,那么p结点的先序前驱一定是在p结点的左兄弟子树即p结点的父结点的左子树当中,所以p结点的先序前驱为左兄弟子树即p结点的父结点的左子树中最后一个被先序遍历的结点:

情况四:如果指定结点p是根结点(没有父结点),按照先序遍历的顺序即根、左、右可知p结点即根结点是第一个被访问的结点,那么p结点就没有先序前驱

强调:上述四种情况找先序前驱是基于能够逆向找到当前结点的父结点

3.在二叉树中最后一个被先序遍历的结点是怎样的呢?

由于先序遍历的顺序是根、左、右,所以从根结点出发,如果根结点有右子树,那么右子树当中的结点肯定是最后被访问到的,因此从根结点出发,应该尽可能的按照向右的路径来找到最右下角的结点,如果最右下角的结点有一个左孩子结点,那么按照先序遍历的顺序即根、左、右的顺序来访问结点,这个左孩子结点就成了最后一个被先序遍历的结点,但如果该左孩子结点还有左孩子结点,那么这个左孩子结点就是最后一个被先序遍历的结点,以此类推,所以一棵二叉树里按照先序遍历的顺序,最后一个被访问的结点应该从根结点出发,优先往右边的路走,如果右边的路没有了,应该往左边的路走,之后再优先右边的路,没有右边的路走左边的路,用这样的方式找到最下面一层的靠右的结点,该结点就是二叉树在先序遍历中最后一个被访问的结点:


五.后序线索二叉树找后序前驱:

1.如果指定结点的左孩子指针已经被线索化:

p结点代表指定结点,如果指定结点的左孩子指针已经被线索化,那么指定结点的左孩子指针所指向的结点就是指定结点的后序前驱,此时找指定结点的后序前驱就会方便很多:

2.如果指定结点的左孩子指针还没有被线索化:

p结点代表指定结点,如果指定结点即p结点的左孩子指针并没有被线索化,那么就意味着该结点即p结点的左孩子指针指向一个结点即存在真正的左孩子(左子树非空),但p结点是否有右孩子不得而知,因此需要分类讨论:

情况一:指定结点即p结点有右孩子

此时指定结点即p结点有左孩子和右孩子,根据后序遍历的顺序左、右、根,可知p结点(此时就是根结点)的后序前驱一定是p结点的右子树当中按照后序遍历最后一个被访问到的结点,因此当指定结点即p结点有右孩子时,p的后续前驱为p结点的右子树当中按照后序遍历最后一个被访问到的结点:

情况二:指定结点即p结点没有右孩子

此时指定结点即p结点有左孩子,但没有右孩子,因此后序遍历的顺序为左、根,可知p结点(此时就是根结点)的后序前驱一定是p结点的左子树当中按照后序遍历最后一个被访问到的结点,因此当指定结点即p结点没有右孩子时,p的后续前驱为p结点的左子树当中按照后序遍历最后一个被访问到的结点:


六.后序线索二叉树找后序后继:

1.如果指定结点的右孩子指针已经被线索化:

p结点代表指定结点,如果指定结点的右孩子指针已经被线索化,那么指定结点的右孩子指针所指向的结点就是指定结点的后序后继,此时找指定结点的后序后继就会方便很多:

2.如果指定结点的右孩子指针没有被线索化:此时要想找到指定结点的后序后继,就必须给结点增加一个父结点指针,指向父结点

p结点代表指定结点,如果指定结点即p结点的右孩子指针并没有被线索化,那么就意味着该结点即p结点的右孩子指针指向一个结点即存在真正的右孩子(右子树非空),按照后序遍历的规则即左、右、根,p结点(此时是根结点)的左子树和右子树当中所有的结点都只可能是p结点的前驱结点,而不可能是p结点的后继结点,所以这种情况下没办法在p结点的左子树和右子树中找到p结点的后序后继:(除非用之前的土办法,再次后序遍历该二叉树,遍历过程中记录当前结点和当前结点的后继结点next,如果当前结点等于指定结点,那么next就是指定结点的后继结点)

此时找不到p结点的后继结点是因为每个结点只有指向它的左孩子指针和右孩子指针,如果发散思维,把二叉树改为三叉链表,也就是给各个结点设置一个指向它的父结点的指针,如果能找到p结点的父结点,那么能否找到p结点的后序后继呢?

情况一:如果能找到指定结点即p结点的父结点,且p结点是p结点的父结点的右孩子结点

按照后序遍历的顺序即左、右、根,无论p结点是否有左孩子结点和右孩子结点,p结点一定是p结点的父结点的右子树当中最后一个被访问的结点,因为p结点是该右子树的根结点,在访问完p结点之后就会访问p结点的父结点,因此p结点的后序后继就是p结点的父结点:

情况二:如果能找到指定结点即p结点的父结点,且p结点是p结点的父结点的左孩子结点,p结点的右兄弟为空即p结点的父结点没有右子树

由于p结点的右兄弟为空即p结点的父结点没有右子树,根据后序遍历的规则即左、右、根,此时遍历的顺序为左、根,显然,无论p结点是否有左孩子结点和右孩子结点,p结点一定是p结点的父结点的左子树当中最后一个被访问的结点,因为p结点是该左子树的根结点,在访问完p结点之后就会紧接着访问p结点的父结点,因此p结点的后序后继就是p结点的父结点:

情况三:如果能找到指定结点即p结点的父结点,且p结点是p结点的父结点的左孩子结点,p结点的右兄弟非空即p结点的父结点有右子树

按照后序遍历的规则即左、右、根,且p结点是p结点的父结点的左孩子结点,那么p结点的后继结点就应该是p结点的右兄弟子树(即p结点的父结点的右子树)当中,按照后序遍历的规则第一个被访问的结点(注:无论p结点是否有左孩子结点和右孩子结点,p结点一定是p结点的父结点的左子树当中最后一个被访问的结点,因为p结点是该左子树的根结点),一棵二叉树当中按照后序遍历第一个被访问的结点应该是从根结点出发,然后尽可能的往左走,如果左边的路没有了,但有右边的路,就继续往右走,之后有左边的路就优先往左走,没有就往右走,以此类推,最终找到最下面的叶子结点,这个叶子结点就是第一个被后序遍历的结点:

情况四:如果指定结点p是根结点(没有父结点),按照后序遍历的顺序即左、右、根可知p结点即根结点是最后一个被访问的结点,那么p结点就没有后序后继


七.总结:

对于中序线索二叉树,找某个结点的前驱和后继都是可以的,所以在中序线索二叉树中给出一个指定结点,往前中序遍历(找前驱即可)和往后中序遍历(找后继即可)都是可以实现的;

对于先序线索二叉树,找某个结点的后继是可以的,但是找某个结点的前驱却无法实现,除非使用三叉链表找到指定结点的父结点,或者使用土办法即重新先序遍历二叉树,这样就可以找到指定结点的前驱->因此在先序线索二叉树中,给出一个指定结点,就可以从这个指定结点开始进行一个正向的先序遍历,因为只能找到后继,而不能找到前驱;

对于后序线索二叉树,找某个结点的前驱是可以的,但是找某个结点的后继却无法实现,除非使用三叉链表找到指定结点的父结点,或者使用土办法即重新后序遍历二叉树,这样就可以找到指定结点的后继->因此在后序线索二叉树中,给出一个指定结点,就可以从这个指定结点开始进行一个逆向的后序遍历,因为只能找到前驱,而不能找到后继;


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值