文字描述

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

    说明(2017-12-25 10:43:20): 1. CZ写的压缩bin包代码,记下来以后好抄. # coding:utf-8 ''' Created on 2014年8月14日 @author: ...

  2. MXNET:多层神经网络

    多层感知机(multilayer perceptron,简称MLP)是最基础的深度学习模型. 多层感知机在单层神经网络的基础上引入了一到多个隐藏层(hidden layer).隐藏层位于输入层和输出层 ...

  3. Android 实现顶层窗口、浮动窗口(附Demo)

    做过Window程序开发的朋友应该都知道,我们要把程序窗口置顶很简单,只要设置一些窗口属性即可.但是到了Android,你无法简单设置一个属性,就让Android的Activity置顶.因为只要有新的 ...

  4. iproute2应用

    linux目前都支持ip命令,与ifconfig类似,但ifconfig的软件net-tools早不更新了,ip功能更强大,推荐使用iproute2套件. ip可以完美替换常用的网络命令,用法如下: ...

  5. supervisor开机自动启动脚本+redis+MySQL+tomcat+nginx进程自动重启配置

    [root@mongodb-host supervisord]# cat mongo.conf [program:mongo]command=/usr/local/mongodb/bin/mongod ...

  6. 【Matplotlib】利用Python进行绘图

    [Matplotlib] 教程:https://morvanzhou.github.io/tutorials/data-manipulation/plt/ 官方文档:https://matplotli ...

  7. Xcode 8.0 新特性 & Swift 3.0 增加的变动

    从 Xcode 8.0 开始,目前所有的插件都无法工作! NSLog 无法输出 -- 此bug等待正式版本... Xcode 提供了文档注释快捷键option + cmd + / 但是要把系统升级到1 ...

  8. Python 函数(可变参数)

    在python函数中,可以定义可变参数,顾名思义,可变参数就是,传入的参数是可变的例如,给定一组数字a,b,c...  请计算a2 + b2 + c2 + …… 要定义出这个函数,我们必须确定输入的参 ...

  9. mysql wait_timeout 8小时问题解决,tomcat数据源的配置

    异常报错: 2017-02-13 09:30:17.597 [startQuertz_Worker-6] ERROR com.autoyol.task.TransStatManageTask#exec ...

  10. [Stats385] Lecture 05: Avoid the curse of dimensionality

    Lecturer 咖中咖 Tomaso A. Poggio Lecture slice Lecture video 三个基本问题: Approximation Theory: When and why ...