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

树的概念

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

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

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

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

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

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

关于树的一些相关术语

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

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

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

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

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

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

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

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

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

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

有根树:根是固定的

无根树:根是不固定的

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

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

树的存储

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

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

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

用vector数组实现孩子表示法

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

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

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

cpp 复制代码
#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

存储时候的代码

cpp 复制代码
#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的两个孩子

实现完整代码如下

cpp 复制代码
#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

cpp 复制代码
#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

cpp 复制代码
#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 接下来也是这种操作,直到队列为空的时候我们遍历完毕

下面实现我们的代码

cpp 复制代码
#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

cpp 复制代码
#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;
}
相关推荐
埃菲尔铁塔_CV算法3 小时前
双线性插值算法:原理、实现、优化及在图像处理和多领域中的广泛应用与发展趋势(二)
c++·人工智能·算法·机器学习·计算机视觉
叫我龙翔3 小时前
【算法日记】从零开始认识动态规划(一)
c++·算法·动态规划·代理模式
AC100AC3 小时前
[NOIP2007 提高组] 矩阵取数游戏
算法·游戏·矩阵
深度混淆3 小时前
C#,图论与图算法,输出无向图“欧拉路径”的弗勒里(Fleury Algorithm)算法和源程序
算法·图论
大草原的小灰灰3 小时前
C++ STL之容器介绍(vector、list、set、map)
数据结构·c++·算法
小刘|4 小时前
数据结构的插入与删除
java·数据结构·算法
廖显东-ShirDon 讲编程5 小时前
《零基础Go语言算法实战》【题目 2-22】Go 调度器优先调度问题
算法·程序员·go语言·web编程·go web
Lulsj5 小时前
代码随想录day24 | 贪心算法理论基础 leetcode 455.分发饼干 376.摆动序列 53. 最大子序和
算法·leetcode·贪心算法
KuaCpp5 小时前
算法初学者(图的存储)链式前向星
c++·算法
Cosmoshhhyyy5 小时前
LeetCode:2270. 分割数组的方案数(遍历 Java)
java·算法·leetcode