有N个节点的链式结构的二叉树(lchild,data,rchild)一共用2N个指针域和N-1条分支线树(即已经占用的指针域),则还有2N-(N-1)=N+1个空指针域。
为了避免浪费资源,我们引入线索化。
线索化:将链式二叉树的空指针域利用起来,指向本节点的前驱或后继,形成线索。此种二叉树称为线索二叉树。线索化的引入需要在节点中增加ltag和rtag标志域,根据其取值的不同判断*lchild和*rchild是指向的左右孩子,还是当前节点的前驱和后继。
二叉树线索化后如同链表一般,而当将遍历序列头和遍历序列尾分别与添加的头节点关联起来,则相当于一个双链表
遍历序列头、尾节点,头节点:
比如一个中序遍历序列 #B#D#A#C,B称为次遍历序列的头节点,C称为此遍历序列的尾节点,根节点是A,而头节点的左孩子指针域(或右)就指向根节点。
代码分别实现了:
StrAssign---生成静态先序遍历序列
CreateBiThrTree---利用先序遍历生成链式二叉树
InThreading-----中序遍历线索化的递归实现
InOrderThreading-------调用InThreading实现遍历序列头、尾与头节点的关联
InOrderTraverse_Thr------利用线索化二叉树链式结构 实现 非递归式中序遍历,大大降低了时间复杂度,时间复杂度为O(n)。
以上函数关系:CreateBiThrTree通过StrAssign得到先序遍历输入序列,生成链式二叉树,InOrderThreading通过中序遍历方式遍历二叉树,并构造头节点,通过线索化
生成一个类似双链表的链式二叉树,在此基础上,才可以通过InOrderTraverse_Thr实施非递归式的中序遍历。
难点解析:
InThreading---主要是递归,费脑力,这里将讲解InOrderThreading中的InThreading
在本例中,树的递归实现是从根节点开始,根节点就代表一颗树。以最简单的三个节点的满二叉树为例 BAC(中序遍历,A是根节点),D为头节点。
以为下分别用A,B,C表示指向三个节点的指针,并在三节点的数据域中写入A,B,C分别代表三个节点。
图中没有ltag和rtag值的变化,只是对指针指向做个解析。
递归解析:
初始状态如图1所示。
1.从根节点开始 InThreading(A),进入函数到InThreading(A->lchild)=InThreading(B)
2.进入InThreading(B),再到InThreading(B->lchild)
3.进入InThreading(B->lchild),由于B->lchild为空,则退回到InThreading(B)中
4.在InThreading(B)中,此时的pre=D,由于B->lchild为空,则执行
if(!B->lchild) //如果没有左孩子
{
B->ltag = Thread; //前驱线索化
B->lchild = pre; //左孩子指针指向前驱
}
if(!pre->rchild)
{
pre->rtag = Thread; //后继线索
pre->rchild = B; //前驱右孩子指针指向后继节点(当前节点为(*T))
}
pre = B; //保持pre指向(*T)的前驱
InThreading(&B->rchild);
执行第4步完毕的状态如图2所示。
5.在InThreading(B)中,执行InThreading(&B->rchild),B->rchild为空,退回到InThreading(B)中,InThreading(B)执行完,
即InThreading(A->lchild)执行完,退回到InThreading(A)中。
6.在InThreading(A)中,此时的pre=B,由于A存在左孩子,则执行
if(!pre->rchild) //前驱没有右孩子
{
pre->rtag=Thread; //后继线索
pre->rchild=A; //前驱右孩子指针指向后继(当前结点A)
}
pre=A; //保持pre指向p的前驱
InThreading(A->rchild); //递归右子树线索化
执行第6步完毕后的状态图3所示。
7.执行6后,pre=A,并进入InThreading(A->rchild),即InThreading(C)
8.在InThreading(C)中,执行InThreading(C->rchild),由于C->rchild为空,则回到InThreading(C)中。
9.在InThreading(C)中,此时pre=A,且pre->rchild不为空,则执行
if(!C->lchild) //如果没有左孩子
{
C->ltag = Thread; //前驱线索化
C->lchild = pre; //左孩子指针指向前驱
}
pre = C; //保持pre指向(*T)的前驱
InThreading(&C->rchild);
10.在InThreading(C)中,一直执行到InThreading(&C->rchild),由于C->rchild为空, 从InThreading(&C->rchild)返回到InThreading(C)中,并且InThreading(C)也执行完,
返回到InThreading(A)中,InThreading(A)执行完,此时的pre=C,即遍历序列的尾节点。
递归完毕后如图4所示。
以下是完整的代码实现:(站在巨人的肩膀上)
运行平台:CenOS6.4 GCC 4.4.7
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 100
typedef int Status;
typedef char ElemType;
ElemType Nil = '#';
typedef enum {Link,Thread} PointerTag; //Link=0表示指向左右孩子指针
//Thread=1表示指向前驱动或后继的线索
typedef struct BiThrNode
{
ElemType data;
struct BiThrNode *lchild,*rchild;
PointerTag ltag;
PointerTag rtag;
}BiThrNode,*BiThrTree;
typedef ElemType String[MAXSIZE];
String StrT;
/*辅助函数:产生静态输入序列*/
Status StrAssign(String StrT,char *chars)
{
if(NULL == chars||strlen(chars)>MAXSIZE)
{
return ERROR;
}
int i;
for(i=0;i<strlen(chars);i++)//在for()中定义i,则在编译时添加参数 -std=c99,而在此种
//模式下,CreateBiThrTree中exit(OVERFLOW)会出现OVERFLOW
//undeclare 错误
{
StrT[i] = *(chars+i);
}
return OK;
}
/*end*/
int pos = 0; //StrT的位置索引
/*利用前序遍历递归的方式生成二叉树*/
Status CreateBiThrTree(BiThrTree *T)
{
if(NULL==T)
{
return ERROR;
}
ElemType ch;
ch = StrT[pos++];
if(Nil == ch)
{
*T = NULL;
}else
{
*T = (BiThrTree)malloc(sizeof(BiThrNode));
if(NULL == *T)
{
exit(OVERFLOW);//
}
(*T)->data = ch;
CreateBiThrTree(&(*T)->lchild);
if((*T)->lchild)
{
(*T)->ltag = Link;
}
CreateBiThrTree(&(*T)->rchild);
if((*T)->rchild)
{
(*T)->rtag = Link; //这里写成了Thread,导致调试了很久
}
return OK;
}
}
/*end*/
/*中序遍历线索化*/
BiThrTree pre=NULL;//始终指向访问过的节点的全局变量
Status InThreading(BiThrTree *T)
{
if(NULL == *T)
{
return ERROR;
}
if(T)
{
InThreading(&(*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)的前驱
InThreading(&(*T)->rchild); /*递归右子树线索化*/
}
}
/*end*/
/*构建首尾相连的线索化二叉树链式结构*/
/*3步:(1)创建头节点
* (2)线索化
* (3)指针互联*/
Status InOrderThreading(BiThrTree *head,BiThrTree *T)
{
*head = (BiThrTree)malloc(sizeof(BiThrNode));
if(!*head)
{
exit(OVERFLOW);
}
/*构建头节点*/
(*head)->ltag = Link;
(*head)->rtag = Thread;
(*head)->rchild = (*head);
if(!(*T))
{
(*head)->lchild = (*head);
}
/*构建完毕*/
else
/*线索化,指针互联*/
{
(*head)->lchild = (*T);
pre = (*head);
InThreading(T);//线索化后,pre已经被赋值为 遍历序列末尾节点的指针
pre->rtag = Thread;
pre->rchild = (*head);
(*head)->rchild = pre;
return OK;
}
}
/*end*/
/*中序遍历 线索二叉树T--非递归算法*/
Status InOrderTraverse_Thr(BiThrTree *T)
{
if(NULL == T)
{
return ERROR;
}
BiThrTree p;
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;
}
}
/*end*/
int main()
{
char *chars = "ABDH#K###E##CFI###G#J##";
StrAssign(StrT,chars);
BiThrTree *T,*head;
T = (BiThrTree *)malloc(sizeof(BiThrTree));
head = (BiThrTree *)malloc(sizeof(BiThrTree));
*T = NULL;
*H = NULL;
CreateBiThrTree(T);
/*中序遍历,并线索化二叉树*/
InOrderThreading(head,T);
printf("非递归中序遍历\n");
InOrderTraverse_Thr(H);
printf("\n");
}