为什么要研究线索二叉树?
当用二叉链表作为二叉树的存储结构时,可以很方便地找到某个结点的左右孩子;但一般情况下,无法直接找到该结点在某种遍历序列中的前驱和后继结点。
之前学到的二叉树链表中空指针域的数量:具有n个结点的二叉链表中,共有n+1个指针域为空
线索二叉树基本概念
利用二叉链表中的空指针域:
如果某个结点的左孩子为空,则将空的左孩子指针域改为指向其前驱;如果某结点的右孩子为空,则将空的右孩子指针域改为指向其后继;
这种改变指向的指针称为线索。
加上线索的二叉树称为线索二叉树。
对二叉树按某种遍历次序使其变为线索二叉树的过程叫线索化。
为了区分lchild和rchild指针到底是指向左右孩子的指针还是指向前驱或者后继的指针,对二叉链表中每个结点增设两个标志域ltag和rtag,并约定:
结点形式为:
线索二叉树存储表示
typedef struct BiThrNode{
int data;
int ltag,rtag;//左右标志
struct BiThrNode *lchild,rchild;//左右孩子指针
}BiThrNode,*BiThrTree;
举例理解三种线索二叉树
红色虚线指向前驱和后继,蓝色实线指向左右孩子
1)先序线索二叉树:
先序序列为:A B C D E
图解为:
2)中序线索二叉树:
中序序列:B C A E D
图解是:
3)后续线索二叉树:
后序序列:C B E D A
图解为:
为什么要增设头结点?
下图为中序线索二叉树
中序序列为H D I B E A F C G
由上图可见,还剩余两个空指针。为了操作方便,所以避免悬空态,我们增设一个头结点:
对头结点的操作:
1)data域置为空,ltag=0,lchild指向根结点
2)rtag=1,rchild指向遍历序列中最后一个结点
3)遍历序列中第一个结点(无前驱)的lchild域和最后一个结点(无后继)的rchild域都指向头结点。
最后结果如图:
像循环链表一样,可以访问任意结点,操作比较方便。
遍历中序线索二叉树
先声明存储表示:
typedef struct BiThrNode{
int data;
int ltag,rtag;//左右标志
struct BiThrNode *lchild,rchild;//左右孩子指针
}BiThrNode,*BiThrTree;
【算法描述】
void InOrderTraverse_Thr(BiThrTree T)
{//T指向头结点,头结点的左链lchild指向根结点
//中序遍历二叉线索树T的非递归算法,对每个数据元素直接输出
BiThrNode *p=T->lchild; //p指向根结点
while(p!=T) //空树或遍历结束时,p==T
{
while(p->ltag==0)
p=p->lchild; //沿着左孩子向下
cout<<p->data; //访问其左子树为空的结点
while(p->rtag==1||p->rchild!=T)
{
p=p->rchild; //沿右线索访问后继结点
cout<<p->data;
}
p=p->rchild; //转向p的右子树
}
}
时间复杂度为O(n),空间复杂度为O(1),因为线索二叉树的遍历不需要栈来实现递归操作
内容参考:《数据结构》严蔚敏