数据结构—线索二叉树
1.二叉链表中空间资源的浪费
我们利用节点建立了二叉链表,但是我们发现二叉链表中存在这许多空指针,那么这部分空间就被浪费了,我们应该想办法解决这个问题
假设有一个节点个数为n的二叉链表,那么其中就有2n个指针域,n个节点就会有n-1个分支线树,也就是说就会有2n-(n-1)=n+1个空指针域。如上图,十个节点的二叉树就会有十一个空指针域。
我们在上一篇博客中也说过,有三种遍历二叉树的方法(前序,中序,后序),不管是哪一种遍历方法,在遍历时都会产生特定的前后顺序。但是,实际在树的结构中,我们只知道一个节点的两个孩子节点的指针,但是不知道遍历时真正的前驱和后继,所以我们干脆在第一次遍历时就利用这些空指针域,让其指向该节点的前驱或者后继。
- 我们把这种指向前驱和后继的指针称为线索,加上线索的二叉 链表称为线索链表,相应的二叉树称为线索二叉树
2.线索化
对于线索二叉树,我们有这样的规定:
- 如果一个节点的左孩子节点为空,则让该指针指向其前驱节点
- 如果一个节点的右孩子节点为空,则让该指针指向其后继节点
例如上面的二叉树,我们可以用中序遍历的方法使其线索化:
中序遍历顺序:H,D,I,B,J,E,A,F,C,G
在这次遍历建立了线索二叉树后,我们再此遍历就不需要使用之前的遍历方法了,因为我们已经将二叉树中叶子节点中的空指针充分利用,其实等于将我们的二叉树变成了一个双向链表,这样我们的插入删除节点,查找节点都更方便。
但现在有一个问题,我如何直到一个节点的左孩子指针是指向它的左孩子还是它的前驱呢?同理,我也没法直到这个节点的右孩子指针是指向其右孩子节点还是其后继。
wile解决这个问题,我们设立如下的数据结构:
typedef enum {Link,Thread} PointerTag;//孩子指针域状态(指向孩子节点或前后驱)
typedef struct BiThrNode
{
int data;
BiThrNode *lchild,*rchild;
PointerTag LTag; //左孩子指针状态
PointerTag RTag; //右孩子指针状态
} BiThrNode,*BiThrTree;
我们给每个节点新增两个枚举类型的状态标记,若该标记为Link,则表名:该指针指向的是该节点的孩子节点。若该标记为Thread,则表示该指针指向的是这个节点的前驱或后继。
接下来就是线索化的过程,下面是中序线索化的核心代码:
BiThrTree pre;//前驱节点(全局变量)
void InThreading(BiThrTree T)
{
if(T)
{
InThreading(T->lchild);
if(!T->lchild)
{
T->LTag = Thread;
T->lchild = pre;
}
if(pre != NULL && !pre->rchild)
{
pre->RTag = Thread;
pre->rchild = T;
}
pre = T;
InThreading(T->rchild);
}
}
类似于中序遍历的代码,只不过把中间的输出语句改为了其他的一些操作,这里我们来分析一下:
- 若该节点不为空,则先对其左孩子节点进行中序线索化。
- 判断:如果该节点没有左孩子的话,就调整左孩子指针状态,并让左孩子指针指向该节点的前驱节点pre;如果前驱节点没有右孩子的话,就调整右孩子指针状态,并让右孩子指针指向当先节点(pre节点的后继节点,也就是当前节点)。
- 再对其右孩子节点进行中序线索化
我们上面也给出了中序线索化之后的二叉树关系图片,可以去验证一下
3.遍历线索二叉树
在线索化了二叉树之后,我们的二叉树实际上变成了一个双向链表的结构,那么我们也就不需要再使用之前的三种遍历方法去遍历二叉树了。
我们在二叉树的线索化之前先先指定一个头节点,让该头节点的左孩子指针指向二叉树的根节点,并且作为第一个访问到的叶子节点的前驱。同时,让该头节点的右孩子指针指向遍历到的最后一个节点,并且最后一个节点的右孩子指针指向该头节点(作为最后一个节点的后继)。以下是我们的实现方式:
/**
指定头指针,连接二叉树,并将其线索化后,尾部连接到头指针
*/
BiThrTree InOrderThreading(BiThrTree pHead,BiThrTree T)
{
pHead = (BiThrTree)malloc(sizeof(BiThrNode));//为头节点申请空间
pHead->LTag = Link; //头节点左孩子指针状态:分支线
pHead->RTag = Thread;//头节点右孩子指针状态:线索
pHead->rchild = pHead;//头节点的右孩子指针先指向自己
if(!T)
{
pHead->lchild = pHead;
}
else
{
pHead->lchild = T;//将头节点的左孩子指针指向树的根节点
pre = pHead;//第一个前驱为头节点
InThreading(T);//进行中序线索化
pre->RTag = Thread;//此时pre为中序遍历到的最后一个节点
pre->rchild = pHead;//将最后一个节点的右孩子指针指向头节点
pHead->rchild = pre;//头节点的右孩子指针指向最后一个节点
}
return pHead;
}
实际线索化之后的二叉树:
接下来是线索二叉树的遍历,我们有这样的遍历规则:
- 1.从树根节点开始,声明一个临时的节点指针
- 2.令该节点指针指向树根,一直向左孩子指针传递,直到左孩子指针为线索,输出该节点的数据
- 3.判断该节点的右孩子指针是否是线索且不是头节点。如果符合该调节,则继续传递并输出,直到某节点的右孩子指针是不存放线索或右孩子不是头节点
- 4.传递该临时节点指针指向该节点的右孩子节点,重复2,3过程,直到临时节点指针指向头节点,表明遍历完成
下面是线索二叉树的遍历代码:
/**
线索二叉树的遍历
*/
void InOrderTraverse(BiThrTree pHead) //传参为头节点指针
{
BiThrTree T = pHead->lchild;
while(T != pHead)
{
while(T->LTag == Link)
T = T->lchild;
printf("%d ",T->data);
while(T->RTag == Thread && T->rchild != pHead)
{
T = T->rchild;
printf("%d ",T->data);
}
T = T->rchild;
}
}
4.完整代码以及运行结果检验
#include<stdio.h>
#include<stdlib.h>
typedef enum {Link,Thread} PointerTag;
typedef struct BiThrNode
{
int data;
BiThrNode *lchild,*rchild;
PointerTag LTag;
PointerTag RTag;
} BiThrNode,*BiThrTree;
BiThrTree pre;
/**
二叉树中序遍历线索化
*/
void InThreading(BiThrTree T)
{
if(T)
{
InThreading(T->lchild);
if(!T->lchild)
{
T->LTag = Thread;
T->lchild = pre;
}
if(pre != NULL && !pre->rchild)
{
pre->RTag = Thread;
pre->rchild = T;
}
pre = T;
InThreading(T->rchild);
}
}
/**
二叉树的创建
*/
BiThrTree CreateBiTree(BiThrTree T,int step)
{
printf("请输入%d层的数据:",++step);
int data;
scanf("%d",&data);
if(data == -1)
{
T = NULL;
}
else
{
T = (BiThrTree)malloc(sizeof(BiThrNode));
T->data = data;
T->LTag = Link;
T->RTag = Link;
T->lchild = CreateBiTree(T->lchild,step);
T->rchild = CreateBiTree(T->rchild,step);
}
return T;
}
/**
指定头指针,连接二叉树,并将其线索化后,尾部连接到头指针
*/
BiThrTree InOrderThreading(BiThrTree pHead,BiThrTree T)
{
pHead = (BiThrTree)malloc(sizeof(BiThrNode));
pHead->LTag = Link;
pHead->RTag = Thread;
pHead->rchild = pHead;
if(!T)
{
pHead->lchild = pHead;
}
else
{
pHead->lchild = T;
pre = pHead;
InThreading(T);
pre->RTag = Thread;
pre->rchild = pHead;
pHead->rchild = pre;
}
return pHead;
}
/**
线索二叉树的遍历
*/
void InOrderTraverse(BiThrTree pHead)
{
BiThrTree T = pHead->lchild;
while(T != pHead)
{
while(T->LTag == Link)
T = T->lchild;
printf("%d ",T->data);
while(T->RTag == Thread && T->rchild != pHead)
{
T = T->rchild;
printf("%d ",T->data);
}
T = T->rchild;
}
}
int main()
{
BiThrTree T;
T = CreateBiTree(T,0);
BiThrTree pHead;
pHead = InOrderThreading(pHead,T);
InOrderTraverse(pHead);
return 0;
}
- 注意:这里的代码中二叉树中存放的是整型数据,所以A,B,C,D,E,F,G,H,I,J 我这里用1,2,3,4,5,6,7,8,9,10来代替,其实运行结果是一样的
8,4,9,2,10,5,1,6,3,7
就等同于:H,D,I,B,J,E,A,F,C,G