数据结构 线索二叉树 c++

以二叉链表来作为储存结构的时候,只能找到左右孩子的信息,不能直接得到结点的前驱和后继信息,这种信息只有在遍历的过程中才能实现。在n个结点的二叉链表中必定存在n+1个空链域。可以用这些空链域来保存这些信息;做以下规定:若结点有左子树,则lchild指向其左孩子,若没有左孩子则指向其前驱;若结点有右子树,则rchild指向其右子树,否则指向其后继;为了标志指针是线索还是指针,需要添加两个标志位来表示指针的性质

lchild LTag rchild RTag

LTag,RTag是枚举类对象, 值为0的时候表示指针, 值为1表示线索

 1 typedef enum PointerTag{Link, Thread}; 

当我们能知道结点的前驱和后继信息的时候,只要找到序列中的第一个结点,然后依次查找后继结点,直到后继为空时而止;

怎么在二叉树中求结点的后继信息?

对于右孩子为空的结点来说,其右孩子指针指向其后继。但是当结点的右孩子不为空的时候,怎么找到其后继信息?根据后序遍历的规定可以直到,结点的后继结点应该是遍历其右子树最左下角的结点。当结点的左孩子为空的时候,左孩子指针指向其前驱, 当左孩子不为空的时候, 结点的前驱是遍历左子树的最后一个结点,也就是左子树最右下角的结点;(这些都是针对中序遍历而言)

对线索树的结点做一下定义, 为了简便,结点类型用int类型

1 typedef struct BiThrNode{
2     int val;
3     struct BiThrNode *lchild, *rchild;
4     PointerTag LTag, RTag;
5 }BiThrNode, *BiThrTree;

对二叉树进行线索化:pre是上一个访问的结点,pre的初始值指向表头结点(即后面的Thrt,其左孩子指向根节点); 线索化的过程是一个从叶子结点到根节点的过程,从左子树到右子树的过程;根据上面的分析可以知道,当一个结点的孩子为null的时候,就指向前驱或者后继。 调用这个函数后除最后一个结点,结点的指针都指向孩子结点或者前驱后继。

 1 void InThreading(BiThrTree p, BiThrTree& pre){
 2     if(p){
 3         InThreading(p->lchild, pre);    //左子树线索化
 4         if(!p->lchild){                    //p左孩子为null,则指向pre,指向p的前驱
 5             p->LTag = Thread;
 6             p->lchild = pre;
 7         }else p->LTag = Link;
 8         if(!pre->rchild){                //pre右孩子为null,则指向p,表示p是pre的后驱
 9             pre->RTag = Thread;
10             pre->rchild = p;
11         }else pre->RTag = Link;
12         pre = p;                        //更新p的位置
13         InThreading(p->rchild, pre);    //右子树线索化
14     }
15 }

建立一个循环到线索树:Thrt是一个头结点,左孩子指向根节点, 右孩子指向树的最后一个结点。又让树的最后一个结点右孩子指向Thrt。这样就构成一个循环的线索树。当对树进行中序遍历的时候,若某一个结点的右孩子指向Thrt,则表示遍历完成

 1 void InOrderThreading(BiThrTree& Thrt, BiThrTree& T){
 2     Thrt = (BiThrNode*) malloc(sizeof(BiThrNode));
 3     Thrt->LTag = Link; Thrt->RTag = Thread;
 4     Thrt->rchild = Thrt;
 5     BiThrTree pre;
 6     if(!T) Thrt->lchild = Thrt;
 7     else{
 8         Thrt->lchild = T;
 9         pre = Thrt;
10         InThreading(T, pre);                    //中序遍历线索化
11         pre->rchild = Thrt; pre->RTag = Thread; //最后一个结点线索化
12         Thrt->rchild = pre;
13     }
14 }

根据线索树进行中序遍历:由上面的分析可以写出下面的中序遍历程序,先找到要遍历的第一个结点,在中序遍历中,即数的最左下角的结点。找到第一个结点后就根据当前节点的后继结点来进行遍历;当结点的右孩子指针不指向后继节点的时候,这继续访问当前结点的右子树(由中序遍历先遍历左子树可以知道, 当访问的结点有右孩子的时候,其右孩子一定已经访问完成),重复上面的过程就遍历所有的结点

 1 void InOrderTraverse(BiThrTree Thrt){
 2     BiThrNode *p = Thrt->lchild;    //让p指向树的根节点
 3     while(p!=Thrt){
 4         while(p->LTag==Link) p = p->lchild;    //指向树的最左下方
 5         cout<<p->val<<" ";
 6         while(p->RTag==Thread && p->rchild!=Thrt){  //根据后继结点进行遍历
 7             p = p->rchild;
 8             cout<<p->val<<" ";
 9         } //接待右孩子不为空的时候,退出循环,继续访问当前结点的右子树
10         p = p->rchild;  //
11     }
12     cout<<endl;
13 }

在将二叉树线索化之前,需要建立一个二叉树,这里通过递归的方式来建立一棵树

 1 BiThrTree CreateBiTree(BiThrTree T, int val){
 2     if(!T){
 3         T = (BiThrNode*) malloc(sizeof(BiThrNode));
 4         T->val = val;
 5         T->lchild = T->rchild = NULL;
 6         return T;
 7     }
 8     if(val<T->val) T->lchild = CreateBiTree(T->lchild, val);
 9     if(val>T->val) T->rchild = CreateBiTree(T->rchild, val);
10     return T;
11 }

完整代码

把数的结点信息按照,层序遍历的顺序储存在数组t之中,建立通过CreateBiTree()建立二叉树, 再通过inorder()来验证二叉树建立是否正确,这里给出的例子,如果建立二叉树正确,二叉遍历的结果应该是一个从1到7的升序数列; 然后验证上面的线索二叉树的构造过程是否正确,先通过InorderThreading来将二叉树线索化, 然后再通过InorderTrverse()来验证;

 1 #include<iostream>
 2 using namespace std;
 3 /*
 4     author: Lai XingYu
 5       date: 2018/5/18
 6   describe: threaded binary tree
 7 */
 8 
 9 typedef enum PointerTag{Link, Thread}; //标志指针类型,前者表示指针, 后者表示线索
10 typedef struct BiThrNode{
11     int val;
12     struct BiThrNode *lchild, *rchild;
13     PointerTag LTag, RTag;
14 }BiThrNode, *BiThrTree;
15 
16 /*
17     对二叉树线索化, pre表示上一个访问的结点
18 */
19 void InThreading(BiThrTree p, BiThrTree& pre){
20     if(p){
21         InThreading(p->lchild, pre);    //左子树线索化
22         if(!p->lchild){                    //p左孩子为null,则指向pre,指向p的前驱
23             p->LTag = Thread;
24             p->lchild = pre;
25         }else p->LTag = Link;
26         if(!pre->rchild){                //pre右孩子为null,则指向p,表示p是pre的后驱
27             pre->RTag = Thread;
28             pre->rchild = p;
29         }else pre->RTag = Link;
30         pre = p;                        //更新p的位置
31         InThreading(p->rchild, pre);    //右子树线索化
32     }
33 }
34 
35 /*
36     Thr是头结点, 其左孩子指向根节点, 右孩子指向树的最后一个结点
37     树的最后一个结点的右孩子指向THr,构成一个回环
38 */
39 void InOrderThreading(BiThrTree& Thrt, BiThrTree& T){
40     Thrt = (BiThrNode*) malloc(sizeof(BiThrNode));
41     Thrt->LTag = Link; Thrt->RTag = Thread;
42     Thrt->rchild = Thrt;
43     BiThrTree pre;
44     if(!T) Thrt->lchild = Thrt;
45     else{
46         Thrt->lchild = T;
47         pre = Thrt;
48         InThreading(T, pre);                    //中序遍历线索化
49         pre->rchild = Thrt; pre->RTag = Thread; //最后一个结点线索化
50         Thrt->rchild = pre;
51     }
52 }
53 
54 void InOrderTraverse(BiThrTree Thrt){
55     BiThrNode *p = Thrt->lchild;    //让p指向树的根节点
56     while(p!=Thrt){
57         while(p->LTag==Link) p = p->lchild;    //指向树的最左下方
58         cout<<p->val<<" ";
59         while(p->RTag==Thread && p->rchild!=Thrt){
60             p = p->rchild;
61             cout<<p->val<<" ";
62         }
63         p = p->rchild;
64     }
65     cout<<endl;
66 }
67 
68 BiThrTree CreateBiTree(BiThrTree T, int val){
69     if(!T){
70         T = (BiThrNode*) malloc(sizeof(BiThrNode));
71         T->val = val;
72         T->lchild = T->rchild = NULL;
73         return T;
74     }
75     if(val<T->val) T->lchild = CreateBiTree(T->lchild, val);
76     if(val>T->val) T->rchild = CreateBiTree(T->rchild, val);
77     return T;
78 }
79 
80 void inorder(BiThrTree T){
81     if(!T) return;
82     if(T->lchild) inorder(T->lchild);
83     cout<<T->val<<" ";
84     if(T->rchild) inorder(T->rchild);
85 }
86 
87 int main(){
88     int t[] = {4,2,5,1,3,6,7}, i;
89     BiThrTree T = NULL, Thrt;
90     for(i=0; i<7; i++) T = CreateBiTree(T, t[i]);
91     inorder(T);
92     cout<<endl;
93     InOrderThreading(Thrt, T);
94     InOrderTraverse(Thrt);
95 return 0;}

 在准备408考试的时候接触到这个线索二叉树,理解还是有些吃力;不能理解的是,这样的储存结构的意义在哪里?就算保存了前驱后继信息,也到先找到该节点, 此外,根据实现的过程来看, 当二叉树建立之后, 若要插入新的节点又要重新对二叉树进行线索化, 这个开销也是不小的

后序线索树的构造

后序线索树的构造比,中序线索树更为复杂,可以分为以下四种情况

  1. 如果节点为根节点,则后继结点为空
  2. 结点是双亲的右孩子,或者是左孩子且没有兄弟结点, 则后继结点是双亲结点
  3. 结点为双清结点的左孩子,且有兄弟结点,则后继结点双亲右子树按照后序遍历的第一个结点。
  4. 结点为双亲的右孩子,后继为双亲结点

猜你喜欢

转载自www.cnblogs.com/mr-stn/p/9058000.html