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

数据结构——树(2)

数据结构基础(12)

文章目录

  • 数据结构基础(12)
      • 二叉树的先序遍历
      • 先序遍历
      • 中序遍历
      • 后序遍历
      • 二叉树的层序遍历
      • 由遍历序列构造二叉树
        • 前序 + 中序遍历序列
        • 后序 + 中序遍历序列
        • 层序 + 中序遍历序列
      • 二叉树的中序遍历(缺点):
      • 中序线索二叉树
      • 中序线索化
      • 先序线索化
      • 后序线索化
      • 中序线索二叉树找中序后继
      • 中序线索二叉树找中序前驱
      • 中序线索二叉树找先序后继
      • 先序线索二叉树找先序前驱
      • 后序线索二叉树找后序前驱

二叉树的先序遍历

  • 遍历:按某种次序把所有结点都访问一遍 – > 线性结构
  • 层次遍历:基于树的层次特性确定的次序规则
  • 二叉树的先/中/后序遍历:基于树的递归特性确定的次序规则

二叉树的递归特性

  1. 要么是个空二叉树
  2. 要么就是由“根节点 + 左子树 + 右子树”组成的二叉树

先序遍历:根左右(NLR)-- > 又叫先根遍历

中序遍历:左根右(LNR)- > 又叫中根遍历

后序遍历:左右根(LRN)- > 又叫后根遍历

基础图:

A
B
C

先序遍历: ABC

中序遍历:BAC

后序遍历:BCA

在加深一点:

A
B
C
D
E
F
G

先序遍历: A B D E C F G

中序遍历; D B E A F C G

后序遍历:D E B F G C A

对算数表达式的“分析树”:

先序遍历 — > 前缀表达式

中序遍历 — > 中缀表达式(需要加界限符)

后序遍历 — > 后缀表达式

先序遍历

先序遍历(PreOrder)的操作过程如下:

  1. 若二叉树为空,则什么也不做;
  2. 若二叉树非空:
    ①访问根结点;
    ②先序遍历左子树
    ③先序遍历右子树

实现代码:

typedef struct BiTNode{ElemType data;struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;//先序遍历
void PreOrder(BiTree T){if(T!=NULL){visit(T);                //访问根结点PreOrder(T->lchild);     //递归遍历左子树PreOrder(T->rchild);     //递归遍历右子树}
}

第一次被访问的点

空间复杂度:O(h)

中序遍历

中序遍历(InOrder)的操作过程如下:

  1. 若二叉树为空,则什么也不做;
  2. 若二叉树非空:
    ①先序遍历左子树;
    ②访问根结点;
    ③先序遍历右子树。

实现代码:

typedef struct BiTNode{ElemType data;struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;//中序遍历
void InOrder(BiTree T){if(T!=NULL){InOrder(T->lchild);  //递归遍历左子树visit(T);            //访问根结点InOrder(T->rchild);  //递归遍历右子树}
}

第二次被访问的点

后序遍历

后序遍历(PostOrder)的操作过程如下:

  1. 若二叉树为空,则什么也不做;
  2. 若二叉树非空:
    ①先序遍历左子树;
    ②先序遍历右子树;
    ③访问根结点。

实现代码:

typedef struct BiTNode{ElemType data;struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;//后序遍历
void PostOrder(BiTree T){if(T!=NULL){PostOrder(T->lchild);  //递归遍历左子树PostOrder(T->rchild);  //递归遍历右子树visit(T);              //访问根结点}
}

第三次被访问的点

二叉树的层序遍历

算法思想:

算法思想:

  1. 初始化一个辅助队列
  2. 根结点入队
  3. 若队列非空,则队头结点出队,访问该结点,并将其左、右孩子插入队尾(如果有的话)
  4. 重复步骤3直至队列为空

实现代码:

//二叉树的结点(链式存储)
typedef struct BiTNode{char data;struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;//链式队列结点
typedef struct LinkNode{BiTNode * data;struct LinkNode *next;
}LinkNode;typedef struct{LinkNode *front,*rear; //队头队尾
}LinkQueue;//层序遍历
void LevelOrder(BiTree T){LinkQueue Q;InitQueue(Q);              //初始化辅助队列BiTree p;EnQueue(Q,T);              //将根结点入队while(!IsEmpty(Q)){        //队列不空则循环DeQueue(Q, p);         //队头结点出队visit(p);              //访问出队结点if(p->lchild!=NULL)EnQueue(Q,p->lchild); //左孩子入队if(p->rchild!=NULL)EnQueue(Q,p->rchild); //右孩子入队}
}

​ 存指针而不是结点

    BiTNode * data;

由遍历序列构造二叉树

  • 一个中序遍历序列可能对应多种二叉树形态

  • 一个前序遍历序列可能对应多种二叉树形态

  • 一个后序遍历序列可能对应多种二叉树形态

  • 一个层序遍历序列可能对应多种二叉树形态

SO : 若只给出一颗二叉树的前/中/后/层 序遍历序列中的一种 ,不能唯一确定一颗二叉树

前序 + 中序遍历序列
  • 前序遍历:根结点、前序遍历左子树、前序遍历右子树

前序遍历序列:

根结点 左子树的前序遍历序列 右子树的前序遍历序列

  • 中序遍历:中序遍历左子树、根结点、中序遍历右子树

中序遍历序列:

左子树的中序遍历序列 根结点 右子树的中序遍历序列

前序遍历序列: A D B C E

中序遍历序列: B D C A E

得出的二叉树:

A
D
E
B
C
后序 + 中序遍历序列
  • 后序遍历:前序遍历左子树、前序遍历右子树、根结点

后序遍历序列:

左子树的后序遍历序列 右子树的后序遍历序列 根结点

  • 中序遍历:中序遍历左子树、根结点、中序遍历右子树

中序遍历序列

左子树的中序遍历序列 根结点 右子树的中序遍历序列

后序遍历序列: E F A H C I G B D

中序遍历序列: E A F D H C B G I

得出的二叉树:

D
A
B
E
F
C
G
H
I
层序 + 中序遍历序列
  • 层序遍历序列

    根结点 左子树的根 右子树的根

  • 中序遍历序列

    左子树的中序遍历序列 根结点 右子树的中序遍历序列

层序遍历序列:A B C D E

中序遍历序列:A C B E D

得出的二叉树:

在这里插入图片描述

KEY:找到树的根节点,并根据中序序列划分左右子树,再找到左右子树根结点

二叉树的中序遍历(缺点):

Q1:如何找到指定结点 P 在中序遍历序列中的前驱?

Q2 : 如何找到 p 的中序后继?

思路:从根节点出发,重新进行一次中序遍历,指针 q 记录当前访问的结点,指针 pre 记录上一个被访问的结点

1.当 q == p 时,pre 为前驱

2.当 pre == p 时,q 为后继

缺点:

找前驱、后继很不方便:遍历操作必须从根开始

中序线索二叉树

中序遍历序列:D G B E A F C

在这里插入图片描述

  • 前驱线索(由左孩子指针充当 —— > 红色线
  • 后继线索(由右孩子指针充当)—— > 绿色线

线索链表代码实现:

//线索二叉树结点
typedef struct ThreadNode{ElemType data;struct ThreadNode *lchild,*rchild;int ltag,rtag;      //左、右线索标志
}ThreadNode,*ThreadTree;
  • tag == 0,表示指针指向孩子
    tag ==1,表示指针是“线索”

  • 先序、后序同理

中序线索化

实现代码

// 线索二叉树结点结构定义
typedef struct ThreadNode{ElemType data;  struct ThreadNode *lchild,*rchild;  int ltag,rtag; 
}ThreadNode,*ThreadTree;// 全局变量 pre,指向当前访问结点的前驱
ThreadNode *pre=NULL;  // 中序遍历二叉树并进行线索化
void InThread(ThreadTree T){if(T!=NULL){InThread(T->lchild);visit(T); InThread(T->rchild);  }
}// 访问结点函数(实际处理线索化逻辑)
void visit(ThreadNode *q) {// 左子树为空,建立前驱线索if(q->lchild==NULL){ q->lchild=pre;q->ltag=1;}// 前驱存在且前驱的右孩子为空,建立前驱结点的后继线索if(pre!=NULL&&pre->rchild==NULL){ pre->rchild=q;  pre->rtag=1;}pre=q;  // 更新pre为当前结点
}// 封装中序线索化的入口函数,统一初始化pre并处理最终结点
void CreateInThread(ThreadTree T){pre=NULL;  // 初始化为NULLif(T!=NULL){  InThread(T); // 处理遍历的最后一个结点:若其右孩子为空,标记为线索if(pre->rchild==NULL){ pre->rtag=1;  }}
}

先序线索化

  • 先序线索化中,注意处理爱滴魔力转圈圈问题,当 ltag == 0时,才能对左子树先序线索化

实现代码;

// 线索二叉树结点结构定义
typedef struct ThreadNode{ElemType data;struct ThreadNode *lchild, *rchild;int ltag, rtag;
}ThreadNode, *ThreadTree;// 全局变量 pre,指向当前访问结点的前驱
ThreadNode *pre = NULL;  // 先序遍历二叉树,一边遍历一边线索化
void PreThread(ThreadTree T){if(T != NULL){visit(T);  // 先处理根节点(执行线索化核心逻辑)// lchild 不是前驱线索(即 ltag == 0 时,说明是真实孩子,递归处理左子树)if (T->ltag == 0)  PreThread(T->lchild);  PreThread(T->rchild);  // 递归处理右子树}
}// 访问结点函数(实际进行线索化操作)
void visit(ThreadNode *q) {// 左子树为空,建立前驱线索if(q->lchild == NULL){  q->lchild = pre;q->ltag = 1;}// 前驱存在且前驱的右孩子为空,建立前驱结点的后继线索if(pre != NULL && pre->rchild == NULL){  pre->rchild = q;  pre->rtag = 1;}pre = q;  // 更新 pre 为当前结点,为下一个结点做准备
}// 先序线索化二叉树的入口函数,统一初始化 pre 并处理最后一个结点
void CreatePreThread(ThreadTree T){pre = NULL;  // pre 初始化为 NULLif(T != NULL){  // 非空二叉树才进行线索化PreThread(T);  // 调用先序线索化递归函数// 处理遍历的最后一个结点:若其右孩子为空,标记为线索if (pre->rchild == NULL)  pre->rtag = 1;  }
}

后序线索化

实现代码:

// 线索二叉树结点结构(需提前定义 ElemType,如 typedef int ElemType; )
typedef struct ThreadNode {ElemType data;struct ThreadNode *lchild, *rchild;int ltag, rtag;  // 0: 指向孩子,1: 线索
} ThreadNode, *ThreadTree;// 全局变量:指向当前访问结点的前驱
ThreadNode *pre = NULL;  // 后序遍历 + 线索化(递归逻辑:左 → 右 → 根)
void PostThread(ThreadTree T) {if (T != NULL) {PostThread(T->lchild);  // 后序遍历左子树PostThread(T->rchild);  // 后序遍历右子树visit(T);  // 访问根节点(执行线索化操作)}
}// 访问结点函数(处理线索化逻辑)
void visit(ThreadNode *q) {// 左子树为空 → 建立前驱线索if (q->lchild == NULL) {  q->lchild = pre;q->ltag = 1;}// 前驱存在且其右孩子为空 → 建立后继线索if (pre != NULL && pre->rchild == NULL) {  pre->rchild = q;  pre->rtag = 1;}pre = q;  // 更新前驱为当前结点
}// 后序线索化入口函数(初始化 + 处理最后结点)
void CreatePostThread(ThreadTree T) {pre = NULL;  // 初始化前驱if (T != NULL) {  // 非空树才线索化PostThread(T);  // 调用后序线索化// 处理遍历的最后一个结点(根结点)if (pre->rchild == NULL) {  pre->rtag = 1;  }}
}

中序线索二叉树找中序后继

在中序线索二叉树中找到指定结点 * p 的中序后继 next

  1. 若 p->rtag == 1,则 next = p->rchild
  2. 若 p->rtag == 0

next = p 的右子树中最左下结点

实现代码:

// 找到以 p 为根的子树中,第一个被中序遍历的结点
ThreadNode *Firstnode(ThreadNode *p){// 循环找到最左下结点(不一定是叶结点)while(p->ltag==0) p=p->lchild;  return p;
}// 在中序线索二叉树中找到结点 p 的后继结点
ThreadNode *Nextnode(ThreadNode *p){// 右子树中最左下结点if(p->rtag==0) return Firstnode(p->rchild);  else return p->rchild;  // rtag==1 直接返回后继线索
}
  • 对中序线索二叉树进行中序遍历(利用线索实现的非递归算法)
void Inorder(ThreadNode *T){for(ThreadNode *p=Firstnode(T);p!=NULL; p=Nextnode(p))visit(p);
}

空间复杂度O(1)

中序线索二叉树找中序前驱

在中序线索二叉树中找到指定结点 * p 的中序前驱 pre

  1. 若 p->ltag == 1,则 pre = p->lchild
  2. 若 p->ltag == 0

pre = p 的左子树中最右下结点

实现代码:

// 找到以 p 为根的子树中,最后一个被中序遍历的结点
ThreadNode *Lastnode(ThreadNode *p){// 循环找到最右下结点(不一定是叶结点)while(p->rtag==0) p=p->rchild;  return p;
}// 在中序线索二叉树中找到结点 p 的前驱结点
ThreadNode *Prenode(ThreadNode *p){// 左子树中最右下结点if(p->ltag==0) return Lastnode(p->lchild);  else return p->lchild;  // ltag==1 直接返回前驱线索
}
  • 对中序线索二叉树进行逆向中序遍历
void RevInorder(ThreadNode *T){for(ThreadNode *p=Lastnode(T);p!=NULL; p=Prenode(p))visit(p);
}

中序线索二叉树找先序后继

在先序线索二叉树中找到指定结点 * p 的中序后继 next

  1. 若 p->rtag == 1,则 pre = p->rchild
  2. 若 p->rtag == 0

假设有左孩子

  • 若 p 有左孩子,则先序后继为左孩子

先序遍历 —— 根 左 右
根 (根 左 右) 右

假设没有左孩子

  • 若 p 没有左孩子,则先序后继为右孩子

先序遍历 —— 根 右
根 (根 左 右)

先序线索二叉树找先序前驱

在先序线索二叉树中找到指定结点 * p 的中序前驱 pre

  1. 若 p->ltag == 1,则 pre = p->lchild
  2. 若 p->ltag == 0

在先序遍历中,左右子树的结点只可能是根的后继,不可能是前驱

后序线索二叉树找后序前驱

在先序线索二叉树中找到指定结点 * p 的中序前驱 pre

  1. 若 p->ltag == 1,则 pre = p->lchild
  2. 若 p->ltag == 0

假设有右孩子

  • 若 p 有右孩子,则后序前驱为右孩子

后序遍历 —— 左 右 根
左 (左 右 根) 根

假设没有右孩子

  • 若 p 没有右孩子,则后序前驱为左孩子

后序遍历 —— 左 根
(左 右 根) 根

中序线索二叉树先序线索二叉树后序线索二叉树
找前驱
找后继
http://www.dtcms.com/a/283691.html

相关文章:

  • 6. 工程化实践类:《Webpack 5 性能优化全指南:从构建速度到输出质量》
  • DocsGPT:您的智能知识助手,解锁高效信息检索
  • pytorch学习笔记(五)-- 计算机视觉的迁移学习
  • Redis3:Redis数据结构与命令全解析
  • Redis单机主从复制+多机主从复制的实现(一主两从)
  • C语言模拟面向对象三大特性与C++实现对比
  • HTML常用标签汇总(精简版)
  • 模型移植实战:从PyTorch到ONNX完整指南
  • ionic 切换开关操作指南
  • iOS 构建配置与 AdHoc 打包说明
  • 从零开发推客小程序系统:完整技术方案与实战经验
  • C语言:20250717笔记
  • Redis深度解析:从缓存原理到高并发实战
  • AI算法机器学习主要类型
  • 专业云端视觉计算解决方案:云渲染云电脑
  • 【AI论文】基于反射生成模型的测试时动态缩放方法
  • 【软件测试】软件测试分类与方法解析:目标到工具
  • HANA SQLScript中的变量类型汇总
  • 云原生环境下的安全控制框架设计
  • USB导出功能(QT)
  • Windows10笔记本电脑开启BIOS
  • 云手机网络加速全攻略:解决游戏卡顿与APP连接失败困扰
  • 玖[9],相机/镜头/光源
  • yolo位置损失中的权重项的作用是什么
  • YOLO融合[CVPR2025]EVSSM中的EDFFN模块
  • LeetCode20
  • 2D视觉系统标定流程与关键要求
  • 不同相机CMOS噪点对荧光计算的影响
  • 前端设计模式应用精析
  • Java零基础快速入门