当前位置:   article > 正文

考研二战_数据结构_day11_线索二叉树_先序前驱怎么找

先序前驱怎么找

目录

前言

一、线索树

1 基本概念

2 中序线索化

3 先序线索化

4 后序线索化

二、找前驱、后继

1 中序找后继

2 中序找前驱

3 先序找后继

4 先序找前驱

5 后序找后继

6 后序找前驱

7 小结

 后记


前言

        线索树的代码属于想起来很复杂,但是实现起来就非常简单的东西。主要是在实现过程中需要注意不要转圈、以及是否需要处理最后一个结点。


一、线索树

1 基本概念

        有N个结点的二叉树中,会有N+1个空链域(推理出来的),可以把这些空的链域用来存储前驱与后继。

2 中序线索化

        线索化就是在对结点进行visit操作时,顺便完成的操作。线索化过程中,最重要的就是找到当前结点的前驱线索,以及前一个结点的后继线索

        换句话说,线索化分为两部分操作,一个处理当前结点,一个处理上一个访问的结点。因此可以定义一个指针pre,用于表示上一个访问的结点,初始置为NULL;除此以外,为了将结点的线索指针与孩子指针相区别,还可以定义一个Ltag与Rtag,用0表示孩子结点,用1表示线索结点。

        如上图所示,这是中序线索化后的结果,黄色线表示前驱线索,紫色表示后继线索。在线索化过程中记当前结点名为root:

        1、若root->LNode == NULL,则将其线索化,将root的左孩子指针指向pre;

        2、若pre->RNode == NULL,则将其线索化,将pre的右孩子指针指向root;

        3、将pre指向root。

  1. typedef int ElemType;
  2. typedef struct TreeNode {
  3. ElemType data;
  4. struct TreeNode *LNode, *RNode;
  5. int Ltag, Rtag;
  6. } BiNode, *BiTree;
  7. BiNode *pre = NULL; //定义全局变量pre
  8. void Thread(BiNode* root) {
  9. root->Ltag = 0;
  10. root->Rtag = 0;
  11. if(root->LNode == NULL) {
  12. root->LNode = pre;
  13. root->Ltag = 1;
  14. }
  15. if(pre != NULL && pre->RNode == NULL) {
  16. pre->RNode = root;
  17. pre->Rtag = 1;
  18. }
  19. pre = root;
  20. }
  21. bool MidThread(BiNode* root) {
  22. if(root) {
  23. MidThread(root->LNode);
  24. Thread(root);
  25. MidThread(root->RNode);
  26. }
  27. return true;
  28. }
  29. int main() {
  30. //定义一棵空树
  31. BiTree root = NULL;
  32. /*中间省略构建过程*/
  33. MidThread(root);
  34. if(pre->RNode == NULL) { //处理最后一个结点
  35. pre->Rtag = 1;
  36. }
  37. return 0;
  38. }

        如代码所示,线索化过程与遍历过程可以说一模一样,只是需要注意处理最后一个结点。

3 先序线索化

         与先序遍历方式一样,但是若是不经过修改,出现如下情况时会出现转圈现象。

出现转圈的主要原因是:当访问结点B的左孩子结点时,由于已经对结点B进行了线索化,导致左孩子指针已经是线索指针了,因此只有当左孩子指针不为线索指针时,才能使用递归,访问其左孩子结点。

  1. bool PreThread(BiNode* root) {
  2. if(root) {
  3. Thread(root);
  4. if(root->Ltag == 0) {
  5. PreThread(root->LNode);
  6. }
  7. PreThread(root->RNode);
  8. }
  9. }
  10. int main() {
  11. PreThread(root);
  12. if(pre->RNode == NULL) { //处理最后一个结点
  13. pre->Rtag = 1;
  14. }
  15. return 0;
  16. }

4 后序线索化

        根据先序线索化,是因为在访问结点的左孩子结点时,其指针已经是线索指针,才会导致转圈的问题,这种情况是先序特有。考虑访问右孩子结点时,由于并不会对当前结点的右孩子指针进行线索化,而是对上一个结点的右孩子指针进行线索化,所以不需要考虑当前结点已经线索化。

        因此后序遍历怎么遍历,后序线索化就怎么线索化,注意最后一个结点的处理即可。

  1. bool PreThread(BiNode* root) {
  2. if(root) {
  3. Thread(root);
  4. if(root->Ltag == 0) {
  5. PreThread(root->LNode);
  6. }
  7. PreThread(root->RNode);
  8. }
  9. }
  10. int main() {
  11. PostThread(root);
  12. if(pre->RNode == NULL) { //处理最后一个结点
  13. pre->Rtag = 1;
  14. }
  15. return 0;
  16. }

二、找前驱、后继

        在二叉树已经线索化的基础上,如何快速找到一个结点的前驱、后继?如何进行遍历?

1 中序找后继

        在使用中序线索化后,若需要找到一个结点 p 的后继会有两种情况:

        1、p->Rtag == 1,线索指针,其后继一定是右孩子指针指向结点(也可能是NULL);

        2、p->Rtag == 0,未线索化,说明其右子树,则需要找到右子树最左边的结点即可

  1. //找到root为根结点的树中序遍历的第一个结果
  2. BiNode* FirstNode(BiNode* root) {
  3. while(root->Ltag == 0) root = root->LNode;
  4. return root;
  5. }
  6. //找到root的后继结点
  7. BiNode* NextNode(BiNode* root) {
  8. if(root->Rtag == 1) return root->RNode;
  9. else return FirstNode(root->RNode);
  10. }
  11. //使用中序线索化进行中序遍历
  12. void MidVisit(BiTree root) {
  13. for(BiNode* p = FirstNode(root); p != NULL; p = NextNode(p)) {
  14. visit(p);
  15. }
  16. }

2 中序找前驱

        中序找前驱方法与中序找后继的方法一模一样。

  1. //找到root为根结点的树中序遍历的最后一个结果
  2. BiNode* LastNode(BiNode* root) {
  3. while(root->Rtag == 0) root = root->RNode;
  4. return root;
  5. }
  6. //找到root的前驱结点
  7. BiNode* PreNode(BiNode* root) {
  8. if(root->Ltag == 1) return root->LNode;
  9. else return LastNode(root->LNode);
  10. }
  11. //使用中序线索化进行中序遍历(倒置顺序)
  12. void MidVisit2(BiTree root) {
  13. for(BiNode* p = LastNode(root); p != NULL; p = PreNode(p)) {
  14. visit(p);
  15. }
  16. }

3 先序找后继

        依旧是两种情况:

        1、当root->Rtag == 1时,后继为其右孩子结点

        2、当root->Rtag == 0时,必有右孩子结点,此时分为两种小情况:

                (1)存在左孩子结点,后继即是左孩子结点

                (2)不存在左孩子结点,后继则是右孩子结点

        总结一下,就是若存在左孩子结点,则后继是左孩子结点否则就是右指针指向结点

  1. BiNode* PreNextNode(BiNode* root) {
  2. if(root->Ltag == 0) return root->LNode;
  3. else return root->RNode;
  4. }
  5. void PreVisit(BiTree root){
  6. for(BiNode* p = root; p != NULL; p = PreNextNode(p)){
  7. visit(p);
  8. }
  9. }

4 先序找前驱

        除非使用三叉树,即除了左右指针,还有一个指向父结点的指针,不然很难找到先序线索化的树中任一结点的前驱

        还是分为两种情况:

        1、root->Ltag == 1,则当前结点的前驱为其左指针指向结点。

        2、root->Ltag == 0,则当前结点必有左孩子结点,但是先序遍历情况下,当前结点的所有子结点一定不可能是其前驱。故需要分为4种情况分析

                (1)当前结点是根结点,不存在前驱

                (2)当前结点有父结点,且当前结点是左孩子结点,则其前驱是父结点。

                (3)当前结点有父结点,且当前结点是右孩子结点,且无兄弟结点,则其前驱是父结点

                (4)当前节点不仅是父结点的右孩子结点,也存在兄弟节点,则其前驱为兄弟节点先序遍历的最后一个结点

        虽然说是有4种情况来考虑,但是首先要找到当前结点的父结点,所以只能通过遍历,有点麻烦,所以我就只是看看原理,就不实现了。

5 后序找后继

        后序找后继与先序找前驱一个原理,也是极难找到。

        1、当root->Rtag == 1时,当前结点的后继即是其右指针指向结点。

        2、当root->Rtag == 0时,当前结点必有右孩子结点,但是在后序遍历的情况下,当前结点的所有子结点一定不可能是其后继。依旧分为四种情况:

                (1)当前结点是根结点,不存在后继

                (2)当前结点有父结点,且当前结点是右孩子结点,则其后继是父结点。

                (3)当前结点有父结点,且当前结点是左孩子结点,且无兄弟结点,则其后继是父结点

                (4)当前节点不仅是父结点的左孩子结点,也存在兄弟节点,则其前驱为兄弟节点后序遍历的第一个结点

        虽然说是有4种情况来考虑,但是首先要找到当前结点的父结点,所以只能通过遍历,有点麻烦,所以我就只是看看原理,就不实现了。

6 后序找前驱

        分为两种情况考虑:

        1、当root->Ltag == 1时,当前结点的前驱结点就是其左指针指向结点。

        2、当root->Ltag == 0时,当前结点必有左孩子结点,依旧分为两种小情况:

                (1)存在右孩子结点,前驱即是右孩子结点

                (2)不存在右孩子结点,前驱则是左孩子结点

        总结一下,就是若存在右孩子结点,则后继是右孩子结点否则就是左指针指向结点

  1. BiNode* PostPreNode(BiNode* root) {
  2. if(root->Rtag == 0) return root->RNode;
  3. else return root->LNode;
  4. }
  5. //后序遍历的倒置
  6. void PostVisit(BiTree root) {
  7. for(BiNode* p = root; p != NULL; p = PostPreNode(p)) {
  8. visit(p);
  9. }
  10. }

7 小结

        根据线索二叉树寻找前驱、后继时,先序不能找前驱,后序不能找后继(除非使用三叉链表或者从头遍历)。虽然我并没有进行实现,但是先找前,后找后是如何找的,一定要会推理出来


 后记

        线索化主要就先序线索化时要注意不要绕圈

        先序线索化很难找前驱,后序线索化很难找后继,难在要找到当前结点的父结点但是要会推理出如何寻找的4种情况。

        明天定个小目标,看到哈夫曼树。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/650659
推荐阅读
相关标签
  

闽ICP备14008679号