在前面的博文中,我们学习了二叉树的创建以及各种遍历操作,那么大家有没有想过一个问题?在一个二叉树中,若大量的节点都没有左右孩子,那么这就造成了内存浪费,因为我们申请了空间却没有很好的利用到他们。所以,这就带来了新的方式— 二叉树的线索化。
什么是线索二叉树呢?
线索二叉树即就是将没有孩子节点的指针指向它的前驱或者后继元素。例如:当前节点没有左孩子,那么它的左节点不能浪费哦,这时候我们可以把它指向它的前驱节点,而如果当前节点没有右孩子,我们就可以把当前右孩子指针域指向该节点的后继节点。
那么,我们怎么辨认一些元素到底是线索节点还是非线索节点呢?我们采用的方法是给每个节点添加两个元素,分别是左右线索标记。
若左线索标记值为0,则该节点为非线索节点,
若左线索标记值为1,则该节点为线索节点,
二叉树数据结构体组成:
- 左孩子节点
- 右孩子节点
- 左线索化标记
- 右线索化标记
即:
typedef struct Tree{
ElemType data;
struct Tree *lchild;
struct Tree *rchild;
int ltag;
int rtag;
}Tree;
以下为前序、中序线索化的代码,复制即可运行~
由于注释已经很详细了,所以这里不再过多说明,如果有疑问的小伙伴,可以留言交流讨论。
#include <iostream>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
using namespace std;
#define ElemType char
#define maxSize 100
typedef struct Tree{
ElemType data;
struct Tree *lchild;
struct Tree *rchild;
int ltag;
int rtag;
}Tree;
void createTree(Tree *&p);
void prePrint(Tree *p);
// 前序线索化
void preThread(Tree *p,Tree *&pre);
// 前序递归释放树节点空间
void destoryRoot(Tree *root);
// 中序线索化
void centerThread(Tree *p,Tree *&pre);
// 中序输出线索二叉树
void centerPrint(Tree *p);
// 获取最左节点
Tree * getLeft(Tree *p);
// 获取中序遍历的下一个元素
Tree * getNext(Tree *p);
int main(int argc, char** argv) {
Tree * root;
Tree *pre = NULL;
cout<<"请输入一组数据:";
// 创建二叉树
createTree(root);
cout<<"前序线索化后输出:";
// 前序线索化
preThread(root,pre);
pre->rchild = NULL;
pre->rtag = 0;
// 输出
prePrint(root);
cout<<endl;
destoryRoot(root);
root = NULL;
pre = NULL;
cout<<"请输入一组数据:";
// 清空缓存区
fflush(stdin);
// 创建二叉树
createTree(root);
// 前序线索化
centerThread(root,pre);
pre->rchild = NULL;
pre->rtag = 0;
cout<<endl;
cout<<"中线索化后输出:";
// 输出
centerPrint(root);
return 0;
}
// 中序线索化 二叉树
void centerThread(Tree *p,Tree *&pre){
if(p != NULL){
// 如果该节点未线索化
if(p->ltag == 0)
centerThread(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;
if(p->rtag == 0)
centerThread(p->rchild,pre);
}
}
void centerPrint(Tree *p){
Tree *s;
for(s = getLeft(p); s!=NULL; s=getNext(s)) {
cout<< s->data;
}
}
Tree * getNext(Tree *p){
if( p == NULL) return NULL;
// 如果当前节点的 右孩子为线索指针,则直接返回右孩子节点
if(p->rtag == 1){
return p->rchild;
}else{
// 否则获取右孩子节点的最左节点
return getLeft(p->rchild);
}
}
Tree * getLeft(Tree *p){
// 如果不是线索指针,则一直向左孩子寻找,直到指向线索指针
// 也就是说p->ltag=1时 也就是说该节点无左孩子节点,左孩子节点被线索化。
while(p != NULL && p->ltag == 0){
p = p->lchild;
}
return p;
}
// 前序递归释放树节点空间
void destoryRoot(Tree *root){
Tree *stack[maxSize];
int top = -1;
Tree *p;
stack[++top] = root;
while( top != -1){
p = stack[top--];
// 这里有个坑 由于已经是线索化后的二叉树,所以需要判断非线索节点才入栈
if(p->rchild != NULL && p->rtag == 0)
stack[++top] = p->rchild;
if(p->lchild != NULL && p->ltag == 0)
stack[++top] = p->lchild;
cout<<"已删除节点数据:"<<p->data<<endl;
delete p;
}
}
void createTree(Tree *&p){
ElemType a;
scanf("%c",&a);
if(a == ' '){
return;
}
p = new Tree();
p->data = a;
p->lchild = NULL;
p->rchild = NULL;
p->ltag = 0;
p->rtag = 0;
createTree(p->lchild);
createTree(p->rchild);
}
// 前序线索化
void preThread(Tree *p,Tree *&pre){
if(p != NULL){
// 如果当前节点的左孩子指针为空
if(p->lchild == NULL){
// 则将该节点的左孩子指针指向 前驱节点
p->lchild = pre;
// 前驱标记
p->ltag = 1;
}
// 如果前驱节点非空 并且 前驱节点的右孩子为空
if(pre != NULL && pre->rchild == NULL){
// 前驱节点指向当前节点
pre->rchild = p;
// 前驱标记
pre->rtag = 1;
}
// 调整前驱指针
pre = p;
// 如果该节点未线索化一定要判断 不然递归不会停止!
if(p->ltag == 0)
preThread(p->lchild,pre);
if(p->rtag == 0)
preThread(p->rchild,pre);
}
}
void prePrint(Tree *p){
if(p != NULL){
while( p != NULL){
// 如果当前节点不是线索节点,那么一直访问它的--->左孩子节点
while(p->ltag == 0){
cout<<p->data<<" ";
p = p->lchild;
}
// 上面节点访问完毕,说明该节点是线索过的节点
// 也就是说该节点没有左孩子,此时相当于访问了--->根节点
cout<<p->data<<" ";
// --->访问右孩子
p = p->rchild;
}
}
}