期末考试快要到了,前段时间却因为在忙别的科目,忽视了数据结构,直到近几天开始重新从二叉树这部分看起,才发现原来自己真的是有点眼高手低了,为此,特意花了一个下午,尝试从最简单的一个字符二叉树开始做起,回忆一下有关线索二叉树的具体C语言实现过程,就当是做个总结吧。
以下图所示的字符树为例,实现二叉树的前序创建,中序线索化,中序遍历输出以及中序释放的整个过程。
一、先序创建![](https://i-blog.csdnimg.cn/blog_migrate/f8160ec2eeeed61ecc73f464643a1faf.png)
使用递归函数实现二叉树的先序创建,定义的二叉树的数据结构和实现代码如下所示:
/*中序线索二叉树*/
typedef struct bitree{
struct bitree *lchild , *rchild;
char ch;
int LTag, RTag;
}BiTreeNode,*LkBiTree;
/*一般使用先序创建*/
void PreCreBiTree(LkBiTree *T)
{
char temp;
scanf("%c",&temp);
/*叶子结点判定*/
if(temp=='#')
{
*T = NULL;
}
else
{
/*生成新节点,先赋值,再生成左右节点*/
*T = (LkBiTree)malloc(sizeof(BiTreeNode));
(*T)->ch = temp;
/*表达式的书写要注意*/
PreCreBiTree(&((*T)->lchild));
PreCreBiTree(&((*T)->rchild));
}
}
从代码中可以看出这种方法读取的条件适用于对于空指针必须用 # 填充,而且对于提供的这个字符二叉树应当输入的前序序列为 ABDG###EH##I##CF#J###
其中这一部分代码的易错点在于对传入参数及递归形式的处理,为了使创建能够成功,我们只能传递指向结构体指针的指针,这样就导致在书写递归函数的参数时就会有 &((*T)->lchild) 这种比较复杂的写法。
二、中序线索化
这一部分的实现可以说是整个函数中最重要的一部分了,附上我的代码
为了使用递归函数创建一个带头结点的线索二叉树,我创建了一个全局变量 pre , 最初它指向头结点,LTag = 0 , lchild 指向根节点, RTag = 1, 最初用于指向中序遍历到的第一个节点,在最后将其置为中序遍历到的最后一个节点。
/*中序线索化*/
void InThreading(LkBiTree T)
{
if(T!=NULL)
{
InThreading(T->lchild);
if(!T->lchild)
{
T->LTag = 1;
T->lchild = pre;
}
else
{
T->LTag = 0;
}
if(!pre->rchild)
{
pre->RTag = 1;
pre->rchild = T;
}
else
{
pre->RTag = 0;
}
pre = T;
InThreading(T->rchild);
}
}
对于这一部分,最好还是使用递归算法来实现更为方便一些,重点在于中间两个 if - else 的准确书写,经过对二叉树结构特点的分析,我们可以知道每次执行这两段条件判断都是在一个左递归结束之后,我们先对左子树进行判定,若为空,则代表可以用于创建前驱指针,以及设定标志值LTag , 反之同理,为了能够处理好后继节点的设置问题,我们这时候就要用到前面设置的全局变量pre 实现后继节点的连接。
如上图所示,用蓝色线条表示左右子树之间的正常连接,红色线条代表我们新更改的线索指针。
接下来是实现连接上头结点之后的完整线索化。
/*全局变量pre*/
LkBiTree pre;
/*带头结点的中序线索化*/
void InorderThreading(LkBiTree *headptr, LkBiTree T)
{
*headptr = (LkBiTree)malloc(sizeof(BiTreeNode));
pre = *headptr;
pre->LTag = 0;
pre->RTag = 1;
pre->rchild = pre;
if(!T)
{
pre->lchild = pre;
}
else
{
pre->lchild = T;
InThreading(T);
pre->rchild = *headptr;
pre->RTag = 1;
(*headptr)->rchild = pre;
}
}
可以看出对这一部分的操作主要是对头结点进行设置以及在上一个函数完成后,将中序序列的最后一个元素的右指针指向头结点,头结点的右指针指向该节点,完成整个线索化过程。
三、中序线索遍历输出
这一部分的代码如下
/*中序遍历线索二叉树,非递归算法*/
void InThTraverse(LkBiTree head)
{
LkBiTree p = head->lchild;
while(p!=head)
{
while(p->LTag==0)
{
p = p->lchild;
}
printf("%c",p->ch);
while(p->RTag==1&&p->rchild!=head)
{
p = p->rchild;
printf("%c",p->ch);
}
p = p->rchild;
}
}
我使用了非递归算法,对于这一部分的算法,重点在于如何实现根据序列顺序由某一节点访问下一节点。根据中序序列的定义,我们知道节点的顺序为 左 -> 中 -> 右 ,所以当停留在某一节点时,我们就应通过循环不停地寻找左子树,直至到达叶子节点,此后便可通过后继线索去以次寻找后续节点,直到完成循环,找到头结点 head , 循环结束。
四、删除节点,释放空间
对于一个完整的代码而言只完成了前面的那些步骤还不够,我们还需要将之前分配的堆空间释放以免造成内存的泄露,这一部分的难点就在于以什么样的方法去一次释放节点的空间。对于一般的二叉树,我们可以通过简单的递归函数后序遍历依次删除各个节点,但是对于线索二叉树而言,那样并不合适,最好的方法应该是沿着中序线索遍历的顺序将节点依次删除,我们所需要做的就只是对上一段代码做出微小的改动,代码如下
/*中序线索化遍历释放*/
void DestroyBiTree(LkBiTree head)
{
LkBiTree p = head->lchild;
LkBiTree temp = head;
while(p!=head)
{
while(p->LTag==0)
{
p = p->lchild;
}
free(temp);
temp = p;
while(p->RTag==1&&p->rchild!=head)
{
p = p->rchild;
free(temp);
temp = p;
}
p = p->rchild;
}
free(temp);
}
注意:这段代码在一开始就将头结点的内存释放掉了,但是这并不影响head指针指向的地址(甚至也不会影响该指针指向的值),这是因为free函数的作用仅仅是让编译器告诉操作系统分配的这一部分空间已经可以收回,却没有将该段空间里的数据清空,所以我们依然可以访问对应数据,也可以继续将head充当循环终止的判定条件。
整个实现的代码如下:
#include<stdio.h>
#include<stdlib.h>
/*中序线索二叉树*/
typedef struct bitree{
struct bitree *lchild , *rchild;
char ch;
int LTag, RTag;
}BiTreeNode,*LkBiTree;
/*全局变量pre*/
LkBiTree pre;
/*一般使用先序创建*/
void PreCreBiTree(LkBiTree *T)
{
char temp;
scanf("%c",&temp);
/*叶子结点判定*/
if(temp=='#')
{
*T = NULL;
}
else
{
/*生成新节点,先赋值,再生成左右节点*/
*T = (LkBiTree)malloc(sizeof(BiTreeNode));
(*T)->ch = temp;
/*表达式的书写要注意*/
PreCreBiTree(&((*T)->lchild));
PreCreBiTree(&((*T)->rchild));
}
}
/*中序线索化*/
void InThreading(LkBiTree T)
{
if(T!=NULL)
{
InThreading(T->lchild);
if(!T->lchild)
{
T->LTag = 1;
T->lchild = pre;
}
else
{
T->LTag = 0;
}
if(!pre->rchild)
{
pre->RTag = 1;
pre->rchild = T;
}
else
{
pre->RTag = 0;
}
pre = T;
InThreading(T->rchild);
}
}
/*带头结点的中序线索化*/
void InorderThreading(LkBiTree *headptr, LkBiTree T)
{
*headptr = (LkBiTree)malloc(sizeof(BiTreeNode));
pre = *headptr;
pre->LTag = 0;
pre->RTag = 1;
pre->rchild = pre;
if(!T)
{
pre->lchild = pre;
}
else
{
pre->lchild = T;
InThreading(T);
pre->rchild = *headptr;
pre->RTag = 1;
(*headptr)->rchild = pre;
}
}
/*中序遍历线索二叉树,非递归算法*/
void InThTraverse(LkBiTree head)
{
LkBiTree p = head->lchild;
while(p!=head)
{
while(p->LTag==0)
{
p = p->lchild;
}
printf("%c",p->ch);
while(p->RTag==1&&p->rchild!=head)
{
p = p->rchild;
printf("%c",p->ch);
}
p = p->rchild;
}
}
/*中序线索化遍历释放*/
void DestroyBiTree(LkBiTree head)
{
LkBiTree p = head->lchild;
LkBiTree temp = head;
while(p!=head)
{
while(p->LTag==0)
{
p = p->lchild;
}
free(temp);
temp = p;
while(p->RTag==1&&p->rchild!=head)
{
p = p->rchild;
free(temp);
temp = p;
}
p = p->rchild;
}
free(temp);
}
int main()
{
LkBiTree T,head;
PreCreBiTree(&T);
printf("首节点为%c\n",T->ch);
InorderThreading(&head, T);
InThTraverse(head);
DestroyBiTree(head);
return 0;
}
欢迎大家批评指正......