线索二叉树 && 树的存储结构

线索二叉树

一、线索二叉树定义

背景:为解决遍历只能从根结点开始这个问题,因为普通二叉树找前驱和后继很麻烦

线索二叉树在二叉树的结点上加上线索的二叉树。

二、线索二叉树的存储结构

由二叉树的链式存储改进而来

二叉树的类型表述
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;
应用:树和二叉树的转换

五、森林和二叉树的转换

本质:用二叉链表存储森林

将森林的根结点连起来,视为兄弟关系

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值