线索二叉树就是给定头结点,可以用p=p->next,将整个二叉树访问完毕的一种二叉树,就像链表一样,给定条件while(p!=NULL) p=p->next;
那么怎么实现?
此时,二叉树节点的定义也不一样了。下面用两种方法。
线索二叉树的优点:遍历过程会变得简单,既不需要递归,也不需要栈。
第一种——采用标志ltag和rtag
enum PointerTag {Link=0,Thread=1};//Link=0,Thread=1;PointerTag=Link表示指向左右孩子指针
//PointerTag=Thread表示指向前驱或者后继的线索
typedef struct node{
ElemTYPE data;
struct node *lchild,*rchild;
PointerTag ltag;
PointerTag rtag;
}BiTreeNode,*BiTree;
其中的ltag和rtag都是在访问中需要用到的标志,究竟是读取还是访问下一个。
在建立二叉树时,所有节点的ltag和rtag都初始化为Link。
其中枚举类型PointerTag需要强调一下,当节点的ltag=Link时,表示它的左子节点是非空的;当节点的ltag=Thread时,表示它的左子节点为空,那么这个左子节点的右孩子为它的父节点。当节点的rtag=Link时,表示它的右子节点非空;当节点的rtag=Thread时,表示节点的右子节点为空,那么该右子节点的左孩子指向该右子节点的父节点。
一、首先给二叉树建立头结点,设置好头结点的左子节点、右子节点、ltag、rtag。
初始化时,头结点的左子节点指向树的根节点,右子节点指向其本身,ltag=Link,rtag=Link。
二、设置好之后,再对树的叶子节点的lchild、rchild、ltag、rtag进行设置。
只要是叶子节点,其ltag和rtag都为Thread,如果没有左子节点,那么该节点肯定有lchild要设置,该节点的左子节点为其父节点或者父节点的父节点或者父节点的父节点的父节点……,且ltag=Thread;如果没有右子节点,那么该节点的rchild肯定要重新设置,该节点的rchild为其父节点或者父节点的父节点或者……且rtag=Thread。
三、最后根据每个节点的ltag和rtag来对节点进行读取。
具体的程序如下:
#include
using namespace std;
typedef int ElemTYPE;
#define N 8
enum PointerTag {Link=0,Thread=1};//Link=0,Thread=1;PointerTag=Link表示指向左右孩子指针
//PointerTag=Thread表示指向前驱或者后继的线索
typedef struct node{
ElemTYPE data;
struct node *lchild,*rchild;
PointerTag ltag;
PointerTag rtag;
}BiTreeNode,*BiTree;
BiTreeNode *pre;//始终指向当前节点
BiTreeNode * CreatTreeNode(ElemTYPE x)
{
BiTreeNode *p;
p=new BiTreeNode;
p->data = x;
p->lchild = NULL;
p->ltag = Link;
p->rchild = NULL;
p->rtag = Link;
return p;
}
BiTreeNode * CreatTree(ElemTYPE p[])
{
BiTreeNode *q[N];
int i;
for(i=0;iq[i]=CreatTreeNode(p[i]);
for(i=0;i2;i++)
{
if((2*i+1)q[i]->lchild=q[2*i+1];
if((2*i+2)q[i]->rchild=q[2*i+2];
}
return q[0];
}
ElemTYPE visit(BiTreeNode *p)
{
return p->data;
}
void InorderTraverse(BiTreeNode *L)
{
if(L!=NULL)
{
InorderTraverse(L->lchild);
cout<" ";
InorderTraverse(L->rchild);
}
}
void InorderTraverse_Threading(BiTreeNode *p)
{
BiTreeNode *m;
m=p->lchild;//m指向根节点
while(m!=p)
{
while(m->ltag==Link)
m=m->lchild;
cout<<m->data<<" ";
while((m->rtag==Thread)&&(m->rchild!=p))
{
m=m->rchild;
cout<<m->data<<" ";
}
m=m->rchild;
}
}
//中序遍历进行中序线索化
void InThreading(BiTreeNode *p)
{
if(p)//如果根节点非空
{
InThreading(p->lchild);//递归左子树线索化
if(p->lchild==NULL)//如果当前节点没有左子树,那么设置该节点的前驱
{ //且该节点的ltag一定为Thread
p->ltag=Thread;//前驱线索
p->lchild=pre;//左孩子指向前驱
}
if(pre->rchild==NULL)//如果节点没有右子树,那么设置该节点的右子树
//且该节点的右tag一定为Thread
{
pre->rchild=p;
pre->rtag=Thread;
}
pre=p;
InThreading(p->rchild);
}
}
//建立头结点,并对树进行中序线索化
int Head_InThreading(BiTreeNode *&head,BiTreeNode *p)
{
head = new BiTreeNode;
if(head==NULL)
return 0;
head->rchild=head;
head->rtag=Link;
if(p==NULL)//如果p为空
{
head->lchild=head;
head->ltag=Link;
}
else
{
pre=head;
//第一步:头结点的左孩子为根节点,即表明头结点的左孩子和ltag
head->lchild=p;
head->ltag=Link;//表示head的左子孩子为p,所有的非叶子节点的ltag和rtag都为link
//第二步:中序遍历找到最后一个节点
InThreading(p); //找到最后一个节点
//第三步:建立头结点与最后一个节点的联系,标明最后一个节点的右孩子和rtag
pre->rchild=head;//
pre->rtag=Thread;//表示可以通过线索化而找到pre的下一个节点
//第四步:标明头结点的右孩子和rtag
head->rchild=pre;//表示头结点的右子节点
}
return 1;
}
void main()
{
BiTreeNode *input,*temp;
ElemTYPE array[N];
int i;
for(i=0;i1;
input=CreatTree(array);
InorderTraverse(input);
cout<
第二种——采用双向循环链表的方法——中序线索链表
采用循环链表要比采用标记法简单,更容易理解。因为在中间不需要更改lchild和rchild,只需要更改前驱和后继,简单了很多。
代码如下:
//给定一个数组,然后将其创建为一个二叉树
#include
using namespace std;
typedef int ElemTYPE;
#define N 8
typedef struct node{
ElemTYPE data;
struct node *lchild,*rchild;
struct node *pred,*succ;//前驱和后继
}BiTreeNode,*BiTree;
BiTreeNode *pre;//一直都是指向当前节点
BiTreeNode * CreatTreeNode(ElemTYPE x)
{
BiTreeNode *p;
p=new BiTreeNode;
p->data=x;
p->lchild=NULL;
p->rchild=NULL;
return p;
}
BiTreeNode * CreatTree(ElemTYPE p[])
{
BiTreeNode *q[N];
int i;
for(i=0;iq[i]=CreatTreeNode(p[i]);
for(i=0;i2;i++)
{
if((2*i+1)q[i]->lchild=q[2*i+1];
if((2*i+2)q[i]->rchild=q[2*i+2];
}
return q[0];
}
ElemTYPE visit(BiTreeNode *p)
{
return p->data;
}
void InorderTraverse(BiTreeNode *L)
{
if(L!=NULL)
{
InorderTraverse(L->lchild);
cout<" ";
InorderTraverse(L->rchild);
}
}
int InThreadTraverse(BiTreeNode *p)
{
BiTreeNode *m;
if(p==NULL)
return 0;
m=p->succ;
while(m!=p)
{
cout<m)<<" ";
m=m->succ;
}//while
return 1;
}
//中序遍历
void InThread(BiTreeNode *p)
{
if(p)
{
InThread(p->lchild);
p->pred=pre;
pre->succ=p;
pre=p;
InThread(p->rchild);
}
}
void InorderThreading(BiTreeNode *&h,BiTreeNode *p)
{
h=new BiTreeNode;
h->lchild=p;h->rchild=NULL;//头结点的左子节点为根节点,
//右子节点为空,这个都无所谓,
//任意指定一个就行
if(p==NULL)
{
h->pred=h;h->succ=h;//如果所给的树是空的,那么头结点的前驱
//和后继都指向自己
}
else
{
pre=h;//当前节点为头结点
InThread(p);//找到最后的节点
pre->succ=h;
h->pred=pre;
}
}
void main()
{
BiTreeNode *input,*head;
ElemTYPE array[N];
int i;
for(i=0;i1;
input=CreatTree(array);
InorderTraverse(input);
cout<
第二种——采用双向循环链表的方法——前序线索链表
思路跟前面差不多,这里我就不贴整个代码了,只贴中间部分:
//对双向循环链表——前序线索链表进行遍历
int PreThreadTraverse(BiTreeNode *p)//此时的p为头结点
{
BiTreeNode *m;
if(p==NULL)
return 0;
m=p->succ;
while(m!=p)
{
cout<<visit(m)<<" ";
m=m->succ;
}//while
return 1;
}
**//前序遍历建立各节点的前驱和后继**
void PreThread(BiTreeNode *p) //p为根节点,pre为头结点
{
if(p)
{
p->pred=pre;
pre->succ=p;
pre=p;
PreThread(p->lchild);
PreThread(p->rchild);
}
}
**//建立头结点,并建立其他个节点的前驱和后继**
void PreorderThreading(BiTreeNode *&h,BiTreeNode *p)
{
h=new BiTreeNode;
h->lchild=p;h->rchild=NULL;//头结点的左子节点为根节点,
//右子节点为空,这个都无所谓,
//任意指定一个就行
if(p==NULL)
{
h->pred=h;h->succ=h;//如果所给的树是空的,那么头结点的前驱
//和后继都指向自己
}
else
{
pre=h;//当前节点为头结点
PreThread(p);//此时p为根节点,找到最后的节点
pre->succ=h;
h->pred=pre;
}
}
结果:
1 2 4 8 5 3 6 7
1 2 4 8 5 3 6 7
Press any key to continue
第二种——采用双向循环链表的方法——后序线索链表
//对双向循环链表——后序线索链表进行遍历
int PostThreadTraverse(BiTreeNode *p)//此时的p为头结点
{
BiTreeNode *m;
if(p==NULL)
return 0;
m=p->succ;
while(m!=p)
{
cout<<visit(m)<<" ";
m=m->succ;
}//while
return 1;
}
//后序遍历建立各节点的前驱和后继
void PostThread(BiTreeNode *p) //p为根节点,pre为头结点
{
if(p)
{
PostThread(p->lchild);
PostThread(p->rchild);
p->pred=pre;
pre->succ=p;
pre=p;
}
}
//建立头结点,并建立其他个节点的前驱和后继
void PostorderThreading(BiTreeNode *&h,BiTreeNode *p)
{
h=new BiTreeNode;
h->lchild=p;h->rchild=NULL;//头结点的左子节点为根节点,
//右子节点为空,这个都无所谓,
//任意指定一个就行
if(p==NULL)
{
h->pred=h;h->succ=h;//如果所给的树是空的,那么头结点的前驱
//和后继都指向自己
}
else
{
pre=h;//当前节点为头结点
PostThread(p);//此时p为根节点,找到最后的节点
pre->succ=h;
h->pred=pre;
}
}