数据结构 5-3-3 二叉树的线索化
一、概念
传统的二叉树,只表示出了二叉树的父子关系,而缺少一种线性的联系,因此引入了二叉线索树,以便加快查找前缀和后继的速度,主要是利用二叉树中没有利用到的节点指针。如果指针没有指向节点,就让左向指针指向节点的前驱节点,让右向指针指向节点的后继节点,由于做出了改动,所以需要在每个节点的结构体里面添加两个标记,用于区分指向的到底是节点还是前驱后继。
struct TNode{int data;int ltag,rtag;struct TNode *left;struct TNode *right;
};
根据遍历方式的不同,节点的前驱后继也会不一样,所以延伸出三种构建二叉线索树的方法,本质上构建二叉线索树的过程就是遍历一遍二叉树的过程,在遍历过程中记录前一个节点,以此来实现前驱后继的指向。
线索化的过程一定是基于前驱后缀的,理顺好前驱后缀的关系,把没有指向的空指针位置做好调整,就能得到线索化的二叉树。先画出图再以图为基础构建代码。
二、中序线索化
先从中序线索化开始,维护一个pre用于指向上一个节点,初始值为NULL,从中序遍历的过程可以看出,先访问的节点应该是整个二叉树最左下的节点,所以先采用递归的方式,一直向左下方向移动,当没有左孩子的时候遍历到了最左下的节点,此时最左下节点的左孩子指针根据定义应该指向该节点的前驱,即pre指针所指的节点。由于当前pre指向的是NULL,所以不对其做操作,只有当pre指向的节点不为空且其右孩子指针空,才修改pre指针指向的节点的右孩子指针让其指向当前节点,即找到了pre指针所指结点的后继节点。将pre移动到当前节点的位置,再遍历节点的右子树部分。重复这个过程直到全部节点遍历一次,最后将中序遍历的最后一个节点即最右下的节点的后继指向空。这样完成的只是线索化,但是输出时如果要利用添加的线索,就需要改一下代码。
void InThread(struct TNode * &p,struct TNode * &pre)
{if(p!=NULL){InThread(p->left,pre);if(p->left==NULL){p->left=pre;p->ltag=1;}if(pre!=NULL&&pre->right==NULL){pre->right=p;pre->rtag=1;}pre=p;InThread(p->right,pre);}
}
void creatInThread()
{struct TNode *pre;pre=NULL;if(t.root!=NULL){InThread(t.root,pre);pre->right=NULL;pre->rtag=1;}
}
输出时,根据中序的规则,先找到最左下的节点,之后如果节点右孩子指针指向的是后继元素,就直接移向后继节点,如果右孩子指针指向的是右孩子节点,那么就找右子树的最左边的节点,重复这个过程直到为空。其实在输出的时候,前驱是没什么卵用的,不可能根据前驱去找一个已经访问过的节点,一般是利用后继,如果有后继即直接跳转,没有就找右子树的最左下节点,相当于笨办法去找后继。
struct TNode * FirstNode(struct TNode *p)
{while(p->ltag==0)p=p->left;return p;
}
struct TNode * NextNode(struct TNode *p)
{if(p->rtag==0)return FirstNode(p->right);elsereturn p->right;
}
void InOrder()
{for(struct TNode *p=FirstNode(t.root);p!=NULL;p=NextNode(p))printf("%d ",p->data);
}
完整版代码如下,层序建树后中序线索化:
#include<bits/stdc++.h>
using namespace std;
struct Tree{struct TNode* root;int size;
};
struct Tree t;
struct TNode{int data;int ltag,rtag;struct TNode *left;struct TNode *right;
};
int num[1005];
int n;
void creat()
{queue<TNode *> q;struct TNode *temp;temp=(struct TNode*)malloc(sizeof(struct TNode));temp->data=num[0];temp->left=NULL;temp->ltag=0;temp->right=NULL;temp->rtag=0;t.root=temp;q.push(temp);int i=1;while(i<n){temp=q.front();q.pop();temp->ltag=0;struct TNode *l;l=(struct TNode*)malloc(sizeof(struct TNode));l->data=num[i];l->left=NULL;l->ltag=0;l->right=NULL;l->rtag=0;i++;temp->left=l;q.push(l);if(i==n)break;temp->rtag=0;struct TNode *r;r=(struct TNode*)malloc(sizeof(struct TNode));r->data=num[i];r->left=NULL;r->ltag=0;r->right=NULL;r->rtag=0;i++;temp->right=r;q.push(r);}
}
void InThread(struct TNode * &p,struct TNode * &pre)
{if(p!=NULL){InThread(p->left,pre);if(p->left==NULL){p->left=pre;p->ltag=1;}if(pre!=NULL&&pre->right==NULL){pre->right=p;pre->rtag=1;}pre=p;InThread(p->right,pre);}
}
void creatInThread()
{struct TNode *pre;pre=NULL;if(t.root!=NULL){InThread(t.root,pre);pre->right=NULL;pre->rtag=1;}
}
struct TNode * FirstNode(struct TNode *p)
{while(p->ltag==0)p=p->left;return p;
}
struct TNode * NextNode(struct TNode *p)
{if(p->rtag==0)return FirstNode(p->right);elsereturn p->right;
}
void InOrder()
{for(struct TNode *p=FirstNode(t.root);p!=NULL;p=NextNode(p))printf("%d ",p->data);
}
int main()
{scanf("%d",&n);for(int i=0;i<n;i++)scanf("%d",&num[i]);creat();creatInThread();InOrder();return 0;
}
三、前序线索化
根据上一篇的总结,前序中序最好改了,换换顺序就可以,线索化也是这样,还是先从前序遍历的过程开始找,第一个访问的节点肯定是根节点,每次都先访问到达的节点的数据,所以不能像中序那样子直接上来就暴力移动,移动应该放在对当前节点的操作之后,顺序为:判断当前节点的有没有左孩子,没有则让其指向前驱,然后判断当前节点是不是谁的后继,之后将pre调整为当前节点,之后先左移递归再右移递归。这里一定要加一个判断,只有当左右孩子为节点时才移动,否则会出现死循环的现象。重复过程直到全部节点遍历一边,最后让最右下的节点指向空即可。
void InThread(struct TNode * &p,struct TNode * &pre)
{if(p!=NULL){if(p->left==NULL){p->left=pre;p->ltag=1;}if(pre!=NULL&&pre->right==NULL){pre->right=p;pre->rtag=1;}pre=p;if(pre->ltag==0)InThread(p->left,pre);if(pre->rtag==0)InThread(p->right,pre);}
}
void creatInThread()
{struct TNode *pre;pre=NULL;if(t.root!=NULL){InThread(t.root,pre);pre->right=NULL;pre->rtag=1;}
}
输出时,就比较简单了,从根节点开始,如果有左孩子节点就移向左孩子,没有就直接移向后继,因为前序遍历时左孩子的后继直接就是右孩子,不管右指针指向的是右孩子还是后继元素,都可以直接移向有指针的位置。
struct TNode * NextNode(struct TNode *p)
{if(p->ltag==0)return p->left;else return p->right;
}
void InOrder()
{for(struct TNode *p=t.root;p!=NULL;p=NextNode(p))printf("%d ",p->data);
}
完整代码如下,层序建树之后前序线索化:
#include<bits/stdc++.h>
using namespace std;
struct Tree{struct TNode* root;int size;
};
struct Tree t;
struct TNode{int data;int ltag,rtag;struct TNode *left;struct TNode *right;
};
int num[1005];
int n;
void creat()
{queue<TNode *> q;struct TNode *temp;temp=(struct TNode*)malloc(sizeof(struct TNode));temp->data=num[0];temp->left=NULL;temp->ltag=0;temp->right=NULL;temp->rtag=0;t.root=temp;q.push(temp);int i=1;while(i<n){temp=q.front();q.pop();temp->ltag=0;struct TNode *l;l=(struct TNode*)malloc(sizeof(struct TNode));l->data=num[i];l->left=NULL;l->ltag=0;l->right=NULL;l->rtag=0;i++;temp->left=l;q.push(l);if(i==n)break;temp->rtag=0;struct TNode *r;r=(struct TNode*)malloc(sizeof(struct TNode));r->data=num[i];r->left=NULL;r->ltag=0;r->right=NULL;r->rtag=0;i++;temp->right=r;q.push(r);}
}
void InThread(struct TNode * &p,struct TNode * &pre)
{if(p!=NULL){if(p->left==NULL){p->left=pre;p->ltag=1;}if(pre!=NULL&&pre->right==NULL){pre->right=p;pre->rtag=1;}pre=p;if(pre->ltag==0)InThread(p->left,pre);if(pre->rtag==0)InThread(p->right,pre);}
}
void creatInThread()
{struct TNode *pre;pre=NULL;if(t.root!=NULL){InThread(t.root,pre);pre->right=NULL;pre->rtag=1;}
}
struct TNode * NextNode(struct TNode *p)
{if(p->ltag==0)return p->left;else return p->right;
}
void InOrder()
{for(struct TNode *p=t.root;p!=NULL;p=NextNode(p))printf("%d ",p->data);
}
int main()
{scanf("%d",&n);for(int i=0;i<n;i++)scanf("%d",&num[i]);creat();creatInThread();InOrder();return 0;
}
四、后续线索化
后续线索化是最难实现的,这个不是很好表达,最好的办法就是画图来表示。
从图不难发现,和前序中序不一样的是,当访问完标号为2的节点的时候,想要找到其后继6是很难的,2号节点有左右孩子,必然不能通过前驱后继指针来直接移动,那么想找到6号节点,唯一的办法就是回到上一层,在转向右子树,但是找上一层又是个麻烦事,因为没有直接指向父节点的指针,只能从根节点从头开始找,这必然是个很费时间的过程,现有的知识解决不了,查了查别人的代码,发现是用带标志域的三叉链表作为存储结构,枉费了一个小时尝试返回上一层的代码。这里放一下改了很久的代码,可以实现三层的线索化,但是超过三层就炸了。
层序建树,不超过三层情况下后序线索化:
#include<bits/stdc++.h>
using namespace std;
struct Tree{struct TNode* root;int size;
};
struct Tree t;
struct TNode{int data;int ltag,rtag;struct TNode *left;struct TNode *right;
};
int num[1005];
int n;
void creat()
{queue<TNode *> q;struct TNode *temp;temp=(struct TNode*)malloc(sizeof(struct TNode));temp->data=num[0];temp->left=NULL;temp->ltag=0;temp->right=NULL;temp->rtag=0;t.root=temp;q.push(temp);int i=1;while(i<n){temp=q.front();q.pop();temp->ltag=0;struct TNode *l;l=(struct TNode*)malloc(sizeof(struct TNode));l->data=num[i];l->left=NULL;l->ltag=0;l->right=NULL;l->rtag=0;i++;temp->left=l;q.push(l);if(i==n)break;temp->rtag=0;struct TNode *r;r=(struct TNode*)malloc(sizeof(struct TNode));r->data=num[i];r->left=NULL;r->ltag=0;r->right=NULL;r->rtag=0;i++;temp->right=r;q.push(r);}
}
void InThread(struct TNode * &p,struct TNode * &pre)
{if(p!=NULL){InThread(p->left,pre);InThread(p->right,pre);if(p->left==NULL){p->left=pre;p->ltag=1;}if(pre!=NULL&&pre->right==NULL){pre->right=p;pre->rtag=1;}pre=p;}
}
void creatInThread()
{struct TNode *pre;pre=NULL;if(t.root!=NULL)InThread(t.root,pre);
}
struct TNode * FirstNode(struct TNode *p)
{while(p->ltag==0)p=p->left;return p;
}
struct TNode * Findup(struct TNode *p)
{struct TNode *s=t.root; while(s->ltag==0){ if(s->left==p)return FirstNode(s->right);s=s->left;}s=t.root;if(s->right==p)return s;while(s->rtag==0){ if(s->right==p)return FirstNode(s->right);s=s->right;}
}
struct TNode * NextNode(struct TNode *p)
{if(p->rtag==0)return Findup(p);elsereturn p->right;
}
void InOrder()
{for(struct TNode *p=FirstNode(t.root);;p=NextNode(p)){printf("%d ",p->data);if(p==t.root)break;}}
int main()
{scanf("%d",&n);for(int i=0;i<n;i++)scanf("%d",&num[i]);creat();creatInThread();InOrder();return 0;
}
数据结构 5-3-3 二叉树的线索化相关推荐
- 二叉树线索化示意图_二叉树的线索化算法思想详解
二叉树的线索化,这几天以来我很难掌握,今天终于想通了,哈哈,首先我们来看看二叉树线索化之后会变成什么样子,这里我们以图中的二叉树为例,图如下: 画的太糙,各位看官讲究着看吧- -.所谓二叉树的线索化, ...
- 二叉树的层序遍历和二叉树的线索化
先根,后子树:先左子树,后右子树 二叉树的根节点 a 入队 a 的子树,根节点 b 和 c 分别入队 然后 b 的子树的根节点入队(为空) c 的子树的根节点入队 d 的子树的根节点入队(为空) e ...
- 线索二叉树的线索化、及遍历
线索二叉树 遍历二叉树是以一定规则将二叉树中结点排列成一个线性序列,得到二叉树中结点的先序序列或中序序列或后序序列.这实际上是对一个非线性序列结构进行线性化操作,使每个结点(除了第一和最后一个)在这些 ...
- 二叉树在线索化后,仍不能有效求解的问题是()
二叉树在线索化后,仍不能有效求解的问题是(D) A先序线索二叉树中求先序后继 B中序线索二叉树中求中序后继 C中序线索二叉树中求中序前驱 D后序线索二叉树中求后序后继 先序遍历(中左右).中序遍历(左 ...
- c语言实现二叉树的线索化和线索二叉树的遍历
时构建线索二叉树的结构体 线索二叉树比平常的二叉树多了一个ltag域和一个rtag域,他们的作用在后面遍历时会说 #include<stdio.h> #include<stdlib. ...
- 二叉树线索化示意图_二叉树的线索化
二叉树是一种非线性结构,遍历二叉树几乎都是通过递归或者用栈辅助实现非递归的遍历.二叉树作为存储结构时,一个节点只能获取节点的左孩子和右孩子,不能直接得到节点的任一遍历序列的前驱或者后继.为了保存这种在 ...
- 算法与数据结构(三) 二叉树的遍历及其线索化(Swift版)
前面两篇博客介绍了线性表的顺序存储与链式存储以及对应的操作,并且还聊了栈与队列的相关内容.本篇博客我们就继续聊数据结构的相关东西,并且所涉及的相关Demo依然使用面向对象语言Swift来表示.本篇博客 ...
- 数据结构-C语言版-二叉树中序线索化
二叉树中的线索化问题,数据结构最核心.密集的考点之一:树,以中序二叉树的线索化 代码参考王道数据结构课程! #include<stdio.h> #include<stdlib.h&g ...
- 线索二叉树原理及前序、中序线索化(Java版)
转载 原文地址:https://blog.csdn.net/UncleMing5371/article/details/54176252 一.线索二叉树原理 前面介绍二叉树原理及特殊二叉树文章中提到, ...
最新文章
- neo4j客户端下载
- ASP.NET MVC 概述
- C/C++写无控制台窗口程序
- oracle asin(),PLSQL ASIN用法及代码示例
- 小程序中实现滚动字幕
- python的内存分配
- 一卡通大冒险(hdu2512)
- C# ListView 简单命令例子
- jieba库词频统计_如何用python对《三国演义》、《红楼梦》等名著开展词云分析及字频统计、出场统计等工作。...
- 如何使用Vue集成其它UI组件?如何阅读UI官网?移动端组件Vant实例教程(熬夜干货)
- wordpress 伪静态nginx设置
- CHAPTER 28 VMX SUPPORT FOR ADDRESS TRANSLATION
- 到底什么是BFC、IFC、GFC和FFC,次奥?
- 如何使用低代码平台设计一套请假流程?
- 画论13 朱景玄《唐朝名画录》
- switch() 参数支持类型
- 算法精品讲解(2)——DP问题入门(适合零基础者,一看就会)
- sim卡无线上网公用服务器,云SIM卡池,云SIM卡解决方案,流量分发随身WIFI方案( 忻瑞科技)...
- 权限管理——用户认证和用户授权
- 内核自带的基于GPIO的LED驱动学习(三)