二叉树在连接存储表示中,空链的数目是大于非空链的数目,即在2n个空链中,有n+1个是空链,如果利用这些空链来指向二叉树其他结点的指针,这结点称为线索,具体建立线索树过程:
- 如果结点的左儿子ptr->left_child为空,则在中序遍历中,用指向在ptr之前访问的结点的指针代替ptr->left_child,即用指向ptr的中序遍历的前驱结点的指针代替空链。
- 如果结点的右儿子ptr->right_child为空,则在中序遍历中,用指向在ptr之后访问的结点的指针代替ptr->right_child,即用指向ptr的中序遍历的后驱结点的指针代替空链。
现在有一颗二叉树,其结构如图:
如果中序遍历这颗二叉树,访问结点的顺序是H,D,I,B,E,A,F,C,G,这里以结点E为例,来说明线索建立的过程吧:
由于结点E的左儿子是一个空链,所以,用指向在结点之前访问的结点(即B结点)的指针代替这个空链,类似地,由于结点E的右儿子也为空,所以,用在中序遍历中指向E后面结点(即结点A)的指针代替这个空链。
该二叉树转换为线索二叉树后,该结构如图:
为了能在内存表示线索二叉树,我们需要增加两个附加域:left_thread和right_thread来区分指针是线索指针还是正常指针,假设ptr是任意一个结点,如果ptr->left_thread=TRUE,那么ptr->left_child是一个线索,否则它就是指向其左儿子的正常指针,right_threadl也类似。构造线索树还加一个额外的头结点,其头结点结构为:
现在假设已经构建好了上面的中序线索二叉树,那么我们就可以使用该线索二叉树实现中序遍历,其思路:
对每一个结点ptr来说,如果ptr->right_thread=TRUE,那么根据线索定义,结点ptr的中序后继结点,是ptr->right_child,否则,结点ptr的中序后继结点是从其右儿子开始,沿着左儿子链到达left->thread=TRUE的结点。整个算法时间 复杂度还是O(n)。
代码实现:
typedef struct thread_tree *thread_pointer;
//线索二叉树结构体
struct thread_tree
{
short left_thread,right_thread;
int data;
thread_pointer left_child,right_child;
};
//寻找该结点的后驱结点
thread_pointer insucc(thread_pointer tree)
{
thread_pointer temp=tree->right_child;
if(!tree->right_thread)// 若果不是线索,则沿左儿子查找
while(!temp->left_thread)
temp=temp->left_child;
return temp;
}
//中序线索树遍历输出
void tinorder(thread_pointer tree)
{
for(;;)
{
thread_pointer temp=insucc(tree);
if(temp==tree) break;
printf("%d",temp->data);
}
}
向上面线索二叉树中插入结点,假设我们只考虑插入一个新结点作为结点parent的右儿子情况,那么我们就有以下两种情况要考虑:
若parent的右儿子为空,则插入新结点后,线索二叉树变化如下图:
若parent的右儿子存在右子树,则插入新结点后,线索二叉树变化如下图:
代码实现:
//在线索二叉树中,向父结点插入新的右儿子
void insert_right(thread_pointer parent,thread_pointer child)
{
thread_pointer temp;
child->right_child=parent->right_child;
child->right_thread=parent->right_thread;
child->left_child=parent;
child->left_thread=TRUE;
parent->right_child=child;
parent->right_thread=child->right_thread;
//当时第二种情况时,需查找新插入的结点的后驱结点,
//并改变其前驱结点为该新的儿子结点
if(!child->right_thread)
{
temp=insucc(child);
temp->left_child=child;
}
}