理解线索二叉树

原链接:理解线索二叉树|CloudWong

线索二叉树原理

遍历二叉树的其实就是以一定规则将二叉树中的结点排列成一个线性序列,得到二叉树中结点的先序序列、中序序列或后序序列。这些线性序列中的每一个元素都有且仅有一个前驱结点后继结点

但是当我们希望得到二叉树中某一个结点的前驱或者后继结点时,普通的二叉树是无法直接得到的,只能通过遍历一次二叉树得到。每当涉及到求解前驱或者后继就需要将二叉树遍历一次,非常不方便。

于是是否能够改变原有的结构,将结点的前驱和后继的信息存储进来。

二叉树结构

观察二叉树的结构,我们发现指针域并没有充分的利用,有很多“NULL”,也就是存在很多空指针。

对于一个有n个结点的二叉链表,每个节点都有指向左右孩子的两个指针域,一共有2n个指针域。而n个结点的二叉树又有n-1条分支线数(除了头结点,每一条分支都指向一个结点),也就是存在2n-(n-1)=n+1个空指针域。这些指针域只是白白的浪费空间。因此, 可以用空链域来存放结点的前驱和后继。线索二叉树就是利用n+1个空链域来存放结点的前驱和后继结点的信息。

线索二叉树

如图以中序二叉树为例,我们可以把这颗二叉树中所有空指针域的lchild,改为指向当前结点的前驱(灰色箭头),把空指针域中的rchild,改为指向结点的后继(绿色箭头)。我们把指向前驱和后继的指针叫做线索 ,加上线索的二叉树就称之为线索二叉树

线索二叉树结点结构

如果只是在原二叉树的基础上利用空结点,那么就存在着这么一个问题:我们如何知道某一结点的lchild是指向他的左孩子还是指向前驱结点?rchild是指向右孩子还是后继结点?显然我们要对他的指向增设标志来加以区分。

因此,我们在每一个结点都增设两个标志域LTagRTag,它们只存放0或1的布尔型变量,占用的空间很小。于是结点的结构如图所示。

结点结构

其中:

LTag为0是指向该结点的左孩子,为1时指向该结点的前驱

RTag为0是指向该结点的右孩子,为1时指向该结点的后继

因此实际的二叉链表图为

实际的二叉链表图

线索二叉树的结构实现

二叉树的线索存储结构定义如下:

typedef char TElemType;                     

typedef enum { Link, Thread } PointerTag;       //Link==0,表示指向左右孩子指针
                                                //Thread==1,表示指向前驱或后继的线索
//二叉树线索结点存储结构
typedef struct BiThrNode {
  TElemType data;                       //结点数据
  struct BiThrNode *lchild, *rchild;    //左右孩子指针
  PointerTag LTag;                      
  PointerTag RTag;                      //左右标志
}BiThrNode, *BiThrTree;

二叉树线索化

线索化

对普通二叉树以某种次序遍历使其成为线索二叉树的过程就叫做线索化。因为前驱和后继结点只有在二叉树的遍历过程中才能得到,所以线索化的具体过程就是在二叉树的遍历中修改空指针

线索化具体实现

以中序二叉树的线索化为例,线索化的具体实现就是将中序二叉树的遍历进行修改,把原本打印函数的代码改为指针修改的代码就可以了。

我们设置一个pre指针,永远指向遍历当前结点的前一个结点。若遍历的当前结点左指针域为空,也就是无左孩子,则把左孩子的指针指向pre(相对当前结点的前驱结点)。

右孩子同样的,当pre的右孩子为空,则把pre右孩子的指针指向当前结点(相对pre结点为后继结点)。

最后把当前结点赋给pre,完成后续的递归遍历线索化。

中序遍历线索化的递归函数代码如下:

void InThreading(BiThrTree B,BiThrTree *pre) {
  if(!B) return;

  InThreading(B->lchild,pre);   
//--------------------中间为修改空指针代码---------------------

  if(!B->lchild){                   //没有左孩子 
    B->LTag = Thread;               //修改标志域为前驱线索
    B->lchild = *pre;               //左孩子指向前驱结点
  }

  if(!(*pre)->rchild){              //没有右孩子
    (*pre)->RTag = Thread;          //修改标志域为后继线索
    (*pre)->rchild = B;             //前驱右孩子指向当前结点
  }

  *pre = B;                         //保持pre指向p的前驱
//---------------------------------------------------------
  InThreading(B->rchild,pre);
}

增设头结点

线索化后的二叉树,就如同操作一个双向链表。于是我们想到为二叉树增设一个头结点,这样就和双向链表一样,即能够从第一个结点正向开始遍历,也可以从最后一个结点逆向遍历。

增设头结点

如上图,在线索二叉链表上添加一个head结点,并令其lchild域的指针指向二叉树的根结点(A),其rchild域的指针指向中序遍历访问的最后一个结点(G)。同样地,二叉树中序序列的第一个结点中,lchild域指针指向头结点,中序序列的最后一个结点rchild域指针也指向头结点。

于是从头结点开始,我们既可以从第一个结点顺后继结点遍历,也可以从最后一个结点起顺前驱遍历。就和双链表一样。

双链表

增设头结点并线索化的代码实现

//为线索二叉树添加头结点,使之可以双向操作
Status InOrderThreading(BiThrTree *Thrt,BiThrTree T){
  if(!(*Thrt = (BiThrTree)malloc(sizeof(BiThrNode)))) exit(OVERFLOW);  //开辟结点
  (*Thrt)->LTag = Link;         
  (*Thrt)->RTag = Thread;               //设置标志域
  (*Thrt)->rchild = (*Thrt);            //右结点指向本身
  if(!T) {
    (*Thrt)->lchild = (*Thrt);
    return OK;       //若根结点不存在,则该二叉树为空,让该头结点指向自身.
  }
  BiThrTree pre;                //设置前驱结点
  //令头结点的左指针指向根结点
  pre = (*Thrt);
  (*Thrt)->lchild = T;
  //开始递归输入线索化
  InThreading(T,&pre);
  //此时结束了最后一个结点的线索化了,下面的代码把头结点的后继指向了最后一个结点.
  //并把最后一个结点的后继也指向头结点,此时树成为了一个类似双向链表的循环.
  pre->rchild = *Thrt;
  pre->RTag = Thread;
  (*Thrt)->rchild = pre;
  return OK;
}

遍历线索二叉树

线索二叉树的遍历就可以通过之前建立好的线索,沿着后继线索依依访问下去就行。

//非递归遍历线索二叉树
Status InOrderTraverse(BiThrTree T) {
  BiThrNode *p = T->lchild;
  while(p!=T){
    while(p->LTag==Link) p = p->lchild;    //走向左子树的尽头
    printf("%c",p->data );
    while(p->RTag==Thread && p->rchild!=T) {  //访问该结点的后续结点
      p = p->rchild; 
      printf("%c",p->data );
    }
    p = p->rchild;
  }
  return OK;
}

完整代码

#include <stdio.h>
#include <stdlib.h>
//函数状态结果代码
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
//Status是函数的类型,其值是函数结果状态代码
typedef int Status;
typedef char TElemType;

typedef enum { Link, Thread } PointerTag;

typedef struct BiThrNode {
  TElemType data;
  struct BiThrNode *lchild, *rchild;
  PointerTag LTag;
  PointerTag RTag;
}BiThrNode, *BiThrTree;

//线索二叉树初始化
Status CreateBiThrNode(BiThrTree * B) {
  char ch;
  scanf("%c", &ch);
  if(ch=='#') *B = NULL;
  else{
    if(!((*B) = (BiThrNode *)malloc(sizeof(BiThrNode)))) exit(OVERFLOW);
    (*B)->data = ch;
    (*B)->LTag = Link;
    (*B)->RTag = Link;
    CreateBiThrNode(&(*B)->lchild);
    CreateBiThrNode(&(*B)->rchild);
  }
  return OK;  
}

//线索二叉树线索化
void InThreading(BiThrTree B,BiThrTree *pre) {
  if(!B) return;

  InThreading(B->lchild,pre);

  if(!B->lchild){
    B->LTag = Thread;
    B->lchild = *pre;
  }

  if(!(*pre)->rchild){
    (*pre)->RTag = Thread;
    (*pre)->rchild = B;
  }

  *pre = B;
  InThreading(B->rchild,pre);
}

//为线索二叉树添加头结点,使之可以双向操作
Status InOrderThreading(BiThrTree *Thrt,BiThrTree T){
  if(!(*Thrt = (BiThrTree)malloc(sizeof(BiThrNode)))) exit(OVERFLOW);
  (*Thrt)->LTag = Link;
  (*Thrt)->RTag = Thread;
  (*Thrt)->rchild = (*Thrt);
  if(!T) {
    (*Thrt)->lchild = (*Thrt);
    return OK;       //若根结点不存在,则该二叉树为空,让该头结点指向自身.
  }
  BiThrTree pre;
  //令头结点的左指针指向根结点
  pre = (*Thrt);
  (*Thrt)->lchild = T;
  //开始递归输入线索化
  InThreading(T,&pre);
  //此时结束了最后一个结点的线索化了,下面的代码把头结点的后继指向了最后一个结点.
  //并把最后一个结点的后继也指向头结点,此时树成为了一个类似双向链表的循环.
  pre->rchild = *Thrt;
  pre->RTag = Thread;
  (*Thrt)->rchild = pre;
  return OK;
}

//非递归遍历线索二叉树
Status InOrderTraverse(BiThrTree T) {
  BiThrNode *p = T->lchild;
  while(p!=T){
    while(p->LTag==Link) p = p->lchild;    //走向左子树的尽头
    printf("%c",p->data );
    while(p->RTag==Thread && p->rchild!=T) {  //访问该结点的后续结点
      p = p->rchild; 
      printf("%c",p->data );
    }
    p = p->rchild;
  }
  return OK;
}

int main() {
  BiThrTree B,T;
  CreateBiThrNode(&B);
  InOrderThreading(&T,B);
  printf("中序遍历二叉树的结果为:");
  InOrderTraverse(T);
  printf("\n");
}

//测试数据:abc##de#g##f###

参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容