线索化二叉树,顾名思义,跟“线索”二字有关。一般的,n各结点的二叉链表共有2n个链域,非空链域为n-1个,但其中的空链域却有n+1个,造成了存储空间的浪费,因此,我们设想利用原来的空链域存放指针,指向树中其他结点。这种指针称为线索。原来的二叉链表就变成了下图结构:
(一)结构体定义
class bintree{
/*
lefttag:0代表指向左孩子,1代表指向前驱
righttag:0代表指向右孩子,1代表指向后继
*/
public:
char data;
bintree *leftchild,*rightchild;
int lefttag,righttag;
};
建立线索也有一定的规则:(考虑中序遍历)
(1)如果pTree->lchild为空,则存放指向中序遍历序列中该结点的前驱结点。这个结点称为pTree的中序前驱;
(2)如果pTree->rchild为空,则存放指向中序遍历序列中该结点的后继结点。这个结点称为pTree的中序后继;
当然,二叉树的线索化是为了方便遍历的,因此要记得和双向链表结点一样,在二叉树链表上添加一 个头结点,如下图所示,并令其leftchild域的指针指向二叉树的根结点(【1】),其rightchild域的指针指向中序遍历访问时的最后一个结点(【2】)。反之,令二叉树的中序序列中第一个结点中,leftchild域指针和最后一个结点的rightchild域指针均指向头结点(【3】【4】)。这样我们既可以从第一个结点起顺后继进行遍历,也可以从最后一个结点起顺前驱进行遍历。
(二)完整测试代码
#include<iostream>
#include<cstdlib>
#include<string>
#include<cstdio>
using namespace std;
class bintree{
/*
lefttag:0代表指向左孩子,1代表指向前驱
righttag:0代表指向右孩子,1代表指向后继
*/
public:
char data;
bintree *leftchild,*rightchild;
int lefttag,righttag;
};
bintree *pre; //用来记录上一个节点,即当前节点的前驱,当前节点为pre的后继
bintree *find(bintree *root,char in_data){
bintree *t = root;
bintree *node;
if(t==NULL)return NULL;
if(t->data == in_data)return t;
else{
node = find(t->leftchild,in_data);
if(node) return node;
else return find(t->rightchild,in_data);
}
}
bintree *createtree(){
bintree *root=new bintree;
bintree *parent;
string in_string;
while(cin>>in_string){ //按照A(0,0)类似输入
if(in_string[2]-'0'==0){ //输入为根节点
root->data=in_string[0];
root->leftchild=root->rightchild=NULL;
root->lefttag=root->righttag=0;
continue;
}
parent=find(root,in_string[2]); //返回第一次出现的位置
if(in_string[4]-'0'==1){ //输入左孩子
bintree *newnode=new bintree;
newnode->data=in_string[0];
newnode->leftchild=newnode->rightchild=NULL;
newnode->lefttag=newnode->righttag=0;
parent->leftchild=newnode;
}else if(in_string[4]-'0'==2){ //输入右孩子
bintree *newnode=new bintree;
newnode->data=in_string[0];
newnode->leftchild=newnode->rightchild=NULL;
newnode->lefttag=newnode->righttag=0;
parent->rightchild=newnode;
}
if(getchar()=='\n')
break;
}
return root;
}
//编写中序线索化过程的函数,再添加头结点的
bintree *inthreading(bintree *root){
bintree *t=root;
if(t){
inthreading(t->leftchild); //线索化左子树
//!!
if(t->leftchild==NULL){ //左子树为空,则将其线索化,使其指向它的前驱
t->lefttag=1;
t->leftchild=pre;
}
//因为要线索化当前节点的后继不方便,所以用pre记录节点t的前一个节点
//可以确定t为pre的后继,那么,每次都是确定当前节点的前驱和该节点的上一个节点pre的后继
if(pre->rightchild==NULL){ //前一个节点的右子树为空则将其线索化,使其指向他的后继
pre->righttag=1;
pre->rightchild=t;
}
pre=t; //把当前节点p赋值给pre,以便下次利用
//!!!
inthreading(t->rightchild); //线索化右子树
}
return root;
}
//通过添加头结点的方式将其线索化
bintree *addheadthread(bintree *root,bintree *head){
bintree *t=root;
head->righttag=0;
head->rightchild=head;
if(t==NULL){
head->lefttag=0;
head->leftchild=head;
}else{
pre=head;
head->lefttag=0;
head->leftchild=t; //完成步骤【1】
inthreading(t); //在该过程中就已完成了线索化和图中所示的步骤3
pre->rightchild=head; //完成步骤【4】
pre->righttag=1;
head->rightchild=pre; //完成步骤【2】
}
}
//中序遍历
void inorder(bintree *head){
bintree *t=head->leftchild; //通过头结点进入根节点
while(t!=head){ //表示已遍历完成,指针t已经回到了头节点
while(t->lefttag==0) //循环找到中序遍历的第一个节点
t=t->leftchild;
cout<<t->data<<" ";
while(t->righttag==1&&t->rightchild!=head){ //不断地输出后继,直到某个节点rightchild指的不是后继,即righttag=1
t=t->rightchild;
cout<<t->data<<" ";
}
t=t->rightchild;//进入右子树
}
}
int main(){
bintree *root=createtree();
bintree *head=new bintree;
addheadthread(root,head);
inorder(head);
return 0;
}
测试代码输入:
输入为多个空格分隔开的A(X,y)
字母代表插入的节点名
X为当前该节点的父节点
y=0代表为根节点、1为左孩子、2为右孩子
如果对二叉树有疑问:二叉树的存储与基本操作实现