线索二叉树
线索二叉树的基本概念
二叉树的线索化是将二叉链表中的空指针改为指向前驱或后继的线索。而前驱或后继的信息只有在遍历时才能得到,因此线索化二叉树的实现就是遍历一次二叉树
以中序线索二叉树的建立为例:
- 附设指针pre指向刚刚访问过的结点,指针p指向正在访问的结点,即pre指向p的前驱。
- 在中序遍历的过程中,检查p的左指针是否为空,若为空就将它指向pre; 检查pre的右指针是否为空,若为空就将它指向p
中序线索二叉树的构造
InThread
通过中序遍历对二叉树线索化的递归算法(就是让结点的空指针指向其中序遍历的前驱或后继结点)
//通过中序遍历对二叉树线索化的递归算法(就是让结点的空指针指向其前驱或后继结点)
//中序线索二叉树的构造, 左根右
void InThread(ThreadTree &p,ThreadTree &pre){
if(p != NULL){
InThread(p->lchild,pre);//递归,线索化左子树
if(p->lchild == NULL){//左子树为空,建立前驱线索
p->lchild=pre;
p->ltag=1;
}
if(pre!=NULL && pre->rchild==NULL){
pre->rchild=p;//建立前驱结点的后继线索
pre->rtag=1;
}
pre=p;//标记当前结点成为刚刚访问过的结点
InThread(p->rchild, pre);//递归,线索化右子树
}//if(p != NULL)
}
CreateInThread
通过中序遍历建立中序线索二叉树的主过程算法如下:
这个函数的作用就是建立中序线索二叉树,调用InThread函数,并处理遍历的最后一个结点。
CreateInThread和InThread的具体关系:前者可以看做统领大局的工头,后者是具体搬砖的小工
//通过中序遍历建立中序线索二叉树的主过程算法如下:
void CreateInThread(ThreadTree &T){
ThreadTree pre=NULL;
if(T){ //非空二叉树,线索化
InThread(T,pre);//线索化二叉树
pre->rchild==NULL;//处理遍历的最后一个结点
pre->rtag=1;//@@@
}
}
带头结点的线索二叉树
如果不想让中序遍历的第一个结点的lchild 和最后一个结点的rchild 为NULL, 则必须要加头结点,不能将之指向树根,因为如下图所示,根的前驱结点和后继结点已经指向了根,如果再把中序遍历的第一个结点的lchild 和最后一个结点的rchild指向根节点,则遍历的时候无法保证结果的正确性。
总而言之即:
若让中序遍历的第一个结点的lchild 和最后一个结点的rchild 不为NULL,则必须额外添加一个Head头结点,不能简单指向根节点
中序线索二叉树的遍历
中序线索二叉树的结点中隐含了线索二叉树的前驱和后继信息。在对其进行遍历时,只要先找到序列中的第一个结点,然后依次找到结点的后继,直至其后继为空.
Firstnode
求中序线索二叉树中,中序序列下的第一个结点
//求中序线索二叉树中,中序序列下的第一个结点
ThreadNode *Firstnode(ThreadNode *p){
//printf("Firstnode:");
while(p->ltag==0){
p=p->lchild;//最左下结点(不一定是叶节点)
}
return p;
}
Nextnode
求中序线索二叉树中,结点p在中序序列下的后继
//求中序线索二叉树中,结点p在中序序列下的后继
ThreadNode *Nextnode(ThreadNode *p){
if(p->rtag==0){
return Firstnode(p->rchild);
}
else{
return p->rchild;//rtag==1 直接返回后继线索
}
}
Inorder
可以写出不含头结点的中序线索二叉树的中序遍历算法
//利用上面的两个算法,
//可以写出不含头结点的中序线索二叉树的中序遍历算法
void Inorder(ThreadNode *T){
for(ThreadNode *p=Firstnode(T);p!=NULL;p=Nextnode(p)){
visit(p);
}
}
完整测试代码
完整测试代码c++
注意:
- 还是要先创建二叉树,然后再对二叉树进行中序遍历线索化
- 在创建二叉树的时候要对ltag rtag进行初始化为0,表示lchild,rchild指向的是左/右孩子(而非前驱/后继),且初始化不能在结构体的定义中初始化,因为定义结构体时,并未给其分配内存,所以初值是无法存储的。应该声明结构体变量后,手工赋值。下面代码中是在malloc后就初始化
- 因为线索化二叉树后各个结点之间都穿成一个环了,所以在中序递归遍历和后序递归销毁的函数中需要对ltag rtag进行判断。
//线索二叉树
#include<stdio.h>
#include<stdlib.h>
#define ElemType char
//tag为0表示指向左/右孩子,为1表示指向结点的前驱/后继
typedef struct ThreadNode{
ElemType data;//数据元素
struct ThreadNode *lchild,*rchild;//左右孩子指针
int ltag;//因为定义结构体时,并未给其分配内存,所以初值是无法存储的。应该声明结构体变量后,手工赋值
int rtag;//左右线索标记
}ThreadNode,*ThreadTree;
void visit(ThreadTree T){
printf("%c ",T->data);
}
//中序线索二叉树的构造, 左根右
void InThread(ThreadTree &p,ThreadTree &pre){
if(p != NULL){
InThread(p->lchild,pre);//递归,线索化左子树
if(p->lchild == NULL){//左子树为空,建立前驱线索
p->lchild=pre;
p->ltag=1;
}
if(pre!=NULL && pre->rchild==NULL){
pre->rchild=p;//建立前驱结点的后继线索
pre->rtag=1;
}
pre=p;//标记当前结点成为刚刚访问过的结点
InThread(p->rchild, pre);//递归,线索化右子树
}//if(p != NULL)
}
//通过中序遍历建立中序线索二叉树的主过程算法如下:
void CreateInThread(ThreadTree &T){
//ThreadNode *Head = (ThreadNode*)malloc(sizeof(ThreadNode));
// Head->lchild=T;
//Head->ltag=1;
//ThreadTree pre=T;
ThreadTree pre=NULL;
if(T){ //非空二叉树,线索化
InThread(T,pre);//线索化二叉树
//Head->rchild=pre;
//Head->rtag=1;
pre->rchild==NULL;
//pre->rchild=T;//处理遍历的最后一个结点
pre->rtag=1;
}
}
//求中序线索二叉树中,中序序列下的第一个结点
ThreadNode *Firstnode(ThreadNode *p){
//printf("Firstnode:");
while(p->ltag==0){
p=p->lchild;//最左下结点(不一定是叶节点)
}
return p;
}
//求中序线索二叉树中,结点p在中序序列下的后继
ThreadNode *Nextnode(ThreadNode *p){
if(p->rtag==0){
return Firstnode(p->rchild);
}
else{
return p->rchild;//rtag==1 直接返回后继线索
}
}
//利用上面的两个算法,
//可以写出不含头结点的中序线索二叉树的中序遍历算法
void Inorder(ThreadNode *T){
for(ThreadNode *p=Firstnode(T);p!=NULL;p=Nextnode(p)){
visit(p);
}
}
//创建线索二叉树,按前序输入, #表示空节点
bool CreateThreadTree(ThreadTree &T){
ElemType ch;
scanf("%c", &ch);
if(ch == '#'){
//printf("您要创建一棵空树吗?\n");
T=NULL;
return false;
}
else{
T=(ThreadTree)malloc(sizeof(ThreadNode));
T->ltag=T->rtag=0;
if(!T){
printf("malloc failure\n");
return false;
}
T->data = ch;
CreateThreadTree(T->lchild);
CreateThreadTree(T->rchild);
return true;
}
}
bool DestroyThreadTree(ThreadTree T){
if(T==NULL){
printf("空节点\n");
return false;
}
if(T->ltag!=1)
DestroyThreadTree(T->lchild);
if(T->rtag!=1)
DestroyThreadTree(T->rchild);
printf("销毁%c\n",T->data);
free(T);//@@@'
T=NULL;
return true;
}
//中序遍历线索二叉树
void InOrder(ThreadTree T){
if(T){
if(T->ltag!=1)
InOrder(T->lchild);
visit(T);
if(T->rtag != 1)
InOrder(T->rchild);
}
}
int main(){
ThreadTree T=NULL;
printf("按前序输入二叉树中节点的值(输入#表示空节点)\n");
CreateThreadTree(T);//输入前序,建立二叉树
CreateInThread(T);//通过中序遍历建立中序线索二叉树
ThreadNode *p=Firstnode(T);//求中序遍历下的第一个结点
printf("\n中序遍历的第一个结点p: %c\n",p->data);
ThreadNode* r=Nextnode(p);//求中序遍历下p的后继
printf("p的后继r: %c\n",r->data);
printf("中序遍历线索二叉树(递归InOrder ≈ 正常BinaryTree遍历): \n");
InOrder(T);
printf("\n");
printf("\n中序遍历线索二叉树(非递归Inorder ≈ Firstnode+Nextnode): \n");
Inorder(T);
printf("\n用完要记得销毁哦!\n");
DestroyThreadTree(T);
return 0;
}
输入样例
测试结果