二叉树链式存储的缺点就是有很多指向空的指针,这样会浪费很多空间。
一棵有n个结点的二叉树,共有2n个指针域,有n-1个分支线,就有2n-(n-1) = n + 1的空指针域。
每个空指针都应该被利用起来,可以让它们指向遍历时其前一个结点或者后一个结点,称遍历时的前一个结点为当前结点的前驱, 遍历时的后一个结点为这个结点的后继。
比如下面的一棵树,中序遍历为:HDIBJEAFCG
对于I结点来说,前驱就是D,后继就是B,原来的二叉树I的左右结点都没有用到。现在可以让它记录一下其前驱和后继,一般是左前驱,右后继。
线索的实现:
/*
功能:中序遍历进行中序线索化(最左结点的左孩子,最右结点的右孩子指向NULL,且最右结点没有线索化)
参数:根结点指针
注:左树空接前驱, 右树空接后继。
前驱和后继是相对于中序遍历的结果。
先打印的是前驱,后打印的是后继。
*/
BiThrTree pre = NULL; //全局变量,用于指向前一个访问的结点
void InThreading(BiThrTree p)
{
if (p)
{
InThreading(p->lchild); //递归左子树线索化
if (p->lchild == NULL) //如果左子树为空
{
p->LTag = Thread; //前驱线索
p->lchild = pre; //左指针指向前驱,最开始是空
}
if (pre != NULL && pre->rchild == NULL)//前驱不为空且没有右孩子
{
pre->RTag = Thread; //后继线索
pre->rchild = p; //前驱右孩子指向后继
}
pre = p; //保证pre是p的前驱,也就是p是pre的后继
InThreading(p->rchild);//递归右子树线索化
}
}
这样线索完了后,如果再加一个头结点,其左孩子指向根,右孩子指向最后继(G结点)。把头结点作为最前驱,同时也作为最后继(G结点)的后继。
可以通过左树是否是Thread找到中序遍历的第一个结点(H结点),然后根据右线索找到后面的结点。如果结点没有右线索怎么办?(比如B结点)那就表明当前结点有右树(E为根的树),再去用同样的方法(左树是否是Thread)来找到它的第一个结点(J)
二叉线索树遍历方法:
/*
功能:中序遍历二叉线索树
参数:头结点指针
*/
void InOrderTraverse(BiThrTree head)
{
BiThrTree p = head->lchild;//p指向根结点
while(p != head)//没有回到头结点,也就是没有遍历完
{
while(p->LTag == Link)//找到左孩子是线索的结点,这个结点没有左孩子,根据中序遍历规则,它就是第一个结点
p = p->lchild;
printf("%c", p->data);//输出p为根的第一个结点
while(p->RTag == Thread && p->rchild != head)//如果右孩子有线索且不为头结点,根据线索找后面的结点
{
p = p->rchild;
printf("%c", p->data);
}
//右孩子不是线索,就去右孩子,把右孩子当作一棵树来遍历
p = p->rchild;//访问右孩子,p作为右树的根
}
}
可以看出用LTag找到树(也包括子树)的第一个结点,用RTag来找后面线索结点。把一对二的关系变成了一对一的关系。
对于需要经常遍历的操作,线索二叉树的时间复杂度0(n)会比用递归方法降低很多。
二叉线索树释放内存:
/*
功能:释放树
参数:树的头结点指针
*/
void FreeBiThrTree(BiThrTree T)
{
BiThrTree p = T->lchild;
BiThrTree q = p; // 用于存要释放的结点
while(p != T)
{
while(p->LTag == Link)//找到第一个结点
{
p = p->lchild;
}
//如果有右孩子有线索,且不为头结点,那可以根据线索找后面的结点, 找到一个释放一个
while(p->RTag == Thread && p->rchild != T)
{
q = p;//存住要释放结点内存
p = p->rchild;
free(q);//释放
}
//右孩子没有线索,根据中序遍历规则,去遍历右树,p指向右孩子作为右树的根结点再进行下一轮循环
q = p;
p = p->rchild;
free(q);
}
}
头文件head.h
#ifndef HEAD_H
#define HEAD_H
typedef enum{
Link, //没有线索
Thread //有线索
}PointerTag;
typedef char TElementType;
typedef struct Node* BiThrTree;
typedef struct Node{
TElementType data;
BiThrTree lchild;
BiThrTree rchild;
/*标志有没有线索*/
PointerTag LTag;
PointerTag RTag;
}BiThrNode;
BiThrTree pre; //全局变量,用于指向前一个访问的结点
/*中序遍历进行中序线索化*/
void InThreading(BiThrTree p);
/*建立二叉树*/
BiThrTree CreateBiThrTree(void);
/*释放内存*/
void FreeBiThrTree(BiThrTree T);
/*中序遍历*/
void InOrderTraverse(BiThrTree T);
/*添加头指针*/
BiThrTree ChangePoint(BiThrTree T);
#endif
操作集operation.c
#include "head.h"
#include <stdio.h>
#include <stdlib.h>
/*
功能:中序遍历进行中序线索化
参数:根结点指针
注:左树空接前驱, 右树空接后继。
前驱和后继是相对于中序遍历的结果。
先打印的是前驱,后打印的是后继。
*/
void InThreading(BiThrTree p)
{
if (p)
{
InThreading(p->lchild); //递归左子树线索化
if (p->lchild == NULL) //如果左子树为空
{
p->LTag = Thread; //前驱线索
p->lchild = pre; //左指针指向前驱,最开始是空
}
if (pre != NULL && pre->rchild == NULL)//前驱不为空且没有右孩子
{
pre->RTag = Thread; //后继线索
pre->rchild = p; //前驱右孩子指向后继
}
pre = p; //保证pre是p的前驱,也就是p是pre的后继
InThreading(p->rchild);//递归右子树线索化
}
}
/*
功能:建立一棵空二叉树
返回:空树头指针
*/
BiThrTree CreateBiThrTree(void)
{
BiThrTree T;
TElementType ch;
scanf("%c", &ch);
if (ch == '#')
{
return NULL;
}
else
{
T = (BiThrTree)malloc(sizeof(BiThrNode));
if (T == NULL)
{
printf("out of space!\n");
exit(EXIT_FAILURE);
}
T->data = ch;
T->LTag = Link;
T->RTag = Link;
T->lchild = CreateBiThrTree();
T->rchild = CreateBiThrTree();
}
return T;
}
/*
功能:释放树
参数:树的头结点指针
*/
void FreeBiThrTree(BiThrTree T)
{
BiThrTree p = T->lchild;
BiThrTree q = p; // 用于存要释放的结点
while(p != T)
{
while(p->LTag == Link)//找到第一个结点
{
p = p->lchild;
}
//如果有右孩子有线索,且不为头结点,那可以根据线索找后面的结点, 找到一个释放一个
while(p->RTag == Thread && p->rchild != T)
{
q = p;//存住要释放结点内存
p = p->rchild;
free(q);//释放
}
//右孩子没有线索,根据中序遍历规则,去遍历右树,p指向右孩子作为右树的根结点再进行下一轮循环
q = p;
p = p->rchild;
free(q);
}
}
/*
功能:中序遍历二叉线索树
参数:头结点指针
*/
void InOrderTraverse(BiThrTree head)
{
BiThrTree p = head->lchild;//p指向根结点
while(p != head)//没有回到头结点,也就是没有遍历完
{
while(p->LTag == Link)//找到左孩子是线索的结点,这个结点其没有左孩子
{
p = p->lchild;
}
printf("%c", p->data);//输出这个结点
while(p->RTag == Thread && p->rchild != head)//如果右孩子有线索且不为头结点,根据线索找后面的结点
{
p = p->rchild;
printf("%c", p->data);
}
//右孩子不是线索,就去右孩子
p = p->rchild;//访问右孩子,后继结点
}
}
/*
功能:添加头结点,将二叉树的空指针指向头结点
参数:根结点指针,头结点指针的地址
*/
BiThrTree ChangePoint(BiThrTree T)
{
if (T == NULL)
{
exit(EXIT_FAILURE);
}
BiThrTree head = (BiThrTree)malloc(sizeof(BiThrNode));
BiThrTree record = T;
while((record)->rchild)//找到最右结点,因为它是中序遍历的最后一个结点
{
(record) = (record)->rchild;
}
head->RTag = Thread;
head->rchild = (record);//头结点右孩子指向最右结点
(record)->RTag = Thread;
(record)->rchild = head;//最右结点也指向头结点
(record) = T; //record回到根结点
while((record)->lchild)//找到最左结点
{
(record) = (record)->lchild;
}
head->LTag = Link;
head->lchild = T;//头结点左孩子指向根结点
(record)->LTag = Thread;
(record)->lchild = head;//最左结点指向头结点
return head;
}
主函数main.c
#include <stdio.h>
#include <stdlib.h>
#include "head.h"
#include "operation.c"
int main(void)
{
BiThrTree T = CreateBiThrTree();
InThreading(T);
BiThrTree head = ChangePoint(T);
InOrderTraverse(head);
printf("\n");
FreeBiThrTree(head);
free(head);
head = NULL;
return 0;
}