用数组实现树的存储遍历【复习笔记】

embedded/2025/2/28 12:14:52/

1. 的基本概念

1.1 的定义和术语

是由 n(n≥0)个有限节点组成的一个具有层次关系的集合。当 n=0 时,称为。在一棵非空中,有且仅有一个特定的节点称为根节点;其余节点可分为 m 个互不相交的有限集 T1、T2、…、Tm,其中每个集合本身又是一棵,并且称为根结点的子

因此,递归定义的

节点的度:一个节点拥有的子个数称为该节点的度。度为 0 的节点称为叶子节点或终端节点;度不为 0 的节点称为分支节点。

的度中节点的最大度数称为的度。

路径中两个结点之间的路径是由这两个结点之间所经过的结点序列构成的,路径长度为序列中 边的个数

节点的层次:从根节点开始定义,根节点为第 1 层,根的子节点为第 2 层,以此类推,若某节点在第 i 层,则其孩子节点在第 i + 1 层。

的高度或深度中节点的最大层次数称为的高度或深度。

1.2 的种类

有序结点的子按照从左往右的顺序排列,不能更改

无序结点的子没有顺序,可以更改

有根根节点已知

无根根节点未知,都可以是根节点

1.3 的存储

存储时,要保存值域,也要保存结点和结点的关系。存储方式有多种:双亲表示法、孩子表示法、孩子双亲表示法、孩子兄弟表示法......

1.3.1 孩子表示法

将每个结点的孩子信息存储下来(无根中,父子关系不明,都存储)

假设存储时有n条信息,第一条为n个结点,接下来n-1行,每行两个x,y表示x和y间有一条边

1. 使用 vector 数组

​
#include<iostream>
#include<vector>
using namespace std;int n;
const int N = 1e5 + 10;
//创建N个vector数组,edges[i]中保存i号结点的孩子信息
vector<int> edges[N];int main()
{cin >> n;for (int i = 1; i < n; i++){int x, y; cin >> x >> y;//x和y间有一条边edges[x].push_back(y);edges[y].push_back(x);}return 0;
}​

2. 链式前向星(使用数组实现的链表)

链式前向星的本质是用数组来模拟链表

#include<iostream>
using namespace std;int n;
const int N = 1e5 + 10;
//一条边的两个数据存储两遍,所有范围扩大2倍
int e[2 * N], ne[2 * N];
//h[N]表示每个父亲结点的集合
int h[N];
int id;void put(int x, int y)
{//先将y存储进数组id++;e[id] = y;//将y进行头插进入父亲结点后ne[id] = h[x];//头标记移动h[x] = id;
}int main()
{cin >> n;for (int i = 1; i < n; i++){int x, y; cin >> x >> y;put(x, y); put(y, x);}return 0;
}

2. 的遍历

的遍历方式有两种,分别为深度优先遍历宽度优先遍历

2.1 深度优先遍历(DFS)

从根结点出发,依次遍历每一颗子;遍历子时,重复第一步。

由此可以看出,深度优先遍历是一种递归形式

案例:

题目描述:给一棵,共 n 个结点,编号 1~n

输入描述:第一行一个整数 n ,表示 n 个结点

                  接下来 n-1 行,每行两个整数 x,y,表示 x 和 y 间有一条边

1. 在 vector 存储下实现

​
#include<iostream>
using namespace std;
#include<vector>const int N = 1e5 + 10;
int n;
//定义布尔数组进行标记
bool st[N];
vector<int> edges[N];void dfs(int x)
{//先打印该父结点cout << x << " ";//标记为打印过了st[x] = true;//依次遍历子结点for (auto u : edges[x]){//如果没打印过,再次深度优先遍历if (!st[x]) dfs(u);}
}int main()
{cin >> n;for (int i = 1; i < n; i++){int x, y; cin >> x >> y;edges[x].push_back(y);edges[y].push_back(x);}//进行深度优先遍历dfs(1);return 0;
}​

2. 在链式前向星存储下实现

#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int e[2 * N], ne[2 * N];
int h[N];
bool st[N];
int n;
int id;void put(int x, int y)
{id++;e[id] = y;ne[id] = h[x];h[x] = id;
}void dfs(int x)
{cout << x << " ";st[x] = true;//遍历每一个孩子for (int i = h[x]; i; i = ne[i]){//递归int v = e[i];if (!st[v]) dfs(v);}}int main()
{cin >> n;for (int i = 1; i < n; i++){int x, y; cin >> x >> y;put(x, y); put(y, x);}//DFSdfs(1);return 0;
}

dfs要将的边扫描两边,边数量为 n-1 ,所以时间复杂度为O(N)

最坏情况,N个结点的高也为N,此时深度为N,所以空间复杂度为O(N)

2.2 宽度优先遍历(BFS)

每次访问同一层的结点,同一层访问完再访问下一层

可以用队列实现:先初始化一个队列,根节点入队,同时标记;当队列不为空,依次拿出头元素访问,将队头元素的所有孩子入队,标记;重复该过程

案例:

题目描述:给一棵,共 n 个结点,编号 1~n

输入描述:第一行一个整数 n ,表示 n 个结点

                  接下来 n-1 行,每行两个整数 x,y,表示 x 和 y 间有一条边

1.  用 vector 实现储存

#include<queue>
#include<iostream>
#include<vector>
using namespace std;
const int N = 1e5 + 10;
bool st[N];
vector<int> edges[N];
int n;void bfs()
{queue<int> q;q.push(1);st[1] = true;while (q.size()){//父亲出队auto u = q.front(); q.pop();cout << u << " ";//孩子入队for (auto v : edges[u]){if (!st[v]){q.push(v);st[v] = true;}}}
}int main()
{cin >> n;for (int i = 1; i < n; i++){int x, y; cin >> x >> y;edges[x].push_back(y);edges[y].push_back(x);}bfs();return 0;
}

2. 链式前向星储存

#include<iostream>
using namespace std;
#include<queue>
const int N = 1e5 + 10;
int e[2 * N], ne[2 * N];
int h[N];
int n;
int id;
bool st[N];void put(int x, int y)
{id++;e[id] = y;ne[id] = h[x];h[x] = id;
}void bfs()
{queue<int> q;q.push(1);st[1] = true;while (q.size()){auto u = q.front(); q.pop();cout << u << " ";//遍历孩子for (int i = h[u]; i; i = ne[i]){int v = e[i];if (!st[v]){q.push(v);st[v] = true;}}}
}int main()
{cin >> n;for (int i = 1; i < n; i++){int x, y; cin >> x >> y;put(x, y); put(y, x);}bfs();return 0;
}

所有结点入队一次,出队一次,时间复杂度O(N)

最坏情况下,所有非根结点同一层,队列最多 n - 1 个元素,空间复杂度O(N)


http://www.ppmy.cn/embedded/167802.html

相关文章

期权帮|国内期权交易投资人做卖出期权价差交易收取的保证金是单边的还是双向的?

锦鲤三三每日分享期权知识&#xff0c;帮助期权新手及时有效地掌握即市趋势与新资讯&#xff01; 国内期权交易投资人做卖出期权价差交易收取的保证金是单边的还是双向的? 在国内期权交易中&#xff0c;投资人做卖出期权价差交易时收取的保证金通常是单边的&#xff0c;但具…

DeepSeek系统架构的逐层分类拆解分析,从底层基础设施到用户端分发全链路

一、底层基础设施层 1. 硬件服务器集群 算力单元&#xff1a; GPU集群&#xff1a;基于NVIDIA H800/H100 GPU构建&#xff0c;单集群规模超10,000卡&#xff0c;采用NVLink全互联架构实现低延迟通信。国产化支持&#xff1a;适配海光DCU、寒武纪MLU等国产芯片&#xff0c;通过…

【JAVA-数据结构】Lambda表达式

还是老规矩&#xff0c;继续进行&#xff0c;有需要的大家持续关注。 1 背景 Lambda表达式是Java SE 8中一个重要的新特性。lambda表达式允许你通过表达式来代替功能接口。 lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达…

API测试中如何利用Postman和Apipost进行参数编码与加密

在API测试工作中&#xff0c;开发者和测试人员经常需要对请求中的某些参数进行编码或加密&#xff0c;以满足安全性和系统需求。这些操作可以针对单独的字段&#xff0c;也可以涉及整个请求体的复杂计算。为了解决这些需求&#xff0c;Postman与Apipost这两款流行的API测试工具…

C++蓝桥杯基础篇(六)

片头 嗨~小伙伴们&#xff0c;大家好&#xff01;今天我们来一起学习蓝桥杯基础篇&#xff08;六&#xff09;&#xff0c;练习相关的数组习题&#xff0c;准备好了吗&#xff1f;咱们开始咯&#xff01; 第1题 数组的左方区域 这道题&#xff0c;实质上是找规律&#xff0c;…

前端TypeScript 面试题及参考答案

目录 解释 unknown 与 any 的区别,如何安全使用 unknown 类型? 如何用类型守卫处理联合类型变量的方法调用? 实现一个工具类型 Nullable ,使 T 可被赋值为 null/undefined 如何用 keyof 和 in 关键字实现枚举类型到联合类型的转换? 类型断言 as 与尖括号语法有何差异…

Hyperlane:高性能Rust后端框架的革新者

Hyperlane&#xff1a;高性能Rust后端框架的革新者 在当今高并发的互联网环境下&#xff0c;开发者亟需兼具性能与开发效率的工具。Hyperlane作为一款基于Rust语言构建的轻量级HTTP服务器框架&#xff0c;凭借其卓越的设计理念和丰富的功能特性&#xff0c;正在成为构建现代We…

微软推出Office免费版,限制诸多,只能编辑不能保存到本地

易采游戏网2月25日独家消息&#xff1a;微软宣布推出一款免费的Office版本&#xff0c;允许用户进行基础文档编辑操作&#xff0c;但限制颇多&#xff0c;其中最引人关注的是用户无法将文件保存到本地。这一举措引发了广泛讨论&#xff0c;业界人士对其背后的商业策略和用户体验…