1、线索二叉树的意义
n个节点的二叉树链表总含有n+1(公式2n-(n-1)=n+1)个空指针域。利用二叉树表中的空指针域,存放指向该节点在某种遍历次序下的前驱和后继节点的指针(这种附加的指针称为“线索”)。相对于二叉树的好处是,有了线索二叉树可以直接得到一个结点的前驱节点或者后继结点的信息,而且使 n + 1 个空链域有了充分的使用,以不至于浪费!但是若直接在二叉树链表中设立 两个指向前驱或者后继的指针,那么这样会降低二叉链表的存储密度!所以采取将n + 1个空链域得到充分的使用才是正确的选择!
2、线索二叉树的存储结构定义
做如下规定:若结点有左子树,则其左指针 lchild指向其左孩子,否则令lchild指向其前驱;若结点有右孩子,则其右指针rchild指向其右孩子,否则令rchild指向其后驱!
所以线索二叉树的存储结构定义如下:
typedef struct BiThrNode{ //结点
char data; //数据域
struct BiThrNode *lchild,*rchild; //指针域
int Ltag,Rtag; //左右标志
}BiThrNode,*BiThrTree;
其中,当 Ltag、Rtag为0的时候,说明lchild和rchild分别是指向左右孩子的,但是当Ltag、Rtag为1的时候,说明lchild指向结点的前驱,而rchird指向的是节点的后继!
3、中序遍历线索化
******、算法步骤
(1)、如果p非空,左子树递归线索化
(2)、如果p的左孩子为空,则给p加上左线索,将其LTag置为1,让q的左孩子指针指向pre(前驱);否则将LTag置为 0;
(3)、如果pre的右孩子为空,则给pre加上右线索化,将其RTag置为1,让pre的右孩子指针指向p(后继);否则将pre的RTag置为0;
(4)、将pre指向刚才访问过的结点p,即pre = p;
(5)、右子树递归线索化
******、核心算法
//中序遍历二叉树,,一边遍历,一边线索化
void Inthread(BiThrTree &q,BiThrTree &pre){
if (q != NULL){
Inthread(q->lchild,pre);
cout<<q->data<<" ";
if (q->lchild == NULL){ //左子树为空,建立前驱线索
q->lchild = pre;
q->Ltag = 1;
}
else q->Ltag = 0;
if ( pre != NULL && pre->rchild == NULL) //若Pre指针不为空,且右孩子为空,则将其线索化
{
pre->rchild = q; //建立前驱结点的后继线索
pre->Rtag = 1;
}
else q->Rtag =0;
pre = q; //保持pre指向q的前驱
Inthread(q->rchild,pre);
}
}
//中序线索化一棵二叉树
void CreateInThrTree(BiThrTree &T){
BiThrNode *pre = NULL; //pre初始为NULL
if(T!=NULL){ //只有非空放入树才能进行线索化
Inthread(T,pre); //中序线索化二叉树
if(pre->rchild = NULL) //处理遍历的最后一个结点
pre->Rtag = 1;
}
}
******、算法实现
#include<string.h>
#include<stdio.h>
#define MaxSize 20
#include<iostream>
#include<stdlib.h>
#define endl '\n'
using namespace std;
typedef struct BiThrNode{ //结点
char data; //数据域
struct BiThrNode *lchild,*rchild; //指针域
int Ltag,Rtag; //左右标志
}BiThrNode,*BiThrTree;
//中序遍历二叉树,,一边遍历,一边线索化
void Inthread(BiThrTree &q,BiThrTree &pre){
if (q != NULL){
Inthread(q->lchild,pre);
cout<<q->data<<" ";
if (q->lchild == NULL){ //左子树为空,建立前驱线索
q->lchild = pre;
q->Ltag = 1;
}
else q->Ltag = 0;
if ( pre != NULL && pre->rchild == NULL) //若Pre指针不为空,且右孩子为空,则将其线索化
{
pre->rchild = q; //建立前驱结点的后继线索
pre->Rtag = 1;
}
else q->Rtag =0;
pre = q; //保持pre指向q的前驱
Inthread(q->rchild,pre);
}
}
//中序线索化一棵二叉树
void CreateInThrTree(BiThrTree &T){
BiThrNode *pre = NULL; //pre初始为NULL
if(T!=NULL){ //只有非空放入树才能进行线索化
Inthread(T,pre); //中序线索化二叉树
if(pre->rchild = NULL) //处理遍历的最后一个结点
pre->Rtag = 1;
}
}
//先序遍历的顺序建立二叉链表
void CreateTree(BiThrTree &T){
char ch;
cin>>ch;
if (ch == '#') T = NULL; //递归结束,建立空树
else{
T = new BiThrNode; //申请一个结点
T->data = ch; //将输入值赋值给T
CreateTree(T->lchild); //递归创建左子树
CreateTree(T->rchild); //递归创建右子树
}
}
main(){
BiThrTree T;
cout<<"\n请输入字符!(若输入的是#代表建立的是一棵空树):";
CreateTree(T); //ABC##DE##G##F###
cout<<"\n中序线索化输出二叉链表:"; //A B C # # D E # # G # # F # # #
CreateInThrTree(T);
}
******、算法分析
可以看到上面的中序遍历线索化其实就是从中序遍历的基础上而来,只是一边中序遍历,一边线索化而已!
4、先序遍历线索化
******、核心算法
//先序遍历二叉树,,一边遍历,一边线索化
//先序遍历二叉树,,一边遍历,一边线索化
void Prethread(BiThrTree &q,BiThrTree &pre){
if (q != NULL){
cout<<q->data<<" ";
if (q->lchild == NULL){ //左子树为空,建立前驱线索
q->lchild = pre;
q->Ltag = 1;
}
else q->Ltag = 0;
if ( pre != NULL && pre->rchild == NULL) //若Pre指针不为空,且右孩子为空,则将其线索化
{
pre->rchild = q; //建立前驱结点的后继线索
pre->Rtag = 1;
}
else q->Rtag =0;
pre = q; //保持pre指向q的前驱
if(q->Ltag == 0) //防止出现转圈,lchild不是前驱线索
Prethread(q->lchild,pre);
Prethread(q->rchild,pre);
}
}
//先序线索化一棵二叉树
void CreatePreThrTree(BiThrTree &T){
BiThrNode *pre = NULL; //pre初始为NULL
if(T!=NULL){ //只有非空放入树才能进行线索化
Prethread(T,pre); //中序线索化二叉树
if(pre->rchild = NULL) //处理遍历的最后一个结点
pre->Rtag = 1;
}
}
******、算法实现
#include<string.h>
#include<stdio.h>
#define MaxSize 20
#include<iostream>
#include<stdlib.h>
#define endl '\n'
using namespace std;
typedef struct BiThrNode{ //结点
char data; //数据域
struct BiThrNode *lchild,*rchild; //指针域
int Ltag,Rtag; //左右标志
}BiThrNode,*BiThrTree;
//先序遍历二叉树,,一边遍历,一边线索化
void Prethread(BiThrTree &q,BiThrTree &pre){
if (q != NULL){
cout<<q->data<<" ";
if (q->lchild == NULL){ //左子树为空,建立前驱线索
q->lchild = pre;
q->Ltag = 1;
}
else q->Ltag = 0;
if ( pre != NULL && pre->rchild == NULL) //若Pre指针不为空,且右孩子为空,则将其线索化
{
pre->rchild = q; //建立前驱结点的后继线索
pre->Rtag = 1;
}
else q->Rtag =0;
pre = q; //保持pre指向q的前驱
if(q->Ltag == 0) //防止出现转圈,lchild不是前驱线索
Prethread(q->lchild,pre);
Prethread(q->rchild,pre);
}
}
//先序线索化一棵二叉树
void CreatePreThrTree(BiThrTree &T){
BiThrNode *pre = NULL; //pre初始为NULL
if(T!=NULL){ //只有非空放入树才能进行线索化
Prethread(T,pre); //中序线索化二叉树
if(pre->rchild = NULL) //处理遍历的最后一个结点
pre->Rtag = 1;
}
}
//先序遍历的顺序建立二叉链表
void CreateTree(BiThrTree &T){
char ch;
cin>>ch;
if (ch == '#') T = NULL; //递归结束,建立空树
else{
T = new BiThrNode; //申请一个结点
T->data = ch; //将输入值赋值给T
CreateTree(T->lchild); //递归创建左子树
CreateTree(T->rchild); //递归创建右子树
}
}
main(){
BiThrTree T;
cout<<"\n请输入字符!(若输入的是#代表建立的是一棵空树):";
CreateTree(T); //ABC##DE##G##F###
cout<<"\n先序线索化输出二叉链表:"; //A B C # # D E # # G # # F # # #
CreatePreThrTree(T);
BiThrNode *p;
cout<<"\n\n请输入要查找的";
}
******、算法分析
if(q->Ltag == 0) //防止出现转圈,lchild不是前驱线索
Inthread(q->lchild,pre);
上面的代码逻辑是防止重复出现q->lchild = pre的情况,所以使用q->Ltag == 0
来判断如果q已经指向了前驱结点B,就没必要再进行前驱线索化!
5、后序遍历线索化
******、核心算法
//后序遍历二叉树,,一边遍历,一边线索化
void PostThread(BiThrTree &q,BiThrTree &pre){
if (q != NULL){
PostThread(q->lchild,pre);
PostThread(q->rchild,pre);
cout<<q->data<<" ";
if (q->lchild == NULL){ //左子树为空,建立前驱线索
q->lchild = pre;
q->Ltag = 1;
}
else q->Ltag = 0;
if ( pre != NULL && pre->rchild == NULL) //若Pre指针不为空,且右孩子为空,则将其线索化
{
pre->rchild = q; //建立前驱结点的后继线索
pre->Rtag = 1;
}
else q->Rtag =0;
pre = q; //保持pre指向q的前驱
}
}
//后序线索化一棵二叉树
void CreatePostThrTree(BiThrTree &T){
BiThrNode *pre = NULL; //pre初始为NULL
if(T!=NULL){ //只有非空放入树才能进行线索化
PostThread(T,pre); //中序线索化二叉树
if(pre->rchild = NULL) //处理遍历的最后一个结点
pre->Rtag = 1;
}
}
******、算法实现
#include<string.h>
#include<stdio.h>
#define MaxSize 20
#include<iostream>
#include<stdlib.h>
#define endl '\n'
using namespace std;
typedef struct BiThrNode{ //结点
char data; //数据域
struct BiThrNode *lchild,*rchild; //指针域
int Ltag,Rtag; //左右标志
}BiThrNode,*BiThrTree;
//后序遍历二叉树,,一边遍历,一边线索化
void PostThread(BiThrTree &q,BiThrTree &pre){
if (q != NULL){
PostThread(q->lchild,pre);
PostThread(q->rchild,pre);
cout<<q->data<<" ";
if (q->lchild == NULL){ //左子树为空,建立前驱线索
q->lchild = pre;
q->Ltag = 1;
}
else q->Ltag = 0;
if ( pre != NULL && pre->rchild == NULL) //若Pre指针不为空,且右孩子为空,则将其线索化
{
pre->rchild = q; //建立前驱结点的后继线索
pre->Rtag = 1;
}
else q->Rtag =0;
pre = q; //保持pre指向q的前驱
}
}
//后序线索化一棵二叉树
void CreatePostThrTree(BiThrTree &T){
BiThrNode *pre = NULL; //pre初始为NULL
if(T!=NULL){ //只有非空放入树才能进行线索化
PostThread(T,pre); //中序线索化二叉树
if(pre->rchild = NULL) //处理遍历的最后一个结点
pre->Rtag = 1;
}
}
//先序遍历的顺序建立二叉链表
void CreateTree(BiThrTree &T){
char ch;
cin>>ch;
if (ch == '#') T = NULL; //递归结束,建立空树
else{
T = new BiThrNode; //申请一个结点
T->data = ch; //将输入值赋值给T
CreateTree(T->lchild); //递归创建左子树
CreateTree(T->rchild); //递归创建右子树
}
}
main(){
BiThrTree T;
cout<<"\n请输入字符!(若输入的是#代表建立的是一棵空树):";
CreateTree(T); //ABC##DE##G##F###
cout<<"\n后序线索化输出二叉链表:"; //A B C # # D E # # G # # F # # #
CreatePostThrTree(T);
}
6、算法总结
上面的各种遍历线索化操作,是在原有的中序、先序、以及后序遍历的基础上改进而来的,是一边执行中序遍历,一边执行线索化操作的!实现思想和原理都是一样的!