线索二叉树
Posted moomcake
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线索二叉树相关的知识,希望对你有一定的参考价值。
将二叉树线索化,实际上就是将其变为一个循环链表,下面的代码是采用中序的线索化,遍历也是中序遍历,都是基于中序的。
在中序遍历序列中求某一结点的前驱和后继的方法:(1)求某一结点的后继:如果所考虑的结点有右孩子,那么就要从该右孩子开始,顺着右孩子的左孩子域找下去,一直到左孩子域为空为止,最后这个结点就是所考虑结点的后继;如果所考虑的结点没有右孩子,那么就要遍历二叉树。(2)求某一结点前驱:如果所考虑结点有左孩子,那么就要从该左孩子开始,顺着该左孩子的右孩子链域找下去,一直到右孩子的链域为空为止,最后这个结点就是所考虑结点的前驱;然后该结点没有左孩子,那么就要遍历二叉树。
下面代码中还有一个要说的就是用来辅助搜索的Address结构体数组,一开始我本来是乡写一个函数,直接就是在遍历的过程中搜索指定数值的结点,但是有时对有时错,搞了一个下午都不知怎么搞好,就放弃了,采用这个结构体数组的方法,简单一些,那个等以后老师讲的时候再看看老师会怎么实现吧,可能会有灵感。
中序遍历,建立线索二叉树
1 #include<iostream> 2 using namespace std; 3 4 class ThreadTree;//线索二叉树 5 6 class Thrnode 7 { 8 private: 9 int data; 10 int ltag; //ltag为0:表示lchild为一般二叉树的指针,指向其左子树 11 int rtag; //ltag为1:表示lchild为指向此结点的前驱的指针,rtag同理 12 Thrnode *lchild; 13 Thrnode *rchild; 14 15 friend class ThreadTree; 16 }; 17 18 int num=0;//记录结点数目 19 struct Address //辅助搜索 20 { 21 int data; 22 Thrnode *p; 23 }ad[100]; 24 25 class ThreadTree 26 { 27 private: 28 Thrnode *root; 29 public: 30 ThreadTree() 31 { 32 root=0; 33 } 34 35 Thrnode *Get_Root() 36 { 37 return root; 38 } 39 40 int Get_data(Thrnode *p) 41 { 42 return p->data; 43 } 44 45 Thrnode *Create_Tree(Thrnode *r) //先序建立二叉树 46 { 47 int d; 48 cout<<"输入数据(0代表空):"; 49 cin>>d; 50 if(d==0) 51 return 0; 52 else 53 { 54 num++; 55 r=new Thrnode; 56 r->ltag=0; 57 r->rtag=0; 58 r->data=d; 59 r->lchild=Create_Tree(r->lchild); 60 r->rchild=Create_Tree(r->rchild); 61 } 62 root=r; 63 return r; 64 } 65 66 void In_Threading(Thrnode *p,Thrnode *&pre) 67 { 68 //以指针p所指向的二叉树进行中序遍历,遍历过程中进行线索化 69 //pre指针是p的前驱指针 70 if(p) 71 { 72 In_Threading(p->lchild,pre);//左子树线索化 73 if(!p->lchild) //若p的左子树为空,给p结点加前驱线索 74 { 75 p->ltag=1; 76 p->lchild=pre; 77 } 78 else 79 p->ltag=0; 80 if(pre && !pre->rchild) 81 { 82 pre->rtag=1; 83 pre->rchild=p; 84 } 85 pre=p; 86 In_Threading(p->rchild,pre); //右子树线索化 87 } 88 89 } 90 91 Thrnode *InOrder_Threading(Thrnode *r) //将二叉树线索化,按中序遍历的顺序 92 { 93 //遍历二叉树,并将其中序线索化,其中,Thrt指针指向头结点,r指向根节点 94 Thrnode *pre=NULL; 95 Thrnode *Thrt; 96 Thrt=new Thrnode; 97 Thrt->ltag=0; 98 Thrt->rtag=1; 99 Thrt->rchild=Thrt;//初始化时,让头结点的右指针指向自己 100 if(!r) 101 Thrt->lchild=Thrt; //若为空树,左指针也指回自己 102 else 103 { 104 Thrt->lchild=r; 105 pre=Thrt; 106 In_Threading(r,pre); //pre是r的前驱指针 107 108 //从上面的函数出来时,pre指在最后一个结点处 109 pre->rchild=Thrt; 110 pre->rtag=1; 111 //最后一个结点的线索化 112 113 Thrt->rchild=pre; 114 } 115 cout<<"二叉树线索化 完成~~"<<endl; 116 return Thrt; 117 } 118 119 void InOrder_Thr(Thrnode *Thrt) //中序遍历线索二叉树 120 { 121 //遍历的同时,将相对应的值和结点指针存入结构体,便于搜索 122 int count=0; 123 Thrnode *r; 124 r=Thrt->lchild; 125 while(r!=Thrt) 126 { 127 while(r->ltag==0) 128 r=r->lchild; 129 cout<<r->data<<" "; 130 ad[count].data=r->data; 131 ad[count].p=r; 132 count++; 133 while(r->rtag==1 && r->rchild!=Thrt) 134 { 135 r=r->rchild; 136 cout<<r->data<<" "; 137 ad[count].data=r->data; 138 ad[count].p=r; 139 count++; 140 } 141 r=r->rchild; 142 } 143 } 144 145 Thrnode *Search(int d) //搜索 146 { 147 for(int i=0;i<num;i++) 148 if(ad[i].data==d) 149 return ad[i].p; 150 } 151 152 void Prior_Thr(Thrnode *Thrt,Thrnode *t,Thrnode *&p) //求结点前驱 153 { 154 //p返回结点t的前驱 155 p=t->lchild; 156 if(p==Thrt) 157 { 158 cout<<"给定的结点是第一个结点,不存在前驱"<<endl; 159 return ; 160 } 161 if(t->ltag==0) 162 while(p->rtag==0) 163 p=p->rchild; 164 } 165 166 void Next_Thr(Thrnode *Thrt,Thrnode *t,Thrnode *&p) //求结点后继 167 { 168 p=t->rchild; 169 if(p==Thrt) 170 { 171 cout<<"该结点为最后一个结点,无后继"<<endl; 172 return ; 173 } 174 if(p->rtag==0) 175 while(p->ltag==0) 176 p=p->lchild; 177 } 178 179 void Insert_Lchild(Thrnode *Thrt,Thrnode *t,int d) //作为左孩子插入 180 { 181 /* 182 将结点值为d的结点插入t后面,作为t的左孩子 183 如果t本来的无左孩子,那么直接插入即可 184 如果t有左孩子,那么: 185 t的左孩子在新的节点q插入后,作为q的左孩子,因此q->ltag=0; 186 将新结点q作为t的左孩子 187 求出新的结点q的前驱,修改q的前驱结点的rchild域,使它的后继为q 188 */ 189 190 Thrnode *q=new Thrnode; 191 q->data=d; 192 193 q->lchild=t->lchild; 194 q->ltag=t->ltag; 195 196 q->rchild=t; 197 q->rtag=1; 198 199 t->lchild=q; 200 t->ltag=0; 201 202 if(q->ltag==0) 203 { 204 Thrnode *p; 205 Prior_Thr(Thrt,q,p); 206 p->rchild=q; 207 } 208 //若q->rtag为0,那么就要找出q的前继 209 cout<<"插入为结点值为:"<<t->data<<"的左孩子成功"<<endl; 210 num++; 211 } 212 213 void Insert_Rchild(Thrnode *Thrt,Thrnode *t,int d) //作为右孩子插入 214 { 215 //思路和上面的左孩子一样 216 Thrnode *q=new Thrnode; 217 q->data=d; 218 219 q->rchild=t->rchild; 220 q->rtag=t->rtag; 221 222 q->lchild=t; 223 q->ltag=1; 224 225 t->rchild=q; 226 t->rtag=0; 227 228 if(q->rtag==0) 229 { 230 Thrnode *p; 231 Next_Thr(Thrt,q,p); 232 p->lchild=q; 233 } 234 cout<<"插入为结点值:"<<t->data<<"的右孩子成功"<<endl; 235 num++; 236 } 237 238 };
1 //t指向头结点,头结点左链lchild指向根结点,头结点右链rchild指向中序遍历的最后一个结点。 2 //中序遍历二叉线索树表示二叉树t 3 int InOrderThraverse_Thr(BiTree t) 4 { 5 BiTree p; 6 p = t->lchild; //p指向根结点 7 while(p != t) //空树或遍历结束时p == t 8 { 9 while(p->ltag == Link) //当ltag = 0时循环到中序序列的第一个结点 10 { 11 p = p->lchild; 12 } 13 printf("%c ", p->data); //显示结点数据,可以更改为其他对结点的操作 14 while(p->rtag == Thread && p->rchild != t) 15 { 16 p = p->rchild; 17 printf("%c ", p->data); 18 } 19 20 p = p->rchild; //p进入其右子树 21 } 22 23 return OK; 24 }
Code
说明:
(1)代码中,p = t->lchild;意思就是上图中的第一步,让p指向根结点开始遍历;
(2)while(p != t)其实意思就是循环直到图中的第四步出现,此时意味着p指向了头结点,于是与t相等(t是指向头结点的指针),结束循环,否则一直循环下去进行遍历操作;
(3)while(p-ltag == Link)这个循环,就是由A->B->D->H,此时H结点的ltag不是link(就是不等于0),所以结束此循环;
(4)然后就是打印H;
(5)while(p->rtag == Thread && p->rchild != t),由于结点H的rtag = Thread(就是等于1),且不是指向头结点。因此打印H的后继D,之后因为D的rtag是Link,因此退出循环;
(6)p=p->rchild;意味着p指向了结点D的右孩子I;
(7).....,就这样不断的循环遍历,直到打印出HDIBJEAFCG,结束遍历操作。
从这段代码可以看出,它等于是一个链表的扫描,所以时间复杂度为O(n)。
由于充分利用了空指针域的空间(等于节省了空间),又保证了创建时的一次遍历就可以终生受用后继的信息(意味着节省了时间)。所以在实际问题中,如果所用的二叉树需要经过遍历或查找结点时需要某种遍历序列中的前驱和后继,那么采用线索二叉链表的存储结构就是非常不错的选择。
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 #define ERROR 0 5 #define OK 1 6 7 typedef enum{Link, Thread} PointerTag; //link = 0表示指向左右孩子指针 8 //Thread = 1表示指向前驱或后继的线索 9 typedef struct BitNode 10 { 11 char data; //结点数据 12 struct BitNode *lchild; //左右孩子指针 13 struct BitNode *rchild; 14 PointerTag ltag; //左右标志 15 PointerTag rtag; 16 }BitNode, *BiTree; 17 18 BiTree pre; //全局变量,始终指向刚刚访问过的结点 19 20 //前序创建二叉树 21 void CreateTree(BiTree *t) 22 { 23 char ch; 24 scanf("%c", &ch); 25 26 if(ch == ‘#‘) 27 { 28 *t = NULL; 29 } 30 else 31 { 32 (*t) = (BiTree)malloc(sizeof(BitNode)); 33 if((*t) == NULL) 34 { 35 return; 36 } 37 (*t)->data = ch; 38 CreateTree(&((*t)->lchild)); 39 CreateTree(&((*t)->rchild)); 40 } 41 } 42 43 44 //t指向头结点,头结点左链lchild指向根结点,头结点右链rchild指向中序遍历的最后一个结点。 45 //中序遍历二叉线索树表示的二叉树t 46 int InOrderThraverse_Thr(BiTree t) 47 { 48 BiTree p; 49 p = t->lchild; //p指向根结点 50 while(p != t) 51 { 52 while(p->ltag == Link) //当ltag = 0时循环到中序序列的第一个结点 53 { 54 p = p->lchild; 55 } 56 printf("%c ", p->data); //显示结点数据,可以更改为其他对结点的操作 57 while(p->rtag == Thread && p->rchild != t) 58 { 59 p = p->rchild; 60 printf("%c ", p->data); 61 } 62 63 p = p->rchild; //p进入其右子树 64 } 65 66 return OK; 67 } 68 69 //中序遍历进行中序线索化 70 void InThreading(BiTree p) 71 { 72 if(p) 73 { 74 InThreading(p->lchild); //递归左子树线索化 75 if(!p->lchild) //没有左孩子 76 { 77 p->ltag = Thread; //前驱线索 78 p->lchild = pre; //左孩子指针指向前驱,这里是第3步 79 } 80 if(!pre->rchild) //没有右孩子 81 { 82 pre->rtag = Thread; //后继线索 83 pre->rchild = p; //前驱右孩子指针指向后继(当前结点p) 84 } 85 pre = p; 86 87 InThreading(p->rchild); //递归右子树线索化 88 } 89 } 90 //建立头结点,中序线索二叉树 91 int InOrderThread_Head(BiTree *h, BiTree t) 92 { 93 (*h) = (BiTree)malloc(sizeof(BitNode)); 94 if((*h) == NULL) 95 { 96 return ERROR; 97 } 98 99 (*h)->rchild = *h; 100 (*h)->rtag = Link; 101 102 if(!t) //如果为NULL 103 { 104 (*h)->lchild = *h; 105 (*h)->ltag = Link; 106 } 107 else 108 { 109 pre = *h; 110 (*h)->lchild = t; //第一步 111 (*h)->ltag = Link; 112 InThreading(t); //找到最后一个结点 113 pre->rchild = *h; //第四步 114 pre->rtag = Thread; 115 (*h)->rchild = pre; //第二步 116 } 117 } 118 119 int main(int argc, char **argv) 120 { 121 BiTree t; 122 BiTree temp; 123 124 printf("请输入前序二叉树的内容: "); 125 CreateTree(&t); //建立二叉树 126 InOrderThread_Head(&temp, t); //加入头结点,并线索化 127 printf("输出中序二叉树的内容: "); 128 InOrderThraverse_Thr(temp); 129 130 printf(" "); 131 return 0; 132 }
以上是关于线索二叉树的主要内容,如果未能解决你的问题,请参考以下文章