线索二叉树
如果二叉树需经常遍历或者查找节点时需要某种遍历序列中的前驱和后驱,那么采用线索二叉链表的存储结构就是非常不错的选择。
构造的是ABECDFG这样的完全二叉树。
输入的扩展二叉树式子是:ABC##D##EF##G##
代码
#include <iostream>
using namespace std;
#define TElemType char
//Link=0表示有左右孩子,Thread==1指向前后继线索
typedef enum{Link,Thread} PointerTag;
typedef struct BiThrNode//二叉树节点类
{
TElemType data;
BiThrNode *lchild,*rchild;
PointerTag Ltag;
PointerTag Rtag;
}BiThrNode, *BinThrTree;
class ThreadTree
{
public:
ThreadTree():root(NULL){head=new BiThrNode;} //创建空树,且头结点必须被创建,否者下面的对头节点的连接会出现段错误
//其中不管是前序还是中序和后续输入的扩展式子是一样的,只是遍历的顺序不一样打印的顺序不一样
//如果需要在函数内对实参指针地址变量进行操作必须以引用指针作为形参
void CreateBinTree_q(BiThrNode *¤t); //递归使用前序创建二叉树
void CreateBinTree_z(BiThrNode *¤t); //递归使用中序创建二叉树
void CreateBinTree_h(BiThrNode *¤t); //递归使用后续创建二叉树
void Head2Root(); //把头结点和根节点进行联系
void CreateInThread(); //创建中序线索,内部调用InThreading
void InOrderTraverse_Thr(); //中序遍历中序线索二叉树
BiThrNode *& returnroot(){return root;} //返回根节点
BiThrNode *& returnhead(){return head;} //返回头结点
private:
BiThrNode *root;
BiThrNode *head;
BiThrNode *pre; //在创建线索时用的一个全局变量,记录上一节点
void InThreading(BiThrNode *current); //中序线索建立的主程序,被CreateInThread调用
};
//这里的二叉树建立,会运用到扩展树的概念
//比如建立一棵AB#D##C##的树,就可以按照这个顺序输入
void ThreadTree::CreateBinTree_q(BiThrNode *¤t)
{
TElemType ch;
cin>>ch;
if(ch=='#')
current=NULL; //这些都给最后那些没有孩子的节点孩子指针赋值了NULL,下面可以在遍历中直接用来判断
else
{
current=new BiThrNode;
current->data=ch; //前序先进行数据位的赋值
cout<<current->data<<' ';
CreateBinTree_q(current->lchild);
CreateBinTree_q(current->rchild);
}
}
//先进入树的左子树,都是先对每一个子叶进行配置才会返回上层
void ThreadTree::CreateBinTree_z(BiThrNode *¤t)
{
TElemType ch;
cin>>ch;
if(ch=='#')
current=NULL;
else
{
current=new BiThrNode; //这个是必须先进行创建的,不创建就没法进行下一步递归
CreateBinTree_z(current->lchild);
current->data=ch;
cout<<current->data<<' ';
CreateBinTree_z(current->rchild);
}
}
void ThreadTree::CreateBinTree_h(BiThrNode *¤t)
{
TElemType ch;
cin>>ch;
if(ch=='#')
current=NULL;
else
{
current=new BiThrNode;
CreateBinTree_h(current->lchild);
CreateBinTree_h(current->rchild);
current->data=ch;
cout<<current->data<<' ';
}
}
//搭建头结点
void ThreadTree::Head2Root()
{
BiThrNode *p,*q;
head->lchild=root;
p=root;
q=root;
while(p->Ltag==Link)
p=p->lchild;
p->lchild=head;
while(q->Rtag==Link&&q->rchild!=NULL)
q=q->rchild;
q->rchild=head;
head->rchild=q;
head->Ltag=Link;
head->Rtag=Thread;
}
//中序遍历线索二叉树
void ThreadTree::CreateInThread()
{
BiThrNode *p=root;
//BiThrNode *pre=NULL;
InThreading(p);
}
void ThreadTree::InThreading(BiThrNode *current)
{
if(current)
{
InThreading(current->lchild);
//由于当前节点的左孩子是空的,那么这时候的会把前驱pre赋值给它的左孩子
//那这里的pre是理解难点,pre对应的节点必定是上层最近的已经完成左子树遍历
//进入右子树的那个节点
if(!current->lchild)
{
current->Ltag=Thread;
current->lchild=pre;
}
//由于前面完成了当前节点左子树的遍历,并且记录下了该节点左子树遍历完成的最后一个
//节点pre,那么这个pre的右子树必定是空的,且它的后继也必定是当前节点p,这是回到p
//节点再给前继节点进行操作的一个步骤
if(!pre->rchild)
{
pre->rchild=current;
pre->Rtag=Thread;
}
pre=current;
InThreading(current->rchild);
}
}
/*
*
*
*/
void ThreadTree::InOrderTraverse_Thr()
{
BiThrNode *p;
Head2Root(); //先进行头结点的搭建
p=head->lchild;
while(p!=head)
{
while(p->Ltag==Link)
p=p->lchild;
cout<<p->data<<' ';
while(p->Rtag==Thread&&p!=head)
{
p=p->rchild;
cout<<p->data<<' ';
}
p=p->rchild;
}
}
int main()
{
ThreadTree bitree;
// cout<<"通过前序创建一个二叉树,输入扩展二叉树公式:"<<endl;
// bitree.CreateBinTree_q(bitree.returnroot());
cout<<"通过中序创建一个二叉树,输入扩展二叉树公式:"<<endl;
bitree.CreateBinTree_z(bitree.returnroot());
//cout<<"通过后序创建一个二叉树,输入扩展二叉树公式:"<<endl;
// bitree.CreateBinTree_h(bitree.returnroot());
bitree.CreateInThread(); //创建中序线索二叉树
cout<<"遍历中序线索二叉树:"<<endl;
bitree.InOrderTraverse_Thr();
return 0;
}
小结
- 其中不管是前序、中序、后序,输入的扩展二叉树参数式都是一样的,只不过遍历的顺序不一样
- 二叉树的主要的操作就是遍历问题,在遍历方法中可以插入对各个节点的操作方法
- 线索二叉树都充分利用了各个节点的指针域,让整棵树都串联了起来,更像一个循环链表,因此它的遍历也是值得讨论的
- 这里的线索二叉树引入了一个头结点,可以实现头结点直接访问最左子叶和最右子叶