【数据结构】二叉树(定义、性质、存储、遍历、构造)解析+完整代码

文章目录

    • 1.树的基本概念
    • 2.二叉树的概念
      • 2.1 二叉树定义和特性
      • 2.2 二叉树性质
      • 2.3 二叉树的存储
        • 2.3.1 顺序存储
        • 2.3.2 链式存储
    • 3.二叉树的遍历
      • 3.1 先序遍历
      • 3.2 中序遍历
      • 3.3 后序遍历
      • 3.4 层序遍历
      • 3.5 由遍历序列构造二叉树
        • A.前序+中序遍历序列
        • B.后序+中序遍历序列
        • C.层序+中序遍历序列
    • *完整代码 二叉树

1.树的基本概念

  • 定义

    • 空树:结点数为0的树
    • 非空树:有且仅有一个根结点
    • 叶子结点:没有后继的结点(终端结点)
    • 分支节点:有后继的结点(非终端结点)
    • 根节点没有前驱,其他任何一个结点都有且仅有一个前驱
  • 基本术语

    • 1.度

      结点的度:树中一个结点的孩子个数。

      树的度:树中结点的最大度数。

    • 2.结点的深度(层次)

      从上往下数,在第几层。

    • 3.结点的高度

      从下往上数,在第几层。

    • 4.树的高度(深度)

      总共多少层。

    • 5.有序树和无序树

      有序树:树中各子树从左至右是有次序的,不能互换。

      无序树:子树无次序,可以互换。

    • 6.森林

      森林是m棵互不相交的树的集合

  • 树的性质

    • 1.结点数=总度数+1

      度数即孩子的个数,加一的那个为根结点

    • 2.度为m的树、m叉树的区别

    • 3.度为m的树第i层至多有 m i − 1 m^{i-1} mi1个结点

      ​ m叉树第i层至多有 m i − 1 m^{i-1} mi1个结点

    • 4.高度为h的m叉树至多有 m h − 1 m − 1 \frac{m^h-1}{m-1} m1mh1个结点

    • 5.高度为h的m叉树至少有h个结点

      高度为h、度为m的树至少有h+m-1个结点

    • 6.具有n个结点的m叉树的最小高度为 ⌈ l o g m ( n ( m − 1 ) + 1 ) ⌉ \lceil log_m(n(m-1)+1) \rceil logm(n(m1)+1)⌉

      根据不等式求解:

2.二叉树的概念

2.1 二叉树定义和特性

  • 定义

    1.每个结点至多有两棵子树;

    2.左右子树不能颠倒(二叉树是有序树)。

  • 特殊二叉树

    • 1.满二叉树

      一棵高度为h,且含有 2 h − 1 2^h-1 2h1个结点的二叉树。

      特点:

      (1)只有最后一层有叶子结点;

      (2)不存在度为1的结点;

      (3)按层序从1开始编号,结点i的左孩子为 2 i 2i 2i,右孩子为 2 i + 1 2i+1 2i+1;结点i的父节点为 ⌊ i / 2 ⌋ \lfloor i/2 \rfloor i/2

    • 2.完全二叉树

      当且仅当其每个结点都与高度为h的满二叉树中编号为1~n的结点一一对应时,称为完全二叉树。

      即 在满二叉树的基础上,最后一层可以不满。

      特点:

      (1)只有最后两层可能有叶子结点;

      (2)最多只有一个度为1的结点;

      (3)和满二叉树相同:按层序从1开始编号,结点i的左孩子为 2 i 2i 2i,右孩子为 2 i + 1 2i+1 2i+1;结点i的父节点为 ⌊ i / 2 ⌋ \lfloor i/2 \rfloor i/2

      (4) i < ⌊ n / 2 ⌋ i<\lfloor n/2 \rfloor i<n/2为分支结点, i > ⌊ n / 2 ⌋ i>\lfloor n/2 \rfloor i>n/2为叶子结点。

      (5)如果一个结点只有一个孩子,那一定是左孩子不是右孩子。

    • 3.二叉排列树

      若一棵二叉树满足:左子树的元素<根节点<右子树

    • 4.平衡二叉树

      树上任一结点的左子树和右子树的深度之差不超过1。

2.2 二叉树性质

  • 1.设非空二叉树中度为0,1,2的结点个数分别为 n 0 、 n 1 、 n 2 n_0、n_1、n_2 n0n1n2,则 n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1。(叶子结点比二分支结点多一个)

    推导:

  • 2.二叉树第i层至多有 2 i − 1 2^{i-1} 2i1个结点

    ​ (m叉树第i层至多有 m i − 1 m^{i-1} mi1个结点)

  • 3.高度为h的二叉树至多有 2 h − 1 2^h-1 2h1个结点(满二叉树)

  • 4.对于完全二叉树,具有n个结点的完全二叉树的高度h为 ⌈ l o g 2 ( n + 1 ) ⌉ \lceil log_2(n+1) \rceil log2(n+1)⌉ ⌊ l o g 2 n ⌋ + 1 \lfloor log_2n \rfloor+1 log2n+1

    推导:
    在这里插入图片描述

  • 5.对于完全二叉树,可以由结点数n推出度为0,1,2的结点个数为 n 0 、 n 1 、 n 2 n_0、n_1、n_2 n0n1n2,完全二叉树最多只有一个度为1的结点,即

    n 1 = 0 或 1 n_1=0或1 n1=01

    n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1 —> n 0 + n 2 n_0+n_2 n0+n2一定是奇数

    ====》

    若完全二叉树有2k个(偶数)个结点,则必有 n 1 = 1 , n 0 = k , n 2 = k − 1 n_1=1,n_0=k,n_2=k-1 n1=1,n0=k,n2=k1

    若完全二叉树有2k-1个(奇数)个结点,则必有 n 1 = 0 , n 0 = k , n 2 = k − 1 n_1=0,n_0=k,n_2=k-1 n1=0,n0=k,n2=k1

2.3 二叉树的存储

2.3.1 顺序存储
  • 结构体

    struct TreeNode{ElemType value; //结点中的数据元素bool isEmpty; //结点是否为空
    };
    
  • 用数组定义,按从上至下(一层层)、从左至右的顺序依次存储完全二叉树中各个结点

    TreeNode t[MaxSize];
    
  • 初始化时标记所有结点为空

    for(int i=0;i<MaxSize;i++){t[i].isEmpty=true;
    }
    

  • 完全二叉树 顺序存储后的几个基本操作

    • i的左孩子 —— 2 i 2i 2i
    • i的右孩子 —— 2 i + 1 2i+1 2i+1
    • i的父节点 —— ⌊ i / 2 ⌋ \lfloor i/2 \rfloor i/2
    • i所在层次 —— ⌈ l o g 2 ( n + 1 ) ⌉ \lceil log_2(n+1) \rceil log2(n+1)⌉ ⌊ l o g 2 n ⌋ + 1 \lfloor log_2n \rfloor+1 log2n+1
  • 若完全二叉树中共有n个结点,则

    • 判断i是否有左孩子?

      2 i < = n 2i<=n 2i<=n

    • 判断i是否有右孩子?

      2 i + 1 < = n 2i+1<=n 2i+1<=n

    • 判断i是否是叶子/分支结点?

      i > ⌊ n / 2 ⌋ i> \lfloor n/2 \rfloor i>n/2

  • 非完全二叉树中,,把二叉树的结点编号与完全二叉树的对应起来:

    • 这种存储方式会浪费很多空间。

    • 最坏情况:高度为h且只有h个结点的单支树(所有结点只有右孩子),也至少需要 2 h − 1 2^h-1 2h1个存储单元。

    ∴ 二叉树的顺序存储只适合存储完全二叉树。

2.3.2 链式存储
  • 结构体

    typedef struct BiTNode{ElemType data;struct BiTNode *lchild,*rchild; //每个结点都有左右两个孩子的指针struct BiTNode *parent; //可不加父指针
    }BiTNode,*BiTree;
    
  • 注:n个结点的二叉链表共有n+1个空链域:

3.二叉树的遍历

  • 遍历:按照某种次序把所有结点都访问一次。

  • 分类

    先序遍历:左右(NLR)

    中序遍历:左右(LNR)

    后序遍历:左右(LRN)

  • 手算方法:分支节点逐层展开法

  • 复杂度

    • 时间复杂度:

      ∵ 每个结点都访问一次且只访问一次,

      ∴ 时间复杂度 O ( n ) O(n) O(n)

    • 空间复杂度:

      ∵ 最坏情况下,二叉树是有n个结点且深度为n的单支树,计算机中递归实现用栈存储,

      ∴ 空间复杂度为 O ( n ) O(n) O(n)

3.1 先序遍历

  • 先序遍历过程:左右(NLR)

    1.若二叉树为空,什么都不做;

    2.若二叉树非空:

    (1)访问根结点;

    ​ (2)先序遍历左子树;

    ​ (3)先序遍历右子树。

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

3.2 中序遍历

  • 中序遍历过程:左右(LNR)

    1.若二叉树为空,什么都不做;

    2.若二叉树非空:

    ​ (1)先序遍历左子树;

    (2)访问根结点;

    ​ (3)先序遍历右子树。

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

3.3 后序遍历

  • 后序遍历过程:左右(LRN)

    1.若二叉树为空,什么都不做;

    2.若二叉树非空:

    ​ (1)先序遍历左子树;

    ​ (2)先序遍历右子树。

    (3)访问根结点。

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

3.4 层序遍历

  • 算法思想

    • 层序遍历 就是从上往下一层一层访问

    1.初始化一个辅助队列;

    2.根结点入队;

    3.若队列非空,则队头结点出队,访问该节点,并将其左右孩子插入队尾;

    4.重复第三次直到队列空。

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); //右孩子入队}
}
  • 补充代码:链式队列结点

    typedef struct LinkNode{BiTNode *data;struct LinkNode *next;
    }LinkNode;typedef struct{LinkNode *front,*rear; //队头队尾指针
    }LinkQueue;
    

3.5 由遍历序列构造二叉树

  • 若只给出一棵二叉树的 前中后层 序遍历中的一种,不能唯一确定一棵二叉树。

    • 构造的三种方法:
      1. 前序+中序
      2. 后序+中序
      3. 层序+中序
A.前序+中序遍历序列
  • 步骤

    1.找到根结点:先序序列第一个结点==根节点;

    2.在中序序列中找到根结点,根据根结点把序列分成:左子树+根结点+右子树;

    3.左右子树分别重复第1、2步。

  • 方法:根据根结点推,前序遍历序列的第一个就是根节点。

B.后序+中序遍历序列
  • 方法:根据根结点推,后序遍历序列的最后一个一个就是根结点。

C.层序+中序遍历序列
  • 方法:

*完整代码 二叉树

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>#define ElemType int// 二叉树链式存储结构
typedef struct BiTNode {ElemType data; // 数据域struct BiTNode *lchild, *rchild; // 左右孩子指针
} BiTNode, *BiTree;// 访问结点
void visit(BiTNode *Node) {printf("%d ", Node->data);
}// 先序遍历:根左右
void PreOrder(BiTree T) {if (T != NULL) {visit(T); // 访问根结点PreOrder(T->lchild);PreOrder(T->rchild);}
}// 中序遍历:左根右
void InOrder(BiTree T) {if (T != NULL) {InOrder(T->lchild);visit(T);InOrder(T->rchild);}
}// 后序遍历:左右根
void PostOrder(BiTree T) {if (T != NULL) {PostOrder(T->lchild);PostOrder(T->rchild);visit(T);}
}// 层序遍历:借助队列
// 链式队列结点
typedef struct LinkNode {BiTNode *data;struct LinkNode *next;
} LinkNode;typedef struct {LinkNode *front, *rear;
} LinkQueue;void InitQueue(LinkQueue *Q) {Q->front = Q->rear = (LinkNode *)malloc(sizeof(LinkNode));Q->front->next = NULL; // 将头节点的 next 指针初始化为 NULL
}void Push(LinkQueue *Q, BiTNode *x) {LinkNode *p = (LinkNode *)malloc(sizeof(LinkNode));p->data = x;p->next = NULL;Q->rear->next = p;Q->rear = p;
}void Pop(LinkQueue *Q, BiTNode **x) {LinkNode *p = Q->front->next;*x = p->data;Q->front->next = p->next;if (Q->rear == p) Q->rear = Q->front;free(p);
}bool Empty(LinkQueue *Q) { // 修改函数参数if (Q->rear == Q->front)return true;elsereturn false;
}void LevelOrder(BiTree T) {LinkQueue Q;InitQueue(&Q);BiTree p;Push(&Q, T); // 根结点入队while (!Empty(&Q)) {Pop(&Q, &p); // 队头结点出队visit(p);// 若左孩子不为空,则入队if (p->lchild != NULL) {Push(&Q, p->lchild);}// 若右孩子不为空,则入队if (p->rchild != NULL) {Push(&Q, p->rchild);}}
}// 中序加先序遍历构造二叉树
BiTree InPreCreateBiTree(ElemType in[], ElemType pre[], int inStart, int inEnd, int preStart, int preEnd) {if (inStart > inEnd || preStart > preEnd) return NULL;BiTree root = (BiTNode *)malloc(sizeof(BiTNode));root->data = pre[preStart]; // 根节点为先序遍历的第一个节点int rootIndex;for (rootIndex = inStart; rootIndex <= inEnd; rootIndex++) {if (in[rootIndex] == pre[preStart]) // 在中序遍历中找到根节点位置break;}int leftLength = rootIndex - inStart; // 左子树长度root->lchild = InPreCreateBiTree(in, pre, inStart, rootIndex - 1, preStart + 1, preStart + leftLength); // 递归构造左子树root->rchild = InPreCreateBiTree(in, pre, rootIndex + 1, inEnd, preStart + leftLength + 1, preEnd); // 递归构造右子树return root;
}// 中序加后序遍历构造二叉树
BiTree InPostCreateBiTree(ElemType in[], ElemType post[], int inStart, int inEnd, int postStart, int postEnd) {if (inStart > inEnd || postStart > postEnd) return NULL;BiTree root = (BiTNode *)malloc(sizeof(BiTNode));root->data = post[postEnd]; // 根节点为后序遍历的最后一个节点int rootIndex;for (rootIndex = inStart; rootIndex <= inEnd; rootIndex++) {if (in[rootIndex] == post[postEnd]) // 在中序遍历中找到根节点位置break;}int leftLength = rootIndex - inStart; // 左子树长度root->lchild = InPostCreateBiTree(in, post, inStart, rootIndex - 1, postStart, postStart + leftLength - 1); // 递归构造左子树root->rchild = InPostCreateBiTree(in, post, rootIndex + 1, inEnd, postStart + leftLength, postEnd - 1); // 递归构造右子树return root;
}int main() {ElemType in[] = {4, 7, 2, 1, 5, 3, 8, 6}; // 中序序列ElemType pre[] = {1, 2, 4, 7, 3, 5, 6, 8}; // 先序序列ElemType post[] = {7, 4, 2, 5, 8, 6, 3, 1}; // 后序序列ElemType level[] = {1, 2, 3, 4, 5, 6, 7, 8}; // 层序序列BiTree rootInPre = InPreCreateBiTree(in, pre, 0, 7, 0, 7);BiTree rootInPost = InPostCreateBiTree(in, post, 0, 7, 0, 7);// 中序加层序构造二叉树需要给定中序和层序序列,由于层序序列不是标准的输入序列,无法直接用现有函数构造,暂不实现printf("InPreOrder: ");PreOrder(rootInPre);printf("\n");printf("InOrder: ");InOrder(rootInPre);printf("\n");printf("InPostOrder: ");PostOrder(rootInPost);printf("\n");printf("LevelOrder: ");LevelOrder(rootInPre);printf("\n");return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/644030.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

G2024-04-24 开源项目日报 Top10

根据Github Trendings的统计,今日(2024-04-24统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目5TypeScript项目3PowerShell项目1C++项目1非开发语言项目1LLaMA模型的C/C++移植 创建周期:306 天开发语言:C, C++协议类型:MIT Li…

【嵌入式】Arduino IDE + ESP32开发环境配置

一 背景说明 最近想捣鼓一下ESP32的集成芯片&#xff0c;比较了一下&#xff0c;选择Arduino IDE并添加ESP32支持库的方式来开发&#xff0c;下面记录一下安装过程以及安装过程中遇到的坑。 二 下载准备 【1】Arduino IDE ESP32支持一键安装包&#xff08;非常推荐&#xff0…

消息队列 Kafka 入门篇(二) -- 安装启动与可视化工具

一、Windows 10 环境安装 1、下载与解压 首先&#xff0c;访问Apache Kafka的官方下载地址&#xff1a; https://kafka.apache.org/downloads 在本教程中&#xff0c;我们将使用kafka_2.13-2.8.1版本作为示例。下载完成后&#xff0c;解压到您的工作目录的合适位置&#xff…

【动态规划】C++ 子序列问题(递增子序列、数对链、定差子序列、斐波那契子序列...)

文章目录 1. 前言2. 例题最长递增子序列 3. 算法题3.1_摆动序列3.2_最长递增子序列的个数3.3_最长数对链[3.4_ 最长定差子序列](https://leetcode.cn/problems/longest-arithmetic-subsequence-of-given-difference/description/)3.5_最长的斐波那契子序列的长度3.6_最长等差数…

jvm知识点总结(一)

JVM的跨平台 java程序一次编写到处运行。java文件编译生成字节码&#xff0c;jvm将字节码翻译成不同平台的机器码。 JVM的语言无关性 JVM只是识别字节码&#xff0c;和语言是解耦的&#xff0c;很多语言只要编译成字节码&#xff0c;符合规范&#xff0c;就能在JVM里运行&am…

小程序AI智能名片商城系统直连:打造用户与企业无缝对接的新时代!

在高度不确定性的商业环境中&#xff0c;企业如何快速响应市场变化&#xff0c;实现与用户的零距离接触&#xff1f;答案就是——小程序AI智能名片商城系统直连&#xff01;这一创新工具不仅为企业打开了与用户直接连接的大门&#xff0c;更为企业提供了持续收集用户反馈、快速…

基于Python+Selenium+Pytest的Dockerfile如何写

使用 Dockerfile 部署 Python 应用程序与 Selenium 测试 在本文中&#xff0c;我们将介绍如何使用 Dockerfile 部署一个 Python 应用程序&#xff0c;同时利用 Selenium 进行自动化测试。我们将使用官方的 Python 运行时作为父镜像&#xff0c;并在其中安装所需的依赖项和工具…

3 命名实体识别调优化

能走到这里说明你对模型微调有了一个基本的认识。那么开始一段命名实体的任务过程&#xff0c;下面使用huggingface官网的数据。 1 准备模型 下面的模型自己选择一个吧&#xff0c;我的内存太第一个模型跑不了。 https://huggingface.co/ckiplab/bert-base-chinese-ner/tree…

stack、queue(priority_queue)的模拟实现和deque的简单介绍

stack和queue(priority_queue) 1. 容器适配器 适配器(Adapter)&#xff1a;一种用来修饰容器(Containers)或仿函数(Functors)或迭代器(Iterator)接口的东西。 适配器是一种设计模式&#xff0c;该模式将一个类的接口转换成客户希望的另外一个接口。 现实中拿插座来说&#xf…

react引入iconfont的svg图标

react引入iconfont的svg图标 本文目录 react引入iconfont的svg图标普通图标通过link引入css组件内引入css使用 svg图标通过script引入js组件内引入js使用 通过封装组件自定义封装组件中调用 通过antd封装使用 普通图标 通过link引入css <link rel"stylesheet" h…

无门槛做数据可视化,真的假的?

做数据可视化确实没有太高的门槛&#xff0c;不管是企业还是个人都能找到适合自己的BI数据可视化工具&#xff0c;通过点击、拖拉的操作实现自主分析、智能分析、数据可视化&#xff0c;还可实现大屏可视化、移动端数据可视化分析&#xff0c;实现实施监控数据、日常运营决策、…

验证 python解释器是否安装成功

一. 简介 前一篇文章学习了下载并安装 python解释器&#xff0c;文章如下&#xff1a; windows系统下python解释器安装-CSDN博客 本文验证 python解释器是否安装成功。 二. 验证 python解释器是否安装成功 1. 首先&#xff0c;打开 Windows系统的 "cmd" 界面。…