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

第七章 二叉树

二叉树

二叉树的定义

二叉树是一种特殊的树型结构,它的特点是每个结点最多只有两颗子树(即二叉树中不存在度大于2的结点),且二叉树的子树有左右之分,其次序不能任意颠倒。

⼆叉的意思是这种树的每⼀个结点最多只有两个孩⼦结点。注意这⾥是多有两个孩⼦,也可能没有孩⼦或者是只有⼀个孩⼦。

⼆叉树结点的两个孩⼦,⼀个被称为左孩⼦,⼀个被称为右孩⼦。其顺序是固定的,就像⼈的左⼿和右⼿,不能颠倒混淆。

特殊的二叉树

满二叉树

一棵二叉树所有非叶子结点都存在左右孩子,并且所有叶子结点都在同一层级上,那么这棵树就是满二叉树。

完全二叉树

在满二叉树的基础上,从最底层最右边开始,连续去掉若干个结点,那么就形成了完全二叉树。

除了上述两种二叉树,还存在堆、二叉排序树等特殊的二叉树。

二叉树的存储

根据定义,二叉树是特殊类型的树,因此可以使用树的存储方式来存储二叉树。在此基础上,标记左右子树即可。

  • 比如使用vector数组存储时,先尾插左孩子再尾插右孩子。这样输出时,就可以按照左孩子、右孩子的顺序输出。
  • 比如使用链式前向星存储时,先头插左孩子再头插右孩子。注意,此时将会按照右孩子、左孩子的顺序输出。

但是,由于二叉树结构的特殊性,我们可以使用更符合其特性的存储方式:顺序存储链式存储

顺序存储

顺序存储是指使用数组来存储二叉树。对于完全二叉树,我们可以使用数组来存储。并且此时可以利用下标来计算左右孩子和父亲:

如果父存在,其下标为i/2;
如果左孩子存在,其下标为2i;
如果右孩子存在,其下标为2
i+1;

当然,非完全二叉树也可以使用顺序存储,但是此时需要补全空结点,使其变为完全二叉树。

再来考虑其最差情况:如果是右斜树即每一个结点只有右孩子,存在4个结点,那么此时需要使用大小为24 - 1的数组来存储。会造成存储空间的极大浪费。因此,顺序存储一般只适用于完全二叉树或者满二叉树。

链式存储

在竞赛中,我们这里的链式存储依然是静态实现

同时,在竞赛中,给定的树结构通常是有编号的,因此我们可以创建两个数组:l[]用于存储左孩子,r[]用于存储右孩子。其中,l[i]表示结点i的左孩子,r[i]表示结点i的右孩子。

注意,这里的编号是指结点的编号,而不是结点的值。

链式存储的优点是可以存储任意形状的二叉树,并且不需要补全空结点。

题目描述

有一个n( n <=106 )个结点的二叉树。给出每个结点的两个孩子结点的编号,建立一颗二叉树(根结点编号为1),求这棵树的深度。

输入描述

第一行一个整数n,表示n个结点。
接下来n行,第i行两个整数l和r,表示第i个结点的左孩子和右孩子的编号。

#include <iostream>
using namespace std;

const int N = 1e6 + 10;

int l[N],r[N];

int main(){
    int n;
    cin >> n;
    for(int i = 1;i <= n;i++){
        cin >> l[i] >> r[i];
    }
    return 0;
}

二叉树的遍历

基于上一章,二叉树同树一样,存在深度优先遍历(DFS)和宽度优先遍历(BFS)两种遍历方式。

深度优先遍历(DFS)

不同于树的深度优先遍历,二叉树的深度优先遍历有三种遍历方式:

  • 前序遍历:先序遍历根结点,再先序遍历左子树,最后先序遍历右子树。
  • 中序遍历:中序遍历左子树,再中序遍历根结点,最后中序遍历右子树。
  • 后序遍历:后序遍历左子树,再后序遍历右子树,最后后序遍历根结点。

简单来说,前序遍历就是按照根、左、右的顺序遍历;中序遍历就是按照左、根、右的顺序遍历;后序遍历就是按照左、右、根的顺序遍历。

再简言之,三种遍历都是从根到子、先左再右的查找顺序之大前提下,前序遍历就是访问到结点就输出;中序遍历先找到并输出左孩子及其子树(子树中依旧先找左孩子及其子树),后输出根结点,最后输出右子树;后序遍历先找到并输出左子树,后找到并输出右子树,最后找全左右孩子及其子树后输出根结点。

另,以上查找孩子及其子树都基于其存在的前提下,注意另外判断,若存在,进行递归查找即可。

题目描述

有一个n( n <=10^6 )个结点的二叉树。给出每个结点的两个孩子结点的编号,建立一颗二叉树(根结点编号为1),求这棵树的深度。

输入描述

第一行一个整数n,表示n个结点。
接下来n行,第i行两个整数l和r,表示第i个结点的左孩子和右孩子的编号。

测试用例

测试⼀:
4
 0 2
 3 4
 0 0
 0 0
测试⼆:
2
 2 0
 0 0
测试三:
3
 2 3
 0 0
 0 0
测试四:
7
 2 3
 0 4
 5 6
 0 0
 0 0
 7 0
 0 0

代码实现

#include <iostream>
using namespace std;

const int N = 1e6 + 10;

int l[N],r[N];

/*先序遍历*/
void dfsx(int u){
   cout << u << " ";
   if(l[u]) dfsx(l[u]);
   if(r[u]) dfsx(r[u]);
}

/*中序遍历*/
void dfsz(int u){
   if(l[u]) dfsz(l[u]);
   cout << u << " ";
   if(r[u]) dfsz(r[u]);
}

/*后序遍历*/
void dfsh(int u){
   if(l[u]) dfsh(l[u]);
   if(r[u]) dfsh(r[u]);
   cout << u << " ";  
}

int main(){
    int n;
    cin >> n;
    for(int i = 1;i <= n;i++){
        cin >> l[i] >> r[i];
    }
    dfsx(1);
    cout << endl;
    dfsz(1);
    cout << endl;
    dfsh(1);
    cout << endl;
    return 0;
}

我们注意到,二叉树中不同于一般的树,没有存储父结点的信息,因此在遍历过程中,不需要另外数组进行标记是否访问过。

宽度优先遍历(BFS)

宽度优先遍历与常规树的宽度优先遍历相同,直接利用队列即可。

#include <iostream>
#include <queue>
using namespace std;

const int N = 1e6 + 10;

int l[N],r[N];

void bfs(int u){
    queue<int> q;
    q.push(u);
    while(q.size()){
        int t = q.front();
        q.pop();
        cout << t << " ";
        if(l[t]) q.push(l[t]); 
        if(r[t]) q.push(r[t]);
    }  
}

int main(){
    int n;
    cin >> n;
    for(int i = 1;i <= n;i++){
        cin >> l[i] >> r[i];
    }
    bfs(1);
    cout << endl;
    return 0; 
}

相关文章:

  • 生成式AI系列(二) LLM生成质量改善的方法——RAG检索增强生成
  • Python评估网络脆弱性
  • Redis常问八股(一)
  • Java网络编程,多线程,IO流综合项目一一ChatBoxes
  • 初识Qt · 信号与槽 · 自定义和参数
  • 【轻松学C:编程小白的大冒险】---变量的定义、声明与应用场景 06
  • leetcode日记(86)恢复二叉搜索树
  • 2008-2024年中国手机基站数据/中国移动通信基站数据
  • VSTO(C#)Excel开发2:Excel对象模型和基本操作
  • 2025年渗透测试面试题总结-字某跳动-安全研究实习生(三面)(题目+回答)
  • 40岁开始学Java:控制反转IoC
  • 前端知识点---路由模式-实例模式和单例模式(ts)
  • 【redis】全局命令set、get、keys
  • 【linux网络编程】浏览网页时客户端与服务器之间数据交互的完整过程
  • 根据输入汉字生成带拼音的米字格字帖
  • 对接RAGflow的API接口报错
  • Java本地缓存简单实现,支持SpEL表达式及主动、过期、定时删除
  • 解锁日常养生密码,拥抱健康生活
  • RabbitMQ学习笔记
  • 腾讯云大模型知识引擎LKE+DeepSeek结合工作流升级智能客服
  • 聊城做网站哪里好/百度开发平台
  • 做批手表批发发的网站/抖音seo怎么做
  • 北京顺义去哪找做网站的/重庆网站制作公司哪家好
  • 郫县网站建设/百度快照优化的优势是什么
  • 做指甲的网站/天津百度推广网络科技公司
  • 韶关网站建设第一品牌/如何制作一个网页链接