2025春新生培训数据结构(树,图)
- 手机
- 2025-09-21 20:03:03

教学目标:
1,清楚什么是树和图,了解基本概念,并且理解其应用场景
2,掌握一种建图(树)方法
3,掌握图的dfs和树的前中后序遍历
例题与习题2025NENU新生培训(树,图)习题
引言恭喜各位!当你学到图和树的内容的时候,已经结束了萌新的状态。
大家想来已经早就听过了关于树和图的传说,但树和图到底是什么呢?又有什么用呢?
大家试想一下,我们现在已知的几种数据结构:栈,队列,链表......这些结构都有一个特点,就是线性的。
这些结构里,从前到后点与点之间的关系是1v1的,而我们不妨思考一下在如下情境中我们该如何存储数据,我们要构造一个这样的情景:
有许多的人,我们知道a比b年龄大,a比c大,d比c大,d比e大......该如何记录这样的信息呢?
如果你能独立想明白的话,我想你已经独立想出了算法中的一大突破,那就是树与图。
我们想一下应该怎么办。是不是本能地想到应该记录一下某个人a和所有与a有关系的人?而这显然一个一维数组我们如果用下标来表示是谁,那么一个值通常是存不下来所有和他有关系的人的。因此我们可以用二维数组来存储,用某一维表示a,然后另一维去存与a有关系的人。
举个例子:
已知有三个人a, b, c, 已知a > b, b > c, a > c
我们可以用一个3 * 3 的数组来记录它,用 1 来表示大于
a b c
a 0 1 1
b 0 0 1
c 0 0 0
这就是一个邻接矩阵, 我们可以从中看出 a 比 b 大, a 比 c大, b 比 c 大
然而邻接矩阵会用节点数目的平方的空间,因此这往往并不适用于节点数目较多而边数较少的情况,所以我们往往会采用另一种方式邻接表,在这种数据结构中,我们记录和每一个顶点有关系的点。
这很方便,以上图为例,如果我们用邻接表的方式,我们所记录的大概是这样:
a : b c
b : c
c :
这同样能够记录图的结构,而消耗的空间很小。
第二种方式又有两种不同的建图方法分别是单链表和动态数组,在讲解建图方式之前,我们不如先来系统地讲述一下图和树的基本概念:
树与图的基本概念 一、图 (Graph)正如我们前面所讲,图是一种由节点(顶点)和边组成的数据结构,通常用于表示复杂的关系。与树不同,图不要求有层次关系,也不一定是连通的。
1. 图的基本概念
顶点 (Vertex):图中的节点。 边 (Edge):连接两个顶点的线,表示它们之间的关系。 有向图 (Directed Graph):边有方向,即从一个顶点指向另一个顶点。
什么样的关系可以由有向图表示?
无向图 (Undirected Graph):边没有方向,表示两个顶点之间的关系是双向的。
什么样的关系可以由无向图表示?
邻接矩阵 (Adjacency Matrix):使用二维数组表示图的结构,适用于稠密图。 邻接表 (Adjacency List):使用链表数组或动态数组表示图的结构,适用于稀疏图。
二、树(Tree)树是一种特殊的图!树是无环的连通图。 树是一种分层结构的非线性数据结构,由节点和边组成。每个树由一个根节点和零个或多个子树组成。
1. 树的基本概念 节点(Node):树中的每个元素。 根节点(Root):树的最顶端节点,没有父节点。 父节点(Parent):一个节点的直接前驱节点。 子节点(Child):一个节点的直接后继节点。 叶节点(Leaf):没有子节点的节点。 高度(Height):树中节点的层次,根节点的高度为0。 深度(Depth):节点到根节点的距离。
2. 树的类型 二叉树 (Binary Tree):每个节点最多有两个子节点,通常称为左子节点和右子节点。
完全二叉树:从根结点到倒数第二层满足完美二叉树,最后一层可以不完全填充,其叶子结点都靠左对齐。
建图建图(树),
我们接下来以这道题为例,从代码的角度来实现一个图的建立,树的建立与图完全一致
一:邻接矩阵建图节点数量为n
首先创建一个n * n 的二维数组A,初始值默认为0
对每一条由u到v的边,A[u][v] = A[v][u] = 1
二:邻接表建图 动态数组:创建一个二维动态数组vector<vector<int>> B(n + 1)
对每一条由u到v的边,B[u].push_back(v), B[v].push_back(u)
链表(较难):这种方法较难,相对来讲比较传统,大家可以自行了解
下面是这道题目的通过代码,希望大家可以先自行尝试解决这道问题
#include <bits/stdc++.h> using namespace std; const int N = 1010, M = 1e5 + 10; // 邻接矩阵 int A[N][N]; // 邻接表 vector<vector<int>> B(N); int main() { int n, m; cin >> n >> m; for (int i = 1; i <= m; i ++ ) { int a, b; cin >> a >> b; // 邻接矩阵存储边 A[a][b] = A[b][a] = 1; // 邻接表存储边 B[a].push_back(b); B[b].push_back(a); } for (int i = 1; i <= n; i ++ ) { for (int j = 1; j <= n; j ++ ) { cout << A[i][j] << " "; } cout << endl; } for (int i = 1; i <= n; i ++ ) { // 一个点相关的边的数量等于这个点的度数 cout << B[i].size() << " "; // 题目中要求从小到大输出 sort(B[i].begin(), B[i].end()); for (auto b : B[i]) { cout << b << " "; } cout << endl; } return 0; } 图(树)的深度优先搜索大家已经学习过在网格图中如何进行搜索,那么在图中该怎么进行搜索呢?我们这里仅讲述dfs,bfs与其类似,大家可以课后自行尝试
在网格图中,我们通常使用上下左右四个方向来进行移动,但是在图和树中,一个点能移动到的位置是所有与其相连的点
我们用邻接表来讲的话,其dfs大概是这样的
// 邻接表 vector<vector<int>> B(N); // u是当前访问的节点,fa是上一个节点 void dfs(int u, int fa) { // b 是相连的点 for (auto b : B[u]) { // 如果不是父节点,就移动过去,这避免了在两个点之间反复横跳 if (b != fa) { dfs(b, u); } } } 二叉树的前中后遍历在前文中,我们如果要去遍历图,遍历的顺序取决于给出边的顺序
举个例子,如果给出12 13 14, 我们会先访问2, 再访问3,再访问4
如果给出的是 13 12 14, 这个时候,顺序就变为了3 2 4
但是在二叉树中,我们知道一个点可以有左子节点和右子节点,如果我们按照某种顺序去访问其左子节点右子节点和其本身,那么就会得到相对来讲比较特殊的遍历顺序
前序遍历:根结点 ---> 左子树 ---> 右子树
中序遍历:左子树---> 根结点 ---> 右子树
后序遍历:左子树 ---> 右子树 ---> 根结点
如果课堂时间有剩余或者大家学有余力的话,我们不如一起来思考一下这些问题:
如何记录二叉树的左子节点和右子节点?
如果用链表去做的话怎么做邻接表?
在博客开头的情境里,能否根据已有信息排列出一个明确的年龄顺序,什么情况下可以?什么情况下不行?
结语关于树与图的知识还有很多,欢迎大家有不懂的知识来问学长学姐,希望大家能够通过思考提高自己的思维能力,算法能力猛猛进步!
感谢阅读!
2025春新生培训数据结构(树,图)由讯客互联手机栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“2025春新生培训数据结构(树,图)”