考点:
(1)线索化
(2)找前驱,找后继
1、为什么引入线索二叉树这种结构?
对于普通二叉树来说,遍历二叉树是以一定的规则将二叉树中的结点排列成一个线性序列,从而得到几种遍历序列,使得该序列中的每个结点(除第一个和最后一个结点)都有一个直接前驱和直接后继。
缺点:传统的二叉链表存储仅能体现一种父子关系,不能直接得到结点在遍历中的前驱结点或后继结点。
这也就是要引入线索二叉树的原因:加快查找结点前驱和后继的速度!
2、线索化本质是什么?
二叉树的线索化是将二叉链表中的空指针改为指向该结点的前驱或后继的线索。
规定:若无左子树,令lchild指向其前驱结点,若无右子树,令rchild指向其后继结点。还需增加两个标志域标识指针域指向左(右)孩子还是左(右)孩子还是指向前驱(后继)。1代表指向线索,0代表指向孩子。
而前驱或后继的信息只有在遍历时才能得到,因此线索化的本质就是遍历一次二叉树的同时线索化二叉树。
3、中序线索二叉树的构造
3.1 线索二叉树的存储结构描述如下:
typedef struct ThreadNode{
ElemType data; //数据元素
struct ThreadNode *lchild, *rchild; //左右孩子指针
int ltag,rtag; //左右线索标志
}ThreadNode,*ThreadTree;
3.2 主代码
/*
* 线索二叉树相对于普通二叉树就是多了一个线索化的过程!
* 这个过程什么时候执行呢?
* 就是在二叉树遍历的时候执行的
*/
//全局变量pre,指向当前结点的前驱结点
ThreadNode *pre=NULL;
//中序线索化二叉树
void CreateInThread(ThreadTree T){
pre=NULL;
if(T!=NULL){
InThread(T);
pre->rchild=NULL; //处理遍历最后一个结点
pre->rtag=1;
}
}
//中序遍历二叉树,一边遍历,一边线索化
void InThread(ThreadTree T){
if(T!=NULL){
InThread(T->ichild);
visit(T);
InThread(T->rchild);
}
}
//遍历的时候线索化
void visit(ThreadNode *q){
if(q->lchild==NULL){ //左子树为空,建立前驱线索
q->lchild=pre;
q->ltag=1;
}
if(pre!=NULL&&pre->rchild==NULL){ //前驱结点右子树为空,建立后继结点
pre->rchild=q;
pre->rtag=1;
}
pre=q;
}
4、前序线索二叉树的构造
ThreadNode *pre=NULL;
voidCreatePreThread(ThreadTree T){
if(T!=NULL){
PreThread(T);
pre->rchild==NULL;
pre->rtag=1;
}
}
void PreThread(ThreadTree T){
if(T!=NULL){
visit(T)
/*前序线索二叉树需要判断lchild是否是前驱线索,如果是前驱线索的话,
因为前面执行visit()方法,所以T->lchild肯定是非空的,那么就会造成无限循环 */
if(T->ltag==0)
PreThread(T->lchilde);
PreThread(T->rchilde);
}
}
void visit(ThreadNode *q){
if(q->lchild==NULL){
q->lchild=pre;
q->ltag=1;
}
if(pre!=NULL&&pre->rchild==NULL){
pre->rchild=q;
pre->rtag=1;
}
pre=q;
}
5、先序线索二叉树找先序前驱和后继
5.1 找前驱
由先序序列为根左右,所以很明显发现,如果p是根结点,是不可能找到根结点的前驱的。
解决方法:
(1)用土方法从头开始遍历,但这很明显不是明智的选择
(2)改用三叉链表可以找到父节点
5.2 找后继
如果p的标志域标识指针域是1的话直接next=p->rchild即可,为0的话看下面:
由先序序列为根左右,如果p是根结点,那么需要分是否有左孩子两种情况:
(1)假设有左孩子,那么p的后继就是p的左孩子,但是!如果其左孩子还有左右孩子,也就是(根(根左右)右),那么很明显,p的后继结点就是p的左孩子。
(2)假设没有左孩子,那么p的后继结点就是p的右孩子,但是!如果其右孩子还有左右孩子,也就是(根(根左右)),那么很明显,p的后继结点就是p的右孩子。
6、中序线索二叉树找先序前驱和后继
6.1 找前驱
由中序序列为左根右,所以很明显发现,如果p是根结点,那么p的前驱就是p的左孩子,但是!如果其左孩子还有左右孩子,也就是((左根右)根右),那么很明显,p的前驱结点就是左孩子的最右孩子。
6.2 找后继
由中序序列为左根右,所以很明显发现,如果p是根结点,那么p的后继就是p的右孩子,但是!如果其右孩子还有左右孩子,也就是(左根(左根右)),那么很明显,p的后继结点就是右结点的最左孩子。
6、后序线索二叉树找先序前驱和后继
6.1 找前驱
由后序序列为左右根,如果p是根结点,那么需要分是否有右孩子两种情况:
(1)假设有右孩子,那么p的前驱就是p的右孩子,但是!如果其右孩子还有左右孩子,也就是(左(左右根)根),那么很明显,p的前驱结点就是右孩子的根结点,即p的右孩子。
(2)假设没有右孩子,那么p的前驱就是p的左孩子,但是!如果其左孩子还有左右孩子,也就是((左右根)根),那么很明显,p的前驱结点就是左孩子的根结点,即p的左孩子。
6.2 找后继
由先序序列为左右根,所以很明显发现,如果p是根结点,是不可能找到根结点的后继的。
解决方法:
(1)用土方法从头开始遍历,但这很明显不是明智的选择
(2)改用三叉链表可以找到父节点
7、线索二叉树的前驱和后继总结
先序线索二叉树 | 中序线索二叉树 | 后序线索二叉树 | |
找前驱 | ✖ | √ | √ |
找后继 | √ | √ | ✖ |