《数据结构》-线索二叉树

静态数组实现二叉树

二叉链表实现二叉树

三叉链表实现二叉树

线索二叉树

为什么需要线索二叉树

当用二叉链表作为二叉树的存储结构时,可以很方便的找到某个结点的左右孩子;但一般情况下,无法直接找到该结点在某种遍历序列中的前驱和后继结点。

解决方法:

  • 通过遍历查找–浪费时间
  • 增加前驱,后继指针域–浪费空间
  • 利用二叉链表中的空指针域

具有 n 个结点的二叉链表中,一共有 2n 个指针域;因为 n 个结点中有 n-1 个孩子,即 2n 个指针域中,有 n-1 个用来指示结点的左右孩子,其余 n+1 个指针域为空。

线索二叉树的定义

如果某个结点的左孩子为空,则将空的左孩子指针域改为 指向其前驱;如果某结点的右孩子为空,这将空的右孩子指针域改为 指向其后继

这种改变指向的指针称为 线索

加上了线索的二叉树称为 线索二叉树(Threaded Binary Tree)

对二叉树按某种遍历次序使其变为线索二叉树的过程叫 线索化

在这里插入图片描述

为了区分 lchildrchild 指针到底是指向孩子的指针,还是指向前驱或者后继的指针,对二叉链表中 每个结点增设两个标识域ltag 和 rtag,并约定

  • ltag = 0 lchild 指向该结点的左孩子
  • ltag = 1 lchild 指向该结点的 前驱
  • rtag = 0 rchild 指向该结点的右孩子
  • rtag = 1 rchild 指向该结点的 后继

所以这样的结点结构为

struct BiThrNode
{
	int data;
	int ltag;
	int rtag;
	struct BiThrNode *lchild;
	struct BiThrNode *rchild;
}BiThrNode,*BiThrTree;

先序线索二叉树

在这里插入图片描述

中序线索二叉树

在这里插入图片描述

后续线索二叉树

在这里插入图片描述

增加头结点的线索二叉树

在这里插入图片描述

本着不能有指针域空着的原则, 避免悬空态,增设一个头结点

头结点的特征

  • ltag = 0 lchild 指向根结点
  • rtag = 1 rchild 指向遍历序列中的最后一个结点
  • 遍历序列中第一个结点的 lc 域 和最后一个结点的 rc 域都指向头结点

在这里插入图片描述

线索二叉树的程序实现

main.c

/*
 * Change Logs:
 * Date           Author       Notes
 * 2021-07-20     tyustli      first version
 */

#include "tree.h"

void visitT(TElemType e)
{
    printf("%d ", e);
}

int main(int argc, char *argv[])
{
    printf("this bitree\r\n");
    BiThrTree H, T;

    printf("请按先序输入二叉树(如:1 2 0 0 0表示1为根结点,2为左子树的二叉树)\n");

    CreateBiThrTree(&T);     // 按先序产生二叉树
    InOrderThreading(&H, T); // 在中序遍历的过程中,中序线索化二叉树
    printf("中序遍历(输出)线索二叉树:\n");
    InOrderTraverse_Thr(H, visitT); // 中序遍历(输出)线索二叉树
    printf("\n");
    DestroyBiThrTree(&H); // 销毁线索二叉树
}

/***************** end of file ******************/

tree.c

#include "tree.h"

TElemType Nil = 0; // 设整型以0为空

// 按先序输入线索二叉树中结点的值,构造线索二叉树T。0(整型)/空格(字符型)表示空结点
void CreateBiThrTree(BiThrTree *T)
{
    TElemType ch;
    scanf("%d ", &ch);
    if (ch == Nil)
        (*T) = NULL;
    else
    {
        (*T) = (BiThrTree)malloc(sizeof(struct BiThrNode)); // 生成根结点(先序)
        if (!(*T))
            exit(-1);
        (*T)->data = ch;                // 给根结点赋植
        CreateBiThrTree(&(*T)->lchild); // 递归构造左子树
        if ((*T)->lchild)               // 有左孩子
            (*T)->LTag = Link;          // 给左标志赋值(指针)
        CreateBiThrTree(&(*T)->rchild); // 递归构造右子树
        if ((*T)->rchild)               // 有右孩子
            (*T)->RTag = Link;          // 给右标志赋值(指针)
    }
}

BiThrTree pre; // 全局变量,始终指向刚刚访问过的结点

// 通过中序遍历进行中序线索化,线索化之后pre指向最后一个结点。算法6.7
void InThreading(BiThrTree p)
{
    if (p) // 线索二叉树不空
    {
        InThreading(p->lchild); // 递归左子树线索化
        if (!p->lchild)         // 没有左孩子
        {
            p->LTag = Thread; // 左标志为线索(前驱)
            p->lchild = pre;  // 左孩子指针指向前驱
        }
        if (!pre->rchild) // 前驱没有右孩子
        {
            pre->RTag = Thread; // 前驱的右标志为线索(后继)
            pre->rchild = p;    // 前驱右孩子指针指向其后继(当前结点p)
        }
        pre = p;                // 保持pre指向p的前驱
        InThreading(p->rchild); // 递归右子树线索化
    }
}

// 中序遍历二叉树T,并将其中序线索化,Thrt指向头结点
void InOrderThreading(BiThrTree *Thrt, BiThrTree T)
{
    if (!(*Thrt = (BiThrTree)malloc(sizeof(struct BiThrNode)))) // 生成头结点不成功
        exit(-1);
    (*Thrt)->LTag = Link;    // 建头结点,左标志为指针
    (*Thrt)->RTag = Thread;  // 右标志为线索
    (*Thrt)->rchild = *Thrt; // 右指针回指
    if (!T)                  // 若二叉树空,则左指针回指
    {
        (*Thrt)->lchild = *Thrt;
    }
    else
    {
        (*Thrt)->lchild = T;   // 头结点的左指针指向根结点
        pre = *Thrt;           // pre(前驱)的初值指向头结点
        InThreading(T);        // 中序遍历进行中序线索化,pre指向中序遍历的最后一个结点
        pre->rchild = *Thrt;   // 最后一个结点的右指针指向头结点
        pre->RTag = Thread;    // 最后一个结点的右标志为线索
        (*Thrt)->rchild = pre; // 头结点的右指针指向中序遍历的最后一个结点
    }
}

// 中序遍历线索二叉树T(头结点)的非递归算法。
void InOrderTraverse_Thr(BiThrTree T, void (*Visit)(TElemType))
{
    BiThrTree p;
    p = T->lchild; // p指向根结点
    while (p != T)
    {                           // 空树或遍历结束时,p==T
        while (p->LTag == Link) // 由根结点一直找到二叉树的最左结点
            p = p->lchild;
        Visit(p->data);                             // 访问此结点
        while (p->RTag == Thread && p->rchild != T) // p->rchild是线索(后继),且不是遍历的最后一个结点
        {
            p = p->rchild;
            Visit(p->data); // 访问后继结点
        }
        p = p->rchild; // 若p->rchild不是线索(是右孩子),p指向右孩子,返回循环,
    }                  // 找这棵子树中序遍历的第1个结点
}

// PreOrderThreading()调用的递归函数
void PreThreading(BiThrTree p)
{
    if (!pre->rchild) // p的前驱没有右孩子
    {
        pre->rchild = p;    // p前驱的后继指向p
        pre->RTag = Thread; // pre的右孩子为线索
    }
    if (!p->lchild) // p没有左孩子
    {
        p->LTag = Thread; // p的左孩子为线索
        p->lchild = pre;  // p的左孩子指向前驱
    }
    pre = p;                     // 移动前驱
    if (p->LTag == Link)         // p有左孩子
        PreThreading(p->lchild); // 对p的左孩子递归调用preThreading()
    if (p->RTag == Link)         // p有右孩子
        PreThreading(p->rchild); // 对p的右孩子递归调用preThreading()
}

// 先序线索化二叉树T,头结点的右指针指向先序遍历的最后1个结点
void PreOrderThreading(BiThrTree *Thrt, BiThrTree T)
{
    if (!(*Thrt = (BiThrTree)malloc(sizeof(struct BiThrNode)))) // 生成头结点
        exit(-1);
    (*Thrt)->LTag = Link;        // 头结点的左指针为孩子
    (*Thrt)->RTag = Thread;      // 头结点的右指针为线索
    (*Thrt)->rchild = *Thrt;     // 头结点的右指针指向自身
    if (!T)                      // 空树
        (*Thrt)->lchild = *Thrt; // 头结点的左指针也指向自身
    else
    {                        // 非空树
        (*Thrt)->lchild = T; // 头结点的左指针指向根结点
        pre = *Thrt;         // 前驱为头结点
        PreThreading(T);     // 从头结点开始先序递归线索化
        pre->rchild = *Thrt; // 最后一个结点的后继指向头结点
        pre->RTag = Thread;
        (*Thrt)->rchild = pre; // 头结点的后继指向最后一个结点
    }
}

// 先序遍历线索二叉树T(头结点)的非递归算法
void PreOrderTraverse_Thr(BiThrTree T, void (*Visit)(TElemType))
{
    BiThrTree p = T->lchild; // p指向根结点
    while (p != T)           // p没指向头结点(遍历的最后1个结点的后继指向头结点)
    {
        Visit(p->data);      // 访问根结点
        if (p->LTag == Link) // p有左孩子
            p = p->lchild;   // p指向左孩子(后继)
        else                 // p无左孩子
            p = p->rchild;   // p指向右孩子或后继
    }
}

// PostOrderThreading()调用的递归函数
void PostThreading(BiThrTree p)
{
    if (p) // p不空
    {
        PostThreading(p->lchild); // 对p的左孩子递归调用PostThreading()
        PostThreading(p->rchild); // 对p的右孩子递归调用PostThreading()
        if (!p->lchild)           // p没有左孩子
        {
            p->LTag = Thread; // p的左孩子为线索
            p->lchild = pre;  // p的左孩子指向前驱
        }
        if (!pre->rchild) // p的前驱没有右孩子
        {
            pre->RTag = Thread; // p前驱的右孩子为线索
            pre->rchild = p;    // p前驱的后继指向p
        }
        pre = p; // 移动前驱
    }
}

// 后序递归线索化二叉树
void PostOrderThreading(BiThrTree *Thrt, BiThrTree T)
{
    if (!(*Thrt = (BiThrTree)malloc(sizeof(struct BiThrNode)))) // 生成头结点
        exit(-1);
    (*Thrt)->LTag = Link;   // 头结点的左指针为孩子
    (*Thrt)->RTag = Thread; // 头结点的右指针为线索
    if (!T)                 // 空树
    {
        (*Thrt)->lchild = (*Thrt)->rchild = (*Thrt); // 头结点的左右指针指向自身
    }
    else
    {                                          // 非空树
        (*Thrt)->lchild = (*Thrt)->rchild = T; // 头结点的左右指针指向根结点(最后一个结点)
        pre = (*Thrt);                         // 前驱为头结点
        PostThreading(T);                      // 从头结点开始后序递归线索化
        if (pre->RTag != Link)                 // 最后一个结点没有右孩子
        {
            pre->rchild = (*Thrt); // 最后一个结点的后继指向头结点
            pre->RTag = Thread;
        }
    }
}

// DestroyBiThrTree调用的递归函数,T指向根结点
void DestroyBiTree(BiThrTree *T)
{
    if (*T) // 非空树
    {
        if ((*T)->LTag == 0)             // 有左孩子
            DestroyBiTree(&(*T)->lchild); // 销毁左孩子子树
        if ((*T)->RTag == 0)             // 有右孩子
            DestroyBiTree(&(*T)->rchild); // 销毁右孩子子树
        free((*T));                      // 释放根结点
        (*T) = NULL;                     // 空指针赋0
    }
}

// 初始条件:线索二叉树Thrt存在。操作结果:销毁线索二叉树Thrt
void DestroyBiThrTree(BiThrTree *Thrt)
{
    if (*Thrt) // 头结点存在
    {
        if ((*Thrt)->lchild)                // 根结点存在
            DestroyBiTree(&(*Thrt)->lchild); // 递归销毁头结点lchild所指二叉树
        free((*Thrt));                      // 释放头结点
        (*Thrt) = NULL;                     // 线索二叉树Thrt指针赋0
    }
}

/***************** end of file ******************/

tree.h

/*
 * Change Logs:
 * Date           Author       Notes
 * 2021-07-20     tyustli      first version
 */

#include <string.h>
#include <ctype.h>
#include <malloc.h> // malloc()等
#include <limits.h> // INT_MAX等
#include <stdio.h>  // EOF(=^Z或F6),NULL
#include <stdlib.h> // atoi()
#include <math.h>   // floor(),ceil(),abs()

// 函数结果状态代码
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
// #define OVERFLOW -2 因为在math.h中已定义OVERFLOW的值为3,故去掉此行
typedef int Status;  // Status是函数的类型,其值是函数结果状态代码,如OK等
typedef int Boolean; // Boolean是布尔类型,其值是TRUE或FALSE

typedef int TElemType;

// 二叉树的二叉线索存储表示
enum PointerTag // 枚举
{
    Link,
    Thread
}; // Link(0):指针,Thread(1):线索

struct BiThrNode
{
    TElemType data;
    struct BiThrNode *lchild, *rchild; // 左右孩子指针
    enum PointerTag LTag, RTag;        // 左右标志
};
typedef struct BiThrNode *BiThrTree;

void CreateBiThrTree(BiThrTree *T);
void InThreading(BiThrTree p);
void InOrderThreading(BiThrTree *Thrt, BiThrTree T);
void InOrderTraverse_Thr(BiThrTree T, void (*Visit)(TElemType));
void PreThreading(BiThrTree p);
void PreOrderThreading(BiThrTree *Thrt, BiThrTree T);
void PreOrderTraverse_Thr(BiThrTree T, void (*Visit)(TElemType));
void PostThreading(BiThrTree p);
void PostOrderThreading(BiThrTree *Thrt, BiThrTree T);
void DestroyBiTree(BiThrTree *T);
void DestroyBiThrTree(BiThrTree *Thrt);

/***************** end of file ******************/

makefile

objects  = main.o tree.o
obj: $(objects)
	cc -o obj $(objects) -lm

main.o : tree.h
tree.o : tree.h

.PHONY : clean
clean :
	-rm obj $(objects)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值