备战蓝桥杯:树的存储与遍历(dfs和bfs)

server/2025/1/17 4:40:29/

树的概念

树的逻辑结构是树形结构,和我们之前的线性结构又不太一样了,是一种一对多的关系

树的结点分为根节点,叶子结点(没有分支的结点) 以及分支结点

从上往下看,每个结点都有0个或多个后继

从下往上看,每个结点除了根结点以外都有一个前驱结点

所以每个结点都有一条边去连接前驱结点,而根结点没有边连接前驱结点,所以结点数=边数+1(除了根结点每个结点都有一条边连接前驱结点,再加上根结点就是结点的个数)

如图,从下往上看,除了根结点,每个结点都有一个前驱结点,也就是对应一条边。所以结点数=边数(9)+1 = 10

关于树的一些相关术语

父结点:直接前驱,根结点没有父结点

孩子结点:直接后继,叶子结点没有直接后继

结点的度:该结点孩子的个数,比如上图A结点的度就是3

树的度:所有结点的度的最大值

树的高度:一共有多少层,图中有四层

两个结点之间的路径:两个结点之间的最短路径

路径长度:两点的路径中,边的个数

有序树:结点的子树顺序按从左到右有序,不能随意更改

无序树:结点的子树顺序是无序的,随便变,没事儿

我们竞赛一般用的都是无序树

有根树:根是固定的

无根树:根是不固定的

(无根树会导致父子关系不明确,我们要把所有清空都存储起来,比如假如a和b之间存在一条边,那我们既要把a存在b的孩子里,也要把b存在a的孩子里)

我们竞赛中用的都是无根树

树的存储

关于树的存储方式有很多,我们本篇文章只学孩子表示法,未来我们的并查集里会学到双亲表示法,竞赛里,我们只需要掌握这两种表示方法就ok了

孩子表示法:对于每个结点,把他所有的孩子全部存起来

因为我们竞赛都是无根树,我们需要把所有情况都考虑到

用vector数组实现孩子表示法

我们竞赛里,一般都是这样给信息

我们首先要创建一个大小充足 的vector的数组,把每个结点的孩子都各自存储在各自的vector里面

如图,我们九个结点,我们就创建一个大小为10的vector数组,下标为0的vector数组我们不用

#include <iostream>
#include <vector>
using namespace std;
const int N = 1e5 + 10;
vector <int> v1[N];
int main()
{int n;cin >> n;}

比如我们看第一个输入的是3和1,那么3和1之间有一条边,我们竞赛都是无根树,所以我们把3当成1的孩子存一次,把1当成3的孩子存一次

1的孩子结点:2,3,4

2的孩子结点:1,5,6

3的孩子结点:1

4的孩子结点:1,7,8,9

5的孩子结点:2

6的孩子结点:2

7,8,9的孩子结点:4

存储时候的代码

#include <iostream>
#include <vector>
using namespace std;
const int N = 1e5 + 10;
vector <int> v1[N];
int main()
{int n;cin >> n;for (int i = 1; i < n; i++){int x, y; cin >> x >> y;v1[x].push_back(y);v1[y].push_back(x);}}

用链式前向星实现孩子表示法

链式前向星就是用链表来存储所有的孩子

我们需要创建一个数组来存储每个结点的一个哨兵位,

然后创建一个哨兵位数组2倍的数组来存储完成e[2*N]和ne[2*N]的数组,还要有id表示存储位置(这里是两倍,因为我们有的结点可能不止一个边,那么就有可能存储两次)

我们实现的时候就是用头插来实现的

比如这个图,我们3和1有一条边,那我们就要把3的哨兵位连接一个1,把1结点的哨兵位连接一个3,代码也就是:id++,e[id] = 1,ne[id]=h[3] h[3] = id,这是把1连接到3的哨兵位,3连接1同理,我们暂且不实现,3和4有一条边,我们把4连接到3的哨兵位的后面,同样是采取头插,id++,e[id]=4,ne[id]=h[3],h[3]=id,这样我们的3结点哨兵位就变成了 头->4->1了,也就是3的两个孩子

实现完整代码如下

#include <iostream>
#include <vector>
using namespace std;
const int N = 1e5 + 10;
int h[N], e[2 * N], ne[2 * N], id;
void add(int x, int y)
{id++;e[id] = x;ne[id] = h[y];h[y] = id;
}
int main()
{int n;cin >> n;for (int i = 1; i < n; i++){int x, y; cin >> x >> y;add(x, y); add(y, x);}
}

树的遍历(DFS和BFS)

DFS,即深度优先遍历,英文叫做Depth First Search

是一种遍历树或者图的一种算法,所谓深度优先遍历,就是每次都尝试往更深的结点走

也就是一条路走到黑

从根结点开始,遍历根结点的孩子,然后再以这个孩子为根遍历它的孩子,所以我们可以写成一种递归的形式

vector存储的树进行dfs

#include <iostream>
#include <vector>
using namespace std;
const int N = 1e6 + 10;
vector <int> edges[N];
bool st[N];void dfs(int u)
{cout << u << " ";st[u] = true;for (auto v : edges[u]){if (!st[v])dfs(v);}
}
int main()
{int n;cin >> n;//建树for (int i = 1; i < n; i++){int a, b; cin >> a >> b;edges[a].push_back(b);edges[b].push_back(a);}//深度优先搜索dfs(1);return 0;
}

过程就是不断的遍历根结点的孩子,不断的遍历根结点的孩子,我们来画一下递归过程图

递归的展开图就是一棵树,递归就是对树进行深度搜索

为了更好的理解递归,我们再画一下斐波那契数列的递归展开图

可以看到,斐波那契算法的递归图画出来也是一棵树

链式前向星存储的树进行dfs

#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int h[N], e[2*N], ne[2*N], id;
bool st[N];
void add(int a, int b)
{id++;e[id] = b;ne[id] = h[a];h[a] = id;
}
void dfs(int u)
{cout << u << " ";st[u] = true;for (int v = h[u]; v; v = ne[v]){if (!st[e[v]]){dfs(e[v]);}}
}
int main()
{int n;cin >> n;for (int i = 1; i < n; i++){int a, b; cin >> a >> b;add(a, b); add(b, a);}dfs(1);return 0;
}

vector存储的树进行bfs

bfs的话,我们的思路就是用队列,我们先把根结点入队列,然后出队列的时候要把该结点的孩子入队列,直到队列为空,搜索完毕

比如这个,我们先把1入队列,然后把输出1并把1出队列,把3,5,2入队列,然后把输出3并把3出队列,把3的孩子7,10带进去,

出5,把4带进去,出2把11带进去,这时候我们的队列里就是7,10,4,11了 我们输出了1,3,5,2 接下来也是这种操作,直到队列为空的时候我们遍历完毕

下面实现我们的代码

#include <iostream>
#include <vector>
#include <queue>
using namespace std;
const int N = 1e5 + 10;
queue <int> q;
vector <int> edges[N];
bool st[N];
void bfs()
{q.push(1); while (q.size()){auto t = q.front();cout << t << " ";st[t] = true;q.pop();for (auto e : edges[t]){if (!st[e]){q.push(e);st[e] = true;}}}}
int main()
{int n;cin >> n;for (int i = 1; i < n; i++){int a, b; cin >> a >> b;edges[a].push_back(b);edges[b].push_back(a);}bfs();}

符合我们的树,结束

链式前向星存储的树进行bfs

#include <iostream>
#include <queue>
using namespace std;const int N = 1e5 + 10;
int h[N], ne[2 * N], e[2 * N], id;
bool st[N];
void add(int a,int b)
{id++;e[id] = b;ne[id] = h[a];h[a] = id;
}
queue <int> q;
void bfs()
{q.push(1);while (q.size()){auto t = q.front(); q.pop();cout << t << " ";st[t] = true;for (int i = h[t]; i; i = ne[i]){if (!st[e[i]]){q.push(e[i]);st[e[i]] = true;}}}
}int main()
{int n;cin >> n;for (int i = 1; i < n; i++){int a, b; cin >> a >> b;add(a, b); add(b, a);}bfs();return 0;
}


http://www.ppmy.cn/server/158996.html

相关文章

Lesson 109 A good idea

Lesson 109 A good idea 词汇 idea n. 主意&#xff0c;想法 复数&#xff1a;ideas 用法&#xff1a;口语&#xff1a;Good idea! 好主意&#xff01;       Big idea! 高见&#xff01;好主意&#xff01;       Great idea! 好主意       Bad idea! 坏主…

leetcode79.单词搜索

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 单词必须按照字母顺序&#xff0c;通过相邻的单元格内的字母构成&#xff0c;其中“相邻”单元格是那些水平相邻或垂直相…

mysql 双主双从 + proxysql 代理

环境 主机ipmaster1192.168.233.101master2192.168.233.102slave1192.168.233.103slave2192.168.233.104client192.168.233.105 需求 master1和master2互为主从&#xff0c;slave1是master1的从&#xff0c;slave2是master2的从。 master主机通过proxysql代理实现负载均衡的…

迅为RK3568开发板篇OpenHarmony配置HDF驱动控制LED-新增 topeet子系统-编写 bundle.json文件

bundle.json 文件内容如下所示&#xff1a; 下面是对各个字段的解释&#xff1a; 1. name: "ohos/demos" - 这是组件或项目的名称&#xff0c;这里表示它属于 OHOS&#xff08;OpenHarmony OS&#xff09;生态系统下的一个名为"demos"的组件。 2. descri…

3.flask蓝图使用

构建一个目录结构 user_oper.py from flask import Blueprint, request, session, redirect, render_template import functools # 创建蓝图 user Blueprint(xkj, __name__)DATA_DICT {1: {"name": "张三", "age": 22, "gender": …

《零基础Go语言算法实战》【题目 2-30】并发安全问题

《零基础Go语言算法实战》 【题目 2-30】并发安全问题 请举例说明如何在 Go 语言的 map 中保证并发安全&#xff0c;且需要实现以下接口&#xff1a; type sp interface { Out(key string, val interface{}) } 【解答】 题目中要求并发安全&#xff0c;那么必须用锁&…

Python爬虫-汽车之家各车系周销量榜数据

前言 本文是该专栏的第43篇,后面会持续分享python爬虫干货知识,记得关注。 在本专栏之前,笔者在文章《Python爬虫-汽车之家各车系月销量榜数据》中,有详细介绍,如何爬取“各车系车型的月销量榜单数据”的方法以及完整代码教学教程。 而本文,笔者同样以汽车之家平台为例,…

《DOM NodeList》

《DOM NodeList》 介绍 DOM&#xff08;文档对象模型&#xff09;是HTML和XML文档的编程接口&#xff0c;它允许开发者在JavaScript等编程语言中操作文档的结构、样式和内容。在DOM中&#xff0c;NodeList是一个重要的接口&#xff0c;它表示一个包含节点&#xff08;如元素、…