线索二叉树
一、线索二叉树定义
背景:为解决遍历只能从根结点开始这个问题,因为普通二叉树找前驱和后继很麻烦
线索二叉树在二叉树的结点上加上线索的二叉树。
二、线索二叉树的存储结构
由二叉树的链式存储改进而来
二叉树的类型表述
typedef struct TreeNode{
Elemtype data; //数据域
struct BiTNode *lchide, *rchild; //左、右孩子指针
}BiTNode, *BiTree;
线索二叉树的类型表述
typedef struct TreeNode{
Elemtype data; //数据域
struct BiTNode *lchide, *rchild; //左、右孩子指针
int ltag, rtag; //左、右线索标志
}ThreadNode, *ThreadTree;
tag==0:表示指针指向孩子
tag==1:表示指针指向线索
三、线索二叉树的分类
中序线索二叉树、先序线索二叉树、后序线索二叉树
四、二叉树线索化
对二叉树进行线索化:对二叉树以某种遍历方式(如先序、中序、后序或层次等)进行遍历,使其变为线索二叉树的过程。
中序线索化
//全局变量pre,指向当前访问结点的前驱
ThreadNode *pre = NULL;
//中序线索化二叉树T(三种一样,只是调用线索化函数不同)
void CreateThread(ThreadTree T){
pre = NULL; //pre初始为NULL
if(T != NULL){ //非空二叉树才能线索化
InTread(T); //中序线索化二叉树
if(pre->rchild == NULL){
pre->rtag = 1; //处理遍历的最后一个结点
}
}
}
//中序遍历二叉树,一边遍历,一边线索化
void InOrder(ThreadTree T){
if(T!=NULL){
InOrder(T->lchild); //递归遍历左子树
visit(T); //访问根结点
InOrder(T->rchild); //递归遍历右子树
}
}
//访问结点,顺便线索化(三种一样)
void visit(TheadNode *q){
if(q->lchild == NULL){//左子树为空,建立前驱线索
q->lchild = pre;
q->ltag = 1;
}
if(pre!=NULL && pre->rchild==NULL){
pre->rchild = q; //建立前驱结点的后继线索
pre->rtage = 1;
}
pre = q;
}
先序线索化
//全局变量pre,指向当前访问结点的前驱
ThreadNode *pre = NULL;
//先序线索化二叉树T(三种一样,只是调用线索化函数不同)
void CreateThread(ThreadTree T){
pre = NULL; //pre初始为NULL
if(T != NULL){ //非空二叉树才能线索化
PreTread(T); //先序线索化二叉树
if(pre->rchild == NULL){
pre->rtag = 1; //处理遍历的最后一个结点
}
}
}
//中序遍历二叉树,一边遍历,一边线索化
void PreOrder(ThreadTree T){
if(T!=NULL){
visit(T); //访问根结点
if(T->ltag == 0) //lchild不是前驱线索,是线索还遍历则无限循环
InOrder(T->lchild); //递归遍历左子树
InOrder(T->rchild); //递归遍历右子树
}
}
//访问结点,顺便线索化(三种一样)
void visit(TheadNode *q){
if(q->lchild == NULL){//左子树为空,建立前驱线索
q->lchild = pre;
q->ltag = 1;
}
if(pre!=NULL && pre->rchild==NULL){
pre->rchild = q; //建立前驱结点的后继线索
pre->rtage = 1;
}
pre = q;
}
后续线索化
//全局变量pre,指向当前访问结点的前驱
ThreadNode *pre = NULL;
//后序线索化二叉树T(三种一样,只是调用线索化函数不同)
void CreateThread(ThreadTree T){
pre = NULL; //pre初始为NULL
if(T != NULL){ //非空二叉树才能线索化
PostTread(T); //后序线索化二叉树
if(pre->rchild == NULL){
pre->rtag = 1; //处理遍历的最后一个结点
}
}
}
//中序遍历二叉树,一边遍历,一边线索化
void PostOrder(ThreadTree T){
if(T!=NULL){
InOrder(T->lchild); //递归遍历左子树
InOrder(T->rchild); //递归遍历右子树
visit(T); //访问根结点
}
}
//访问结点,顺便线索化(三种一样)
void visit(TheadNode *q){
if(q->lchild == NULL){//左子树为空,建立前驱线索
q->lchild = pre;
q->ltag = 1;
}
if(pre!=NULL && pre->rchild==NULL){
pre->rchild = q; //建立前驱结点的后继线索
pre->rtage = 1;
}
pre = q;
}
五、线索二叉树找前驱和后继
1.1 中序线索二叉树找中序后继
在中序线索二叉树中找指定结点*p的中序后继next
算法思想:
(1)若p->rtag == 1,则next为后继线索,即next = p->rchild。
(2)若p->rtag == 0,则p必有右孩子,next为p的右子树中最左下结点。
//找到以P为根的子树中,第一个被中序遍历的结点
ThreadNode *Firstnode(ThreadNode *p){
//循环找到最左下结点(不一定是叶子结点)
while(p->ltag == 0) p = p->lchild;
return p;
}
//在中序线索二叉树中找到结点p的后继结点
TheadNode *Nextnode(ThreadNode *p){
//右子树中最左下结点
if(p->rtag == 0) return Fistnode(p->rchild);
else return p->rchild; //rtag=1直接返回后继线索
}
应用:中序线索二叉树的中序遍历
空间复杂度O(1)
//对中序线索二又树进行中序遍历(利用线索实现的非递算法)
void Inorder(ThreadNode *T){
for (ThreadNode *p=Firstnode(T); p!=NULL; p=Nextnode(p))
visit (p);
}
1.2 中序线索二叉树找中序前驱
在中序线索二叉树中找指定结点*p的中序前驱pre
算法思想:
(1)若p->rtag == 1,则pre为前驱线索,即pre = p->lchild。
(2)若p->rtag == 0,则p必有左孩子,pre为p的左子树中最右下结点。
//找到以P为根的子树中,最后一个被中序遍历的结点
ThreadNode *Lastnode(ThreadNode *p){
//循环找到最右下结点(不一定是叶子结点)
while(p->rtag == 0) p = p->rchild;
return p;
}
//在中序线索二叉树中找到结点p的前驱结点
TheadNode *Prenode(ThreadNode *p){
//右子树中最左下结点
if(p->rtag == 0) return Lastnode(p->lchild);
else return p->lchild; //rtag=1直接返回前驱线索
}
应用:中序线索二叉树的逆向中序遍历
空间复杂度O(1)
//对中序线索二又树进行逆向中序遍历(利用线索实现的非递算法)
void Inorder(ThreadNode *T){
for (ThreadNode *p=Lastnode(T); p!=NULL; p=Prenode(p))
visit (p);
}
2.1 先序线索二叉树找先序后继
在先序线索二叉树中找指定结点*p的先序后继next
先序遍历:根左右。
算法思想:
(1)若p->rtag == 1,则pre为前驱线索,即next = p->rchild。
(2)若p->rtag == 0,则
①若p有左孩子,则next为左孩子
②若p没有左孩子,则next为右孩子
2.2先序线索二叉树找先序前驱
在先序线索二叉树中找指定结点*p的先序前驱pre
由于先序遍历:根左右。p结点的左右子树中的结点只能是根的后继,不可能是前驱,因此无法找前驱
解决:改用三叉链表,三个指针:指向父结点、左孩子结点和右孩子结点
算法思想:
(1)若p->rtag == 1,则pre为前驱线索,即pre = p->lchild。
(2)若p->rtag == 0,则
①若能找到p的父结点且p是左孩子,则pre为父结点
②若能找到p的父结点且p是右孩子,其左兄弟为空,则pre为父结点
②若能找到p的父结点且p是右孩子,其左兄弟为非空,则pre为左兄弟子树中最后一个被先序遍历的结点。
3.1后序线索二叉树找后序后继
在后序线索二叉树中找指定结点*p的后序后继next`
由于后序遍历:左右根。p结点的左右子树中的结点只能是根的后前驱,不可能是后继,因此`无法找前后继
解决:改用三叉链表,三个指针:指向父结点、左孩子结点和右孩子结点
算法思想:
(1)若p->rtag == 1,则next为后继线索,即next = p->rchild。
(2)若p->rtag == 0,则
①若能找到p的父结点且p是右孩子,则next为父结点
②若能找到p的父结点且p是左孩子,其右兄弟为空,则next为父结点
②若能找到p的父结点且p是左孩子,其右兄弟为非空,则next为左兄弟子树中第一个被后续序遍历的结点。
3.2后序线索二叉树找后序前驱
在后序线索二叉树中找指定结点*p的后序前驱pre
先序遍历:左右根。
算法思想:
(1)若p->rtag == 1,则pre为前驱线索,即pre = p->lchild。
(2)若p->rtag == 0,则
①若p有右孩子,则pre为右孩子
②若p没有右孩子,则pre为左孩子
树的存储结构
一、存储结构
顺序存储和链式存储
方法:
双亲表示法(顺序存储)
孩子表示法(顺序+链式存储)
孩子兄弟表示法(链式存储)
二、双亲表示法(顺序存储)
双亲表示法:顺序存储结点数据,结点中保存父结点在数组中的下标
优点:找父节点方便。
缺点:找孩子不方便。
注:双亲表示法与二叉树的顺序存储不一样,双亲表示法也可表示二叉树
类型描述
结点包括数据和父亲下标, 树包括结点数组和结点个数
#define MAX_TREE_SIZE 100 //树中最多结点数
typedef struct{ //树的结点定义
ElemType data; //数据元素
int parent; //双亲位置域
}PTNode;
typedef struct{ //树的类型定义
PTNode nodes[MAX_TREE_SIZE]; //双亲表示
int n; //结点数
}PTree;
增加一个结点
新增元素,无需按逻辑次序存储,可以放到删除结点留下的存储空间里
删除一个结点
方案一:数据取出,双亲指针改为-1
方案二:用存储空间中最后一个存的结点把要删的结点覆盖
查找一个结点
找父结点方便、找孩子不方便
空数据导致遍历慢
三、孩子表示法(顺序+链式存储)
孩子表示法:顺序存储结点数据,结点中保存孩子链表头指针(链式存储)
优点:找孩子方便。
缺点:找父结点不方便。
类型描述
孩子结点包括孩子下标和下一个孩子指针, 数组包括数据和孩子结点, 树包括数组和数组元素(结点)个数及根的下标
#define MAX_TREE_SIZE 100 //树中最多结点数
struct CTNode{
int child; //孩子结点在数组中的位置
struct CTNode *next; //下一个孩子
};
typedef struct{
ElemType data;
struct CTNode* firstchild; //第一个孩子
}CTBox;
typedef struct{
CTBox nodes[MAX_TREE_SIZE];
int n, r; //结点数和根的位置
}CTree;
增加一个结点
新增元素,父结点后新增一个孩子结点,数组中加一个数组元素
删除一个结点
父结点后的链表中将此结点删除
数组中: ①若此结点后无链表,则直接删除 ②若此结点后有链表,再处理子树
查找一个结点
按图一行一行遍历
找孩子结点方便,找父结点不方便
四、孩子兄弟表示法(顺序+链式存储)
孩子兄弟表示法:用二叉链表存储树——两个指针:第一个孩子和右兄弟
用此方法存储的树,形态上和二叉树类似
类型描述
由二叉树的链式存储(二叉链表)改变而来
typedef struct CSNode{
Elemtype data; //数据域
struct CSTNode *firstchild, *nextsibling; //第一个孩子和右兄弟指针
}CSTNode, *CSTree;
应用:树和二叉树的转换
五、森林和二叉树的转换
本质:用二叉链表存储森林
将森林的根结点连起来,视为兄弟关系
1万+

被折叠的 条评论
为什么被折叠?



