数据结构 - 线索二叉树

来源:互联网 发布:java 反斜杠加双引号 编辑:程序博客网 时间:2024/04/29 02:43

线索树

  遍历二叉树是按一定的规则将树中的结点排列成一个线性序列,即是对非线性结构的线性化操作。如何找到遍历过程中动态得到的每个结点的直接前驱和直接后继(第一个和最后一个除外)?如何保存这些信息?问:一棵有n个结点的二叉树,有多少个空闲指针域未用?    若一棵二叉树有n个结点,则有n-1条指针连线 , 而n个结点共有2n个指针域(Lchild和Rchild) ,所以有n+1个空闲指针域未用。可以利用这些空闲的指针域来存放结点的直接前驱和直接后继信息。

对结点的指针域做如下规定:
◆ 若结点有左孩子,则Lchild指向其左孩子,否则,指向其直接前驱;
◆ 若结点有右孩子,则Rchild指向其右孩子,否则,指向其直接后继;

线索二叉树的结点结构

用这种结点结构构成的二叉树存储结构;叫做线索链表;指向结点前驱和后继的指针叫做线索;按照某种次序遍历,加上线索的二叉树称之为线索二叉树。

线索二叉树的结点结构与示例typedef struct BiTreeNode{   ElemType  data;struct BiTreeNode *Lchild , *Rchild ; int  Ltag , Rtag ;}BiThrNode ;
如何在线索树中找结点的直接后继?以图 ,所示的中序线索树为例:

这里写图片描述

◆ 若树中结点的右链是线索(Rtag=1),则右链直接指示了结点的直接后继,如结点G的直接后继是结点E。
◆ 若树中结点的右链是指针( Rtag=0)。根据中序遍历的规律, Rtag=0的结点的直接后继是遍历其右子树时访问的第一个结点,即右子树中最左下位置的(叶子)结点。如结点C的直接后继:沿右指针找到右子树的根结点F,然后沿左链往下直到Ltag=1的结点即为C的直接后继结点H。

后序遍历线索树

在后序遍历的线索树中找结点的直接后继比较复杂,可分以下三种情况:

若结点是二叉树的根结点:其直接后继为空;
若结点是其父结点的左孩子且其父结点没有右子树:直接后继为其父结点;
若结点是其父结点的左孩子且其父结点有右子树:直接后继是对其父结点的右子树按后序遍历的第一个结点。

线索化二叉树

二叉树的线索化指的是依照某种遍历次序使二叉树成为线索二叉树的过程。

线索化的过程就是在遍历过程中修改空指针使其指向直接前驱或直接后继的过程。   下面主要讨论按中序遍历次序线索化二叉树。   仿照线性表的存储结构,在二叉树的线索链表上也添加一个头结点head,头结点的指针域的安排是:

◆ Lchild域:指向二叉树的根结点;
◆ Rchild域:指向中序遍历时的最后一个结点;
◆ 二叉树中序序列中的第一个结点Lchild指针域和最后一个结点Rchild指针域均指向头结点head。

  添加了头结点的线索二叉树,如同为二叉树建立了一个双向线索链表,对一棵线索二叉树既可从头结点也可从最后一个结点开始按寻找直接后继进行遍历。显然,这种遍历不需要堆栈。
#define  MAX_NODE   50typedef enmu{ Link , Thread} PointerTag ;/*  Link=0表示指针, Thread=1表示线索   */typedef struct BiThrNode{   ElemType  data;struct BiTreeNode *Lchild , *Rchild ; PointerTag  Ltag , Rtag ;}BiThrNode, *BiThrTree;

按先序序列构造二叉线索树

ElemType Nil=‘#’;   /*以#为空 */Status CreateBiThrTree(BiThrTree *T){      ElemType ch;       scanf("%c",&ch);    if(h==Nil)  *T=NULL;    else    {   *T=(BiThrTree)malloc(sizeof(BiThrNode));        if(!*T) return ERROR;        (*T)->data=ch; /* 生成根结点(前序) */        CreateBiThrTree(&(*T)->lchild); /* 递归构造左子树 */        if((*T)->lchild)    (*T)->LTag=Link; /* 有左孩子 */        CreateBiThrTree(&(*T)->rchild); /* 递归构造右子树 */        if((*T)->rchild)   (*T)->RTag=Link; /* 有右孩子 */    }    return OK;}

中序遍历线索化的递归函数

BiThrNode *pre//全局变量,始终指向刚刚访问过的结点void  InThreading (BiThrNode *T){   if(T)    {  Inorder_Threading(T->lchild) /* 递归左子树线索化 */    if(!T->lchild) /* 没有左孩子 */    {      T->LTag=Thread; /* 前驱线索 */        T->lchild=pre; /* 左孩子指针指向前驱 */     }    if(!pre->rchild) /* 前驱没有右孩子 */    {       pre->RTag=Thread; /* 后继线索 */         pre->rchild=T; /* 前驱右孩子指针指向后继(当前结点T) */      }      pre=T; /* 保持pre指向T的前驱 */      Inorder_Threading(T->rchild); /* 递归右子树线索化 */    }}

前驱结点的线索化:if(!T->lchild)表示如果某结点的左指针域为空,因为其前驱结点刚刚访问过,赋值给了pre,所以可将pre赋值给T->lchild,并修改T->LTag=Thread(即定义为1)以完成前驱结点的线索化;

后继结点的线索化:因此时后继结点还没有访问到,因此只能对它的前驱结点pre的右指针rchild做判断, if(!pre->rchild)表示如果前驱的右指针域为空,则T就是pre的后继,于是pre->rchild=T,并且设置pre->RTag=Thread,完成后继结点的线索化。

完成前驱和后继的判断后,要将当前结点T赋值给pre,以便于下次使用。

/* 中序遍历二叉树T,并将其中序线索化,Thrt指向头结点 */Status InOrderThreading(BiThrTree *Thrdhead, BiThrTree T){   * Thrdhead =(BiThrTree)malloc(sizeof(BiThrNode));     if(!* Thrdhead )  return ERROR;    (* Thrdhead )->LTag=Link; /* 建头结点 */    (* Thrdhead )->RTag=Thread;    (* Thrdhead )->rchild= NULL; //右指针此时为空    if(!T)   (* Thrdhead )->lchild= * Thrdhead; //若二叉树空,则左指针回指    else    {        (* Thrdhead )->lchild=T; //非空,指向根节点        pre=(* Thrdhead );        InThreading(T); /* 中序遍历进行中序线索化 */        pre->rchild=* Thrdhead; //pre是中序遍历的最后一个结点        pre->RTag=Thread; /* 最后一个结点线索化 */        (* Thrdhead )->rchild=pre;    }    return OK;}

线索二叉树遍历

    线索二叉树的创建虽然比较复杂,但在线索二叉树中,由于有线索存在,在某些情况下可以方便地找到指定结点在某种遍历序列中的直接前驱或直接后继。   此外,在线索二叉树上进行某种遍历比在一般的二叉树上进行这种遍历要容易得多,不需要设置堆栈,且算法十分简洁。
/* 中序遍历二叉线索树T(头结点)的非递归算法 */Status InOrderTraverse_Thr(BiThrTree T){     BiThrTree p;    p=T->lchild;   /* p指向根结点 */    while(p!=T)  /* 空树或遍历结束时,p==T */    {      while(p->LTag==Link)            p=p->lchild; //当LTag==0时循环到中序序列第一个结点        visit(p->data);        while(p->RTag==Thread&&p->rchild!=T)        {            p=p->rchild;            visit(p->data); /* 访问后继结点 */        }        p=p->rchild;    }    return OK;}
1 0