文字描述

  从二叉树的遍历可知,遍历二叉树的输出结果可看成一个线性队列,使得每个结点(除第一个和最后一个外)在这个线形队列中有且仅有一个前驱和一个后继。但是当采用二叉链表作为二叉树的存储结构时,只能得到结点的左孩子结点和右孩子结点,要想知道结点的前驱或后继,需要再遍历一次才知道。另外,叶子结点的左右孩子结点是空链域,在有n个结点的二叉链表中必定存在n+1个空链域,原因见[附录1证明]。由此,便可以考虑利用这些叶子结点的空链域来存放结点的前驱和后继结点。

  线索二叉树的存储结构中增加两个标志域LTag和RTag,标志左/右链域是孩子结点还是前驱或后继。这种存储结构叫做线索存储,之前前驱和后继的结点指针叫线索,加上线索的二叉树叫线索二叉树。关于线索二叉树主要有两个问题需要解决:

  第一个问题 如何建立线索二叉树?

  第二个问题 如何遍历线索二叉树?

  第一个问题:如何建立线索二叉树?

  线索化的实质就是将二叉链表的空指针改为前驱或后继的线索。而前驱或后继的信息只有在遍历时才能得到,因此线索化的过程即为在遍历过程中修改空指针的过程。为了记下遍历过程中访问结点的先后关系,附设一个pre始终指向刚刚访问过的结点,若指针p指向当前访问的结点,则pre就是p的前去,p就是pre的后继。

  第二个问题:如何遍历线索二叉树?

  在线索树上遍历,只要先找到序列中的第一个结点,然后依次找结点后继直至后继为空时为止。树中所有叶子结点的链域就是线索,左链指示了该结点的前驱,右链域指示了该结点的后继,而如何在线索树中找非叶子结点的前驱或后继呢?

  对于先序线索树 根据先序遍历的规律知,非终端结点的后继:如果其左孩子存在,则后继就是其左孩子结点. 否则后继就是其右孩子结点.

  对于中序线索树 根据中序遍历的规律知,。非终端结点的后继应该是遍历其右子树时访问的第一个结点,即右子树中最左下的结点;

  对于后序线索树 在后序线索树中找结点后继复杂些,分3种情况: (1)若结点x是二叉树的根,其后继就是为空;(2)若结点x是其双亲结点的右孩子或者 是其双亲的左孩子且其双亲没有右子树, 则其后继即为双亲结点 (3)若结点x是其双亲的左孩子,且双亲有右子树,则其后继为双亲的右子树上按后序遍历列出的第一个结点。可见,在后序线索树上找后继时需知道结点双亲,即需带标志域的三叉链表作存储结构。

示意图

算法分析

  线索二叉树上遍历二叉树,时间复杂度仍然为n, 但是常数因子要比非线索二叉树的遍历算法小,且不需要另外设栈或队列。 因此,若二叉树需要经常遍历或查找结点在遍历所得线性序列中的前驱和后继,则应采用线索链表做存储结构。

代码实现

 /*
*
* 编译本文件后,可输入如下参数:
*
* ./a.out - + a \# \# \* b \# \# - c \# \# d \# \# / e \# \# f \# \#
*
*/ #include <stdio.h>
#include <stdlib.h> #define DEBUG
#define EQ(a, b) ((a)==(b))
/*树中结点的最大个数*/
#define MAX_TREE_SIZE 100 typedef char KeyType;
typedef int InfoType; /*树中的结点类型*/
typedef struct{
KeyType key;
InfoType otherinfo;
}TElemType; /*Link==0指针,Thread==1线索*/
typedef enum PointerTag{Link=, Thread}PointerTag; /*
* 二叉树的二叉线索存储表示
*
* 链表中的结点包含五个数据:数据域data,左指针域lchild,右指针域rchild, 左标志, 右标志
*/
typedef struct BiTNode{
TElemType data;
struct BiTNode *lchild, *rchild;
PointerTag LTag, RTag;
}BiThrNode, *BiThrTree; /*
* 创建二叉链表
*
* 按先根次序输入二叉树中结点的值,'#'表示空结点
* 构造二叉链表表示的二叉树T
*/
int I = ;
BiThrTree CreateBiTree(TElemType input[]){
TElemType data = input[I++];
BiThrTree T = NULL;
if(data.key == '#'){
T = NULL;
return T;
}else{
if(!(T=(BiThrNode *)malloc(sizeof(BiThrNode)))){
printf("Error: overflow!\n");
exit();
}
T->data = data;
T->lchild = CreateBiTree(input);
T->rchild = CreateBiTree(input);
return T;
}
} /*遍历二叉树时用到的函数指针参数*/
int vist(TElemType e){
printf("%c ", e.key);
return ;
} /*
* pre总指向刚刚访问过的结点,
* 若p指向刚刚访问过的结点,则pre指向它的前驱;p指向pre的后继。
*/
BiThrTree pre; //////////////////////////////////////////////////////////////////////////////////
//先序线索二叉树的遍历与建立-start//////////////////////////////////////////////// int PreOrderTraverse_Thr(BiThrTree Thrt, int (*fun)(TElemType e)){
BiThrTree p = Thrt->lchild;
while(p!=Thrt){
fun(p->data);
while(p->LTag == Link){
p = p->lchild;
fun(p->data);
}
p = p->rchild;
}
return ;
} /*先序线索二叉树的建立*/
void PreThreading(BiThrTree p){
if(p){
if(!p->lchild){
p->LTag = Thread;
p->lchild = pre;
}
if(!pre->rchild){
pre->RTag = Thread;
pre->rchild = p;
}
pre = p;
if(p->LTag == Link)
PreThreading(p->lchild);
if(p->RTag == Link)
PreThreading(p->rchild);
}
return ;
} /*先序线索二叉树的建立*/
BiThrTree PreOrderThreading(BiThrTree T){
//建立头指针
BiThrTree Thrt = NULL;
if(!(Thrt=(BiThrTree)malloc(sizeof(BiThrNode)))){
printf("malloc fail!\n");
return NULL;
}
Thrt->LTag = Link;
//右指针回值
Thrt->RTag = Thread;
Thrt->rchild = Thrt;
if(!T){
//若二叉树空,则右指针回指
Thrt->lchild = Thrt;
}else{
Thrt->lchild = T;
pre = Thrt;
//中序线索化
PreThreading(T);
//最后一个结点线索化
pre->RTag = Thread;
pre->rchild = Thrt;
Thrt->rchild = pre;
}
return Thrt;
} //先序线索二叉树的遍历与建立-end///////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////
//中序线索二叉树的遍历与建立-start////////////////////////////////////////////////
void InThreading(BiThrTree p){
if(p){
//左子树线索化
InThreading(p->lchild);
//pre指向p的前驱
if(!p->lchild){
p->LTag = Thread;
p->lchild = pre;
}
//p指向pre的后继
if(!pre->rchild){
pre->RTag = Thread;
pre->rchild = p;
}
//保持pre指向p的前驱
pre = p;
//右子树线索化
InThreading(p->rchild);
}
return ;
} /*
* 中序线索二叉树的建立
*
* 中序遍历二叉树T,并将其中序线索化,返回值指向线索化的头结点
* 头结点的lchild域的指针指向二叉树的根结点,其rchild域的指针指向中序遍历时访问的最后一个结点;
* 另外,令二叉树中序序列中的第一个结点的lchild域指针和最后一个结点rchild域的指针均指向头结点。
*/
BiThrTree InOrderThreading(BiThrTree T){
//建立头指针
BiThrTree Thrt = NULL;
if(!(Thrt=(BiThrTree)malloc(sizeof(BiThrNode)))){
printf("malloc fail!\n");
return NULL;
}
Thrt->LTag = Link;
//右指针回值
Thrt->RTag = Thread;
Thrt->rchild = Thrt;
if(!T){
//若二叉树空,则右指针回指
Thrt->lchild = Thrt;
}else{
Thrt->lchild = T;
//pre总指向刚刚访问过的结点,若p指向刚刚访问过的结点,则pre指向它的前驱;p指向pre的后继。
pre = Thrt;
//中序遍历进行中序线索化
InThreading(T);
//最后一个结点线索化
pre->RTag = Thread;
pre->rchild = Thrt;
Thrt->rchild = pre;
}
return Thrt;
} /*
* 中序线索二叉树的遍历
*/
int InOrderTraverse_Thr(BiThrTree Thrt, int (*fun)(TElemType e)){
BiThrTree p = Thrt->lchild;
while(p!=Thrt){
while(p->LTag==Link) p = p->lchild;
fun(p->data);
while(p->RTag==Thread && p->rchild != Thrt){
p = p->rchild;
fun(p->data);
}
p = p->rchild;
}
return ;
}
//中序线索二叉树的遍历与建立-end//////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////
//后序线索二叉树的遍历与建立-start///////////////////////////////////////////////
/*三叉链表结构*/
typedef struct BiPTNode{
TElemType data;
struct BiPTNode *lchild, *rchild, *parent;
PointerTag LTag, RTag;
}BiPThrNode, *BiPThrTree; ////////////////////////////////////////
//与队列相关的结构体和函数声明-start////
typedef struct QNode{
BiPThrTree data;
struct QNode *next;
}QNode, *QuenePtr; typedef struct{
QuenePtr front;
QuenePtr rear;
}LinkQueue; LinkQueue* InitQueue(void);
int QueueEmpty(LinkQueue *Q);
int GetHead(LinkQueue *Q, BiPThrTree *e);
int EnQueue(LinkQueue *Q, BiPThrTree *e);
int DeQueue(LinkQueue *Q, BiPThrTree *e);
//与队列相关的结构体和函数声明-end////////
////////////////////////////////////////// /*
* 三叉链表的建立
*
* 按照层序遍历的顺序依次输入结点input, 然后建立带双亲结点的三叉链表。
*
*/
BiPThrTree CreatePBiTree(TElemType input[]){
I = ;
TElemType data;
BiPThrTree PT = NULL, parent, lchild=NULL, rchild=NULL;
if((data=input[I++]).key == '#'){
return PT;
}else{
if(!(PT=(BiPThrNode *)malloc(sizeof(BiPThrNode)))){
exit();
}
PT->data = data;
PT->parent = NULL;
LinkQueue *Q = InitQueue();
EnQueue(Q, &PT);
while(QueueEmpty(Q)){
DeQueue(Q, &parent);
if((data=input[I++]).key == '#'){
lchild = NULL;
}else{
lchild = (BiPThrNode *)malloc(sizeof(BiPThrNode));
lchild->data = data;
lchild->parent = parent;
EnQueue(Q, &lchild);
}
(parent)->lchild = lchild; if((data=input[I++]).key == '#'){
rchild = NULL;
}else{
rchild = (BiPThrNode *)malloc(sizeof(BiPThrNode));
rchild->data = data;
rchild->parent = parent;
EnQueue(Q, &rchild);
}
(parent)->rchild = rchild;
}
}
return PT;
} /*
* pre_p总指向刚刚访问过的结点,
* 若p指向刚刚访问过的结点,则pre_p指向它的前驱;p指向pre_p的后继。
*/
BiPThrTree pre_p; void PostThreading(BiPThrTree p){
if(p){
PostThreading(p->lchild);
PostThreading(p->rchild);
if(!p->lchild){
p->LTag = Thread;
p->lchild = pre_p;
}
if(!pre_p->rchild){
pre_p->RTag = Thread;
pre_p->rchild = p;
}
pre_p = p;
}
return ;
} /*后序线索二叉树的建立*/
BiPThrTree PostOrderThreading(BiPThrTree T){
BiPThrTree Thrt = NULL;
if(!(Thrt=(BiPThrTree)malloc(sizeof(BiPThrNode)))){
return NULL;
}
Thrt->LTag = Link;
Thrt->RTag = Thread;
Thrt->rchild = Thrt;
if(!T){
Thrt->lchild = Thrt;
}else{
Thrt->lchild = T;
pre_p = Thrt;
PostThreading(T);
pre_p->RTag = Thread;
pre_p->rchild = Thrt;
Thrt->rchild = pre_p;
}
} /*后序线索二叉树的遍历*/
void PostOrderTraverse_Thr(BiPThrTree Thrt, int (*fun)(TElemType e))
{
BiPThrTree p = Thrt->lchild;
while(p->LTag == Link){
p = p->lchild;
}
while(p!=Thrt){
fun(p->data);
while(p->RTag==Thread && p->rchild != Thrt){
p = p->rchild;
fun(p->data);
}
if(p->parent){
p = p->parent;
}else{
break; }
}
}
//后序线索二叉树的遍历与建立-end//////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////// int main(int argc, char *argv[])
{
if(argc < )
return -; TElemType input[MAX_TREE_SIZE];
int i = , j = ;
for(i=; i<MAX_TREE_SIZE; i++){
input[i].key = '#';
} //按先根次序输入二叉树中结点的值,'#'表示空树
for(i=; i<argc; i++){
if(i>MAX_TREE_SIZE)
break;
input[i-].key = argv[i][];
input[i-].otherinfo = i-;
}
#ifdef DEBUG
printf("输入数据以建立二叉树(#表示空空结点):\n");
for(j=; j< i-; j++){
printf("%c ", input[j].key);
}
printf("\n");
#endif
printf("先序线索二叉树的建立与遍历:(按照先序次序建立二叉树)\n");
I=;
BiThrTree PreT = CreateBiTree(input);
BiThrTree PreThrt = PreOrderThreading(PreT);
PreOrderTraverse_Thr(PreThrt, vist); printf("\n中序线索二叉树的建立与遍历:(按照先序次序建立二叉树)\n");
I = ;
BiThrTree InT = CreateBiTree(input);
BiThrTree InThrt = InOrderThreading(InT);
InOrderTraverse_Thr(InThrt, vist); printf("\n后序线索二叉树的建立与遍历:(按照层序次序建立二叉树)\n");
I=;
BiPThrTree PostT = CreatePBiTree(input);
BiPThrTree PostThrt = PostOrderThreading(PostT);
PostOrderTraverse_Thr(PostThrt, vist);
printf("\n");
return ;
} //////////////////////////////////////////////////////////////////////////////////
//与队列相关的函数的实现-start///////////////////////////////////////////////////
LinkQueue* InitQueue(void)
{
LinkQueue *Q = (LinkQueue*)malloc(sizeof(LinkQueue));
Q->front = Q->rear = (QuenePtr)malloc(sizeof(QNode));
if(!Q->front){
printf("malloc fail!\n");
return NULL;
}
return Q;
} int QueueEmpty(LinkQueue *Q)
{
if(Q->front == Q->rear){
return ;
}else{
return -;
}
} int GetHead(LinkQueue *Q, BiPThrTree *e)
{
if(Q->front == Q->rear){
return -;
}
*e = Q->front->next->data;
return ;
} int EnQueue(LinkQueue *Q, BiPThrTree *e)
{
QuenePtr p = (QuenePtr)malloc(sizeof(QNode));
if(!p){
printf("malloc fail!\n");
return -;
}
p->data = *e;
p->next = NULL;
Q->rear->next = p;
Q->rear = p;
return ;
} int DeQueue(LinkQueue *Q, BiPThrTree *e)
{
if(Q->front == Q->rear){
return -;
}
QuenePtr p = Q->front->next;
*e = p->data;
Q->front->next = p->next;
if(p == Q->rear){
Q->rear = Q->front;
}
free(p);
return ;
}
//与队列相关的函数的实现-end//////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////

线索二叉树的建立与遍历

运行

附录1

  证明n个结点的二叉链表中必有n+1个空链域:

  n个结点的二叉链表中共有n*2个链域,除根结点外的每个结点都有一个父亲结点,所以2*n个链域中有n-1个有内容的链域。所以共有2*n-(n-1) = n+1个空链域 。

树和二叉树->线索二叉树的更多相关文章

  1. 线索二叉树的详细实现(C++)

    线索二叉树概述 二叉树虽然是非线性结构,但二叉树的遍历却为二又树的结点集导出了一个线性序列.希望很快找到某一结点的前驱或后继,但不希望每次都要对二叉树遍历一遍,这就需要把每个结点的前驱和后继信息记录下 ...

  2. 【Java】 大话数据结构(9) 树(二叉树、线索二叉树)

    本文根据<大话数据结构>一书,对Java版的二叉树.线索二叉树进行了一定程度的实现. 另: 二叉排序树(二叉搜索树) 平衡二叉树(AVL树) 二叉树的性质 性质1:二叉树第i层上的结点数目 ...

  3. 【PHP数据结构】完全二叉树、线索二叉树及树的顺序存储结构

    在上篇文章中,我们学习了二叉树的基本链式结构以及建树和遍历相关的操作.今天我们学习的则是一些二叉树相关的概念以及二叉树的一种变形形式. 完全二叉树 什么叫完全二叉树呢?在说到完全二叉树之前,我们先说另 ...

  4. 数据结构《9》----Threaded Binary Tree 线索二叉树

    对于任意一棵节点数为 n 的二叉树,NULL 指针的数目为  n+1 , 线索树就是利用这些 "浪费" 了的指针的数据结构. Definition: "A binary ...

  5. 数据结构之线索二叉树——C语言实现

     线索二叉树操作 (1) 线索二叉树的表示:将每个节点中为空的做指针与右指针分别用于指针节点的前驱和后续,即可得到线索二叉树. (2) 分类:先序线索二叉树,中序线索二叉树,后续线索二叉树 (3) 增 ...

  6. [线索二叉树] [LeetCode] 不需要栈或者别的辅助空间,完成二叉树的中序遍历。题:Recover Binary Search Tree,Binary Tree Inorder Traversal

    既上篇关于二叉搜索树的文章后,这篇文章介绍一种针对二叉树的新的中序遍历方式,它的特点是不需要递归或者使用栈,而是纯粹使用循环的方式,完成中序遍历. 线索二叉树介绍 首先我们引入“线索二叉树”的概念: ...

  7. 数据结构之---C语言实现线索二叉树

    //线索二叉树,这里在二叉树的基础上增加了线索化 //杨鑫 #include <stdio.h> #include <stdlib.h> typedef char ElemTy ...

  8. javascript实现数据结构:线索二叉树

    遍历二叉树是按一定的规则将树中的结点排列成一个线性序列,即是对非线性结构的线性化操作.如何找到遍历过程中动态得到的每个结点的直接前驱和直接后继(第一个和最后一个除外)?如何保存这些信息? 设一棵二叉树 ...

  9. 笔试算法题(41):线索二叉树(Threaded Binary Tree)

    议题:线索二叉树(Threaded Binary Tree) 分析: 为除第一个节点外的每个节点添加一个指向其前驱节点的指针,为除最后一个节点外的每个节点添加一个指向其后续节点的指针,通过这些额外的指 ...

随机推荐

  1. linux每日命令(33):diff命令

    diff 命令是 linux上非常重要的工具,用于比较文件的内容,特别是比较两个版本不同的文件以找到改动的地方.diff在命令行中打印每一个行的改动.最新版本的diff还支持二进制文件.diff程序的 ...

  2. 2. Tensorflow的数据处理中的Dataset和Iterator

    1. Tensorflow高效流水线Pipeline 2. Tensorflow的数据处理中的Dataset和Iterator 3. Tensorflow生成TFRecord 4. Tensorflo ...

  3. 【iCore4 双核心板_ARM】例程十八:USBD_VCP实验——虚拟串口

    实验步骤: 1.将跳线冒跳至USB_OTG,通过Micro USB 线将iCore4 USB-OTG接口与电脑相连. 2.打开设备管理器,可以找到虚拟出来的端口,(特殊情况下如果没有虚拟出端口,我们可 ...

  4. 跨控制器跳转view——RedirectToRoute和RedirectToAction

    已知控制器AccountController.cs和HomeController.cs,如果从页面Account/Login直接跳转到Home/Index,可以利用RedirectToRoute和Re ...

  5. Git 删除操作

    Tom 更新了自己的本地存储库并进入src目录下找到编译后的二进制.查看提交信息后,他意识到,编译后的二进制是由Jerry加入的. . [tom@CentOS src]$ pwd /home/tom/ ...

  6. AndroidStudio 代码(导入类)报错但可正常运行,以及解决此问题后带来的系列问题解决

    首先是应用中很多导入的类都报红色异常显示找不到此类,但运行编译正常: 第一种方法: 点击AndroidStudio菜单File -> Invalidate Caches/Restar… ,在弹出 ...

  7. Oracle11.2.0.4 RAC GI ORA-15003: diskgroup "XXXX" already mounted in another lock name space

    最新文章:Virson‘s Blog 安装GI,在执行root.sh时报错: Disk Group CRSDG creation failed with the following message: ...

  8. 树莓GPIO &&python

      from http://www.cnblogs.com/xiaobo-Linux/p/8969324.html 命令行控制LED灯  echo 12 > /sys/class/gpio/ex ...

  9. 总结一下搭建简单Web服务器的一些方法

    使用nodejs+anywhere模块搭建静态文件服务器 anywhere随时随地将你的当前目录变成一个静态文件服务器的根目录. 安装npm install anywhere -g,然后进入任意目录在 ...

  10. python加快数据处理的方法

    1.一切数据库操作最好使用内网连接, 2.使用批量操作接口操作数据库,而不是多线程频繁操作单条数据 3.如果python进程的cpu使用率达到100%了,需要开启多进程.java单个进程cpu使用率在 ...