文字描述

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

    原文转自:https://tech.meituan.com/namenode.html 感谢原作者 一.概述 从整个HDFS系统架构上看,NameNode是其中最重要.最复杂也是最容易出现问题的地方, ...

  2. CSS让页面平滑滚动

    我们以往实现平滑滚动往往用的是jQuery, 如实现平滑回到顶部,就写如下代码: $('.js_go_to_top').click(function () { $(".js_scroll_a ...

  3. Java知多少(中)

    Java知多少(上) )interface接口 )接口和抽象类的区别 )泛型详解 )泛型通配符和类型参数的范围 )异常处理基础 )异常类型 )未被捕获的异常 )try和catch的使用 )多重catc ...

  4. JavaScript高级用法二之内置对象

    综述 本篇的主要内容来自慕课网,内置对象,主要内容如下 1 什么是对象 2 Date 日期对象 3 返回/设置年份方法 4 返回星期方法 5 返回/设置时间方法 6 String 字符串对象 7 返回 ...

  5. Redis介绍以及安装(Linux)

    Redis介绍以及安装(Linux) redis是当前比较热门的NOSQL系统之一,它是一个key-value存储系统.和Memcached类似,但很大程度补偿了memcached的不足,它支持存储的 ...

  6. [Model] GoogLeNet

    主要就是对Inception Module的理解 网络结构分析 没有densy layer竟然,这是给手机上运行做铺垫么. 一个新型的模块设计: [不同类型的layer并行放在了一起] 最初的设计: ...

  7. [React] 03 - Intro: react.js in twelve demos

    Ref: React 入门实例教程 这算什么,react学习例子的十二门徒?哈哈 如何运行别人的react项目? Ref: [React全家桶入门之CODE]项目代码与使用方法 使用git克隆项目到本 ...

  8. vue跨域代理配置

    实际:http://a.com/b.php 代理:http://localhost/b.php 配置config/index.js proxyTable: { '/api': { target:'ht ...

  9. 解决pycharm在ubuntu下搜狗输入法一直固定在左下角的问题

    1.缩放VMware,当ubuntu中出现下拉导航条时,点击左上角查看>立即适应客户机,然后在pycharm中打中文的时候不用全屏,就可以看到输入法显示的文字了. 2.目前没有发现搜狗输入法版本 ...

  10. Python实现C代码统计工具(二)

    目录 Python实现C代码统计工具(二) 声明 一. 问题提出 二. 代码实现 三. 效果验证 Python实现C代码统计工具(二) 标签: Python 代码统计 声明 本文将对<Pytho ...