文字描述

  从二叉树的遍历可知,遍历二叉树的输出结果可看成一个线性队列,使得每个结点(除第一个和最后一个外)在这个线形队列中有且仅有一个前驱和一个后继。但是当采用二叉链表作为二叉树的存储结构时,只能得到结点的左孩子结点和右孩子结点,要想知道结点的前驱或后继,需要再遍历一次才知道。另外,叶子结点的左右孩子结点是空链域,在有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. 编译错误 ld: cannot find -lz

    [时间:2017-04] [状态:Open] [关键词:makefile,gcc,linux,ld,libz.so] 在新安装的centos上编译程序遇到上述问题,找了半天,原来是没有安装 需要安装z ...

  2. mercurial的几个易用性小技巧

    其实这两年,能够采用mercurial的项目我都尽量用,甚至有些上游是git的,或者需要托管到公司内gitlab上与别人协作的,我都装上hg-git.无它,只是因为mercurial易用性比git好得 ...

  3. Kylin 与 Spark SQL相比,有哪些差异和优势?

    SparkSQL本质上是基于DAG模型的MPP.而Kylin核心是Cube(多维立方体).关于MPP和Cube预处理的差异,重复如下: > MPP [1] 的基本思路是增加机器来并行计算,从而提 ...

  4. PHP扩展类ZipArchive实现压缩解压Zip文件和文件打包下载

    文章转载自:https://my.oschina.net/junn/blog/104464 PHP ZipArchive 是PHP自带的扩展类,可以轻松实现ZIP文件的压缩和解压,使用前首先要确保PH ...

  5. Serializable接口

    Serializable这个接口起啥作用呢?? 这个接口没有提供任何方法,我们实现它有什么意义呢? Serializable接口是启用其序列化功能的接口.Serializable接口中没有任何方法,一 ...

  6. js中关于Blob对象的介绍与使用

    js中关于Blob对象的介绍与使用   blob对象介绍 一个 Blob对象表示一个不可变的, 原始数据的类似文件对象.Blob表示的数据不一定是一个JavaScript原生格式 blob对象本质上是 ...

  7. vscode忽略node_module

    1.文件 ---> 首选项  ---> 设置 英文版对应:File ---> Preferences-> User Settings 2.打开 setting.json 3.将 ...

  8. @Transactional(rollbackFor = Exception.class)

    @Transactional(rollbackFor = Exception.class)这个注解只有在出异常时才会回滚,需要回滚时没有异常也要人为制造异常(自定义异常)所以,如果使用了异常捕获,很有 ...

  9. iLBC

    iLBC是一种专为包交换网络通信设计的编解码,优于目前流行的G.729.G.723.1,对丢包进行了特有处理,即使在丢包率 相当高的网络环境下,仍可获得非常清晰的语音效果.

  10. Python 字符串转JSON; 先装字典在转JSON; json.dumps(d)

    #-*- coding:UTF-8 -*- import os; import json class MysqlUtil(): def __init__(self): pass if __name__ ...