当前位置: 首页 > news >正文

线索二叉树构造及遍历算法

线索二叉树构造以及遍历算法

  • 线索二叉树(中序遍历版)
    • 构造线索二叉树
    • 构造双向线索链表
    • 遍历中序线索二叉树

线索二叉树(中序遍历版)

中序遍历找到对应结点的前驱(土方法)

visit 函数
q 是否等于 p
传入节点 q
final = pre
pre = q
返回
开始
调用 InOrder T
树 T 是否为空
结束
递归调用 InOrder T->lchild
调用 visit T
递归调用 InOrder T->rchild
visit
typedef struct BiNode{
    int data;
    BiNode *lchild, *rchild;
}BiNode, *BiTree;

BiNode *p;  // 指向目标节点
BiNode *pre = NULL; // 指向当前访问节点的前驱
BiNode *final = NULL; // 记录最终结果

void InOrder(BiTree T){
    if(T != NULL){
        InOrder(T -> lchild);
        visit(T);
        InOrder(T -> rchild);
    }
}

// 找到目标节点的前驱节点
void visit(BiNode *q){  // q代表当前访问节点
    if(p == q)
        final = pre;   // 如果当前访问节点和目标节点一致了,那么pre就是我们需要找的前驱
    else
        pre = q;       // 如果不一致,那么更新前驱节点
}

由上述代码我们可以知道,如果使用土方法来寻找目标节点的前驱节点,那么每找一次,就需要对二叉树进行一次遍历,这样对资源的浪费是不言而喻的,所以我们需要采用线索二叉树来更加快速地寻找对应节点的前驱后继,通过线索二叉树,我们可以实现对二叉树的随机访问。

问题1:为什么在visit函数中不需要对q进行迭代?

回答1:因为 q q q的迭代是在 I n O r d e r InOrder InOrder中进行的,在每一次对 I n O r d e r InOrder InOrder的递归中,传入 v i s i t visit visit的节点会不会·不断变化,也就实现了对 q q q的迭代。

线索二叉树实际就是用空的 n + 1 个空指针指向直接前驱和直接后继。如果一个节点的左孩子为空,则左孩子指针指向当前节点的前驱,改 l t a g ltag ltag为1;如果一个节点的右孩子为空,则用右孩子指针指向当前节点的后继,改 r t a g rtag rtag为1。

lchildltagdatartagrchild
指示左孩子00指示右孩子
指示直接前驱11指示直接后继
// 线索二叉树的存储结构
typedef struct ThreadTree{
    int data;
    struct ThreadTree *lchild, *rchild;
    int ltag, rtag;
}ThreadNode, *ThreadTree;

构造线索二叉树

通过中序遍历对二叉树线索化

InThread函数
p 是否为空?
传入 p 和 pre
返回
递归调用 InThread p->lchild, pre
p->lchild 是否为空?
p->lchild = pre p->ltag = 1
不做操作
pre 是否非空且 pre->rchild 是否为空?
pre->rchild = p pre->rtag = 1
不做操作
pre = p
递归调用 InThread p->rchild, pre
开始
调用 CreateInThread T
树 T 是否为空?
结束
初始化 pre = NULL
调用 InThread T, pre
pre->rchild = NULL pre->rtag = 1
void InThread(ThreadTree &p, ThreadTree &pre){ // p是当前访问节点,pre为当前访问节点的前驱
    if(p != NULL){
        InThread(p -> lchild);
        if(p -> lchild == NULL){  // 如果左孩子为空,则更新左孩子为前驱,ltag为1
            p -> lchild = pre;
            ltag = 1;
        }
        if(pre != NULL && pre -> rchild == NULL){ // 若前驱节点非空且其右子树为空,则更新其右孩子为后继,rtag为1
            pre -> rchild = p;
            pre -> rtag = 1;
        }
        pre = p;
        InThread(p -> rchild);
    }
}
void CreateInThread(ThreadTree T){
    Thread pre = NULL;
    if(T != NULL){
        InThread(T, pre);
        pre -> rchild = NULL; // 处理最后一个节点
        pre -> rtag = 1;
    }
}

问题2:为什么在创建二叉树的时候需要判断pre是否为空?

回答2为了避免空指针引用,我们在创建线索二叉树的时候,会把pre初始化为NULL(也就是其实并没有这个节点),因为第一个节点没有直接的前驱,而如果我们不对空指针进行判断的话,那么pre的后继就会是当前节点p,那么究竟谁才是第一个节点呢?运行起来就会导致程序崩溃。只有当pre不为空时,才有意义去判断其右子树是否为空,为它建立线索二叉树才有意义。

构造双向线索链表

但是这样的线索二叉树还是存在一些问题,比如没有办法从第一个节点直接遍历到最后一个节点,为此我们可以建立一个头节点,让其lchild指向二叉树的根节点,其rchild指向中序遍历时访问的最后一个节点。令中序遍历的第一个节点的lchild指向头节点,也就是第一个节点的前驱不再是NULL,而是head;令中序遍历的最后一个节点的rchild指向头节点,也就是最后一个节点的后继也不再是NULL,而是head。这样一来,我们就获得了一个双向线索链表。

InThread函数
p 是否为空?
传入 p 和 pre
返回
递归调用 InThread p->lchild, pre
p->lchild 是否为空?
p->lchild = pre p->ltag = 1
不做操作
pre 是否非空且 pre->rchild 是否为空?
pre->rchild = p pre->rtag = 1
不做操作
pre = p
递归调用 InThread p->rchild, pre
开始
调用 CreateInThread T
分配头节点 head
head 分配成功?
结束
设置 head->ltag = 0
设置 head->rtag = 1
设置 head->rchild = head
T 是否为空?
设置 head->lchild = head
设置 head->lchild = T
初始化 pre = head
调用 InThread T, pre
设置 pre->rchild = head pre->rtag = 1
初始化 first = head->lchild
first->ltag == 0?
first = first->lchild
设置 first->lchild = head
void InThread(ThreadTree &p, ThreadTree &pre){ // p是当前访问节点,pre为当前访问节点的前驱
    if(p != NULL){
        InThread(p -> lchild);
        
        if(p -> lchild == NULL){  // 如果左孩子为空,则更新左孩子为前驱,ltag为1
            p -> lchild = pre;
            ltag = 1;
        }
        if(pre != NULL && pre -> rchild == NULL){ // 若前驱节点非空且其右子树为空,则更新其右孩子为后继,rtag为1
            pre -> rchild = p;
            pre -> rtag = 1;
        }
        pre = p;
        
        InThread(p -> rchild);
    }
}
void CreateInThread(ThreadTree T){
    ThreadNode *head = (ThreadTree*)malloc(sizeof(ThreadTree));
    if(head == NULL)
        return ;
    
    head -> ltag = 0; // 指向根节点
    head -> rtag = 1; // 指向最后一个节点
    head -> rchild = head; // 初始化右指针指向自己
    
    if(T == NULL){
        head -> lchild = head;
    }else {
        head -> lchild = T;
        ThreadTree pre = head;
        InTread(T, pre);
		
        // 处理最后一个节点
        pre -> rchild = head;
        pre -> rtag = 1;
        
        // 处理第一个节点
        ThreadNode *first = head -> lchild; // 初始化第一个节点为根节点,方便找到第一个节点
        // 寻找中序遍历的第一个节点
        while(first -> ltag == 0) // 如果lchild是指向左孩子则迭代
            first = first -> lchild;
        first -> lchild = head;
    }
}

遍历中序线索二叉树

只要先找到序列中的第一个节点,然后依次找节点的后继,直到其后继为空便可完成遍历;

1. 求第一个节点

ThreadNode *FirstNode(ThreadNode *p){
    while(p -> ltag == 0) p = p -> lchild;
    return p;
}

2. 求中序线索二叉树中节点p在中序序列下的后继

ThreadNode *NextNode(ThreadNode *p){
    if(p -> rtag == 0) return FirstNode(p -> rchild); // 右子树中最左下节点
    else return p -> rchild;
}

3. 求中序线索二叉树的最后一个节点

ThreadNode *LastNode(ThreadNode *p){
    while(p -> rtag == 0) p = p -> rchild;
    return p;
}

4. 求节点p前驱

ThreadNode *PreNode(ThreadNode *p){
    if(p -> ltag == 0) return LastNode(p -> lchild);
    return p;
}

利用上述1.2.两个算法,我们可以写出不含头节点的中序线索二叉树的中序遍历算法:

void InOrder(ThreadNode *T){
    for(ThreadNode *p = FirstNode(T); p != NULL; p = NextNode(p))
        visit(p); // 访问节点,可自由设定
}

相关文章:

  • Day 14: 从上到下打印二叉树
  • Android启动速度优化
  • STM32单片机FreeRTOS系统11 系统时钟节拍和时间管理,定时器组
  • 基于小波分析法的行波测距判断故障及定位故障Matlab仿真
  • std::ranges::views::split, lazy_split, std::ranges::split_view, lazy_split_view
  • vs2022用git插件重置--删除更改(--hard)后恢复删除的内容
  • 深入解析Go语言Channel:源码剖析与并发读写机制
  • 高级定时器的解码器模式
  • upload-labs-靶场(1-19关)通关攻略
  • python-leetcode 50.岛屿数量
  • 颤抖:quiver,shiver,tremble的区别
  • 火绒终端安全管理系统V2.0--分层防御之行为拦截层
  • Benewake(北醒) 快速实现TF-NOVA IIC接口与电脑通信的操作说明
  • 电子文档安全管理系统 V6.0 resources/backup存在任意文件下载漏洞(DVB-2025-8794)
  • 数据结构与算法:归并排序
  • AIM-T500绝缘监测仪:实时监测,确保IT系统绝缘安全-安科瑞 蒋静
  • 2025-03-12 学习记录--C/C++-PTA 习题10-11 有序表的增删改查操作
  • 论数组去重之高效方法
  • ai讲angular rxjs
  • [023-01-40].第40节:组件应用 - OpenFeign与 Sentinel 集成实现fallback服务降级
  • “女硕士失踪13年生两孩”案进入审查起诉阶段,哥哥:妹妹精神状态好转
  • 中共中央、国务院印发《生态环境保护督察工作条例》
  • 浙江首个核酸药谷落子杭州,欢迎订阅《浪尖周报》第23期
  • 印控克什米尔地区再次传出爆炸声
  • 印巴开始互袭军事目标,专家:冲突“螺旋式升级”后果或不可控
  • 乘客被地铁厕所门砸伤,南京地铁:突然坏的,已和乘客沟通处理