搜索二叉树是对于普通二叉树空间浪费的空间的利用,树的遍历方式前序遍历,中序遍历后序遍历,层序遍历,只有进行中序遍历时,造成空间浪费的节点是有规律的因此搜索二叉树采取的是利用中序遍历来线索化二叉树达到有效利用空间来提高搜索二叉树的性能。
为什么只有中序遍历可以有效利用空间提高二叉树的效率,大家试一下写出来就知道了,我这里给大家演示一下中序遍历:
中序遍历结果:4,2,5,1,6,7,3
4是2的前驱,2是4的后继,同理5是2的后继,2 是5的前驱等等。这样可以任意节点快速搜索提高性能。当然最开始还是要从根节点开始进入搜索函数。
思路:首先初始化二叉树,与普通二叉树不同的是需要两个变量来记录该节点孩子的状态(有无左孩子或者右孩子)这里我定义枚举类型typedef enum{link,Thread},link代表该节点有孩子,Thread代表该节点没有孩子因此指向该孩子的指针可以作为线索。(初始化ltarg,rtarg。此时该二叉树所有节点的ltag,rtag我都设置为link,这样方便我们后续对于二叉树ltag,rtag进行修改)
根据中序遍历来创建搜索二叉树
使用中序遍历的方法来进行搜索二叉树的遍历:
因为是中序遍历根据中序遍历的顺序来讲,首先要对左子树进行线索化后对根节点进行线索化,最后对右子树线索化。构造中序遍历函数,参数传递树的根节点。首先使用递归调用该函数函数传递传递当前节点的左孩子位置(使用该递归的方式可以直接找到左子叶的位置)进行左子树的线索化。根节点不需要线索化因为左右孩子都存在不能线索化,然后再对右子树进行线索化。线索化详解:如果当前节点的左孩子为空那么此时设置该节点的ltag为线索,并将左孩子指针指向前驱节点,注意此时,前驱节点需要被全局变量保存否则无法得到前驱变量我在代码中设置全局变量pre来保存。例如中序遍历顺序是ABCD当B为当前节点,B的前驱就是A,A先于B获取,pre保存A的值可以将B的前驱设置为A。同样的后继此时注意后继指的是A的后继是B,现在要做的是将A的后继设置成B当然此时要判断A是否有右孩子,右孩子不存在才可以作为线索指向A的后继B。最后需要改变pre的值从A变成B。此时该节点的线索化操作结束。pre是动态改变的,所以需要一个初始值,因此我们需要找到pre的初始值,找到之后再调用刚才构造的中序遍历函数。此时整个代码结束。接下来我们看看完整代码:
#include <stdio.h>
#include <stdlib.h>
typedef char ElemType;
// 线索存储标志位
// Link(0):表示指向左右孩子的指针
// Thread(1):表示指向前驱后继的线索
typedef enum {Link, Thread} PointerTag;
typedef struct BiThrNode
{
char data;
struct BiThrNode *lchild, *rchild;
PointerTag ltag;
PointerTag rtag;
} BiThrNode, *BiThrTree;
// 全局变量,始终指向刚刚访问过的结点
BiThrTree pre;
// 创建一棵二叉树,约定用户遵照
前序遍历的方式输入数据
void CreateBiThrTree( BiThrTree *T )
{
char c;
scanf("%c", &c);
if( ' ' == c )
{
*T = NULL;
}
else
{
*T = (BiThrNode *)malloc(sizeof(BiThrNode));
(*T)->data = c;
(*T)->ltag = Link;
(*T)->rtag = Link;
CreateBiThrTree(&(*T)->lchild);
CreateBiThrTree(&(*T)->rchild);
}
}
// 中序遍历线索化
void InThreading(BiThrTree T)
{
if( T )
{
InThreading( T->lchild ); // 递归左孩子线索化
if( !T->lchild ) /*如果该结点没有左孩子,
设置ltag为Thread,并把lchild指向刚刚访问的结点。*/
{
T->ltag = Thread;
T->lchild = pre;
}
if( !pre->rchild )
{
pre->rtag = Thread;
pre->rchild = T;
}
pre = T;
InThreading( T->rchild ); // 递归右孩子线索化
}
}
//这里是重要部分,设置pre初始值调用inthreading函数
void InOrderThreading( BiThrTree *p, BiThrTree T )
{
//设置了一个头指针p指向头节点
*p = (BiThrTree)malloc(sizeof(BiThrNode));
(*p)->ltag = Link;
(*p)->rtag = Thread;
(*p)->rchild = *p;
if( !T ) /*当树为空,pre的左孩子指向他自己,这里我设置T(根节点)是p指向
节点的左孩子,也可以设置为右孩子但是下面代码就要稍微做一下调整*/
{
(*p)->lchild = *p;
}
else
{
(*p)->lchild = T;
pre = *p;//pre有了初始值*p
InThreading(T);
/*以下三部是为了形成一个闭合回路。
pre最后指向的是最右边的右子叶(树里面的最大值)*/
pre->rchild = *p;
pre->rtag = Thread;
(*p)->rchild = pre;
}
}
void visit( char c )
{
printf("%c", c);
}
// 中序遍历二叉树,非递归
void InOrderTraverse( BiThrTree T )
{
BiThrTree p;
p = T->lchild;
while( p != T )
{
while( p->ltag == Link )
{
p = p->lchild;
}
visit(p->data);
while( p->rtag == Thread && p->rchild != T )
{
p = p->rchild;
visit(p->data);
}
p = p->rchild;
}
}
int main()
{
BiThrTree P, T = NULL;
CreateBiThrTree( &T );
InOrderThreading( &P, T );
printf("中序遍历输出结果为: ");
InOrderTraverse( P );
printf("\n");
return 0;
}
附上树的图以便于大家参照:
红色是后继,深黑是前驱。图片来源于小甲鱼的数据结构与算法的线索二叉树章节。