【算法通关指南:数据结构与算法篇】树形结构遍历指南:DFS 递归深搜与 BFS 队列广搜实战解析

🔥小龙报:个人主页

🎬作者简介:C++研发,嵌入式,机器人方向学习者

❄️个人专栏:《算法通关指南》
永远相信美好的事情即将发生

文章目录

  • 前言
  • 一、深度优先遍历-DFS
    • [1.1 vector数组](#1.1 vector数组)
    • [1.2 链式前向星](#1.2 链式前向星)
    • [1.3 复杂度分析](#1.3 复杂度分析)
      • [1.3.1 时间复杂度](#1.3.1 时间复杂度)
      • [1.3.2 空间复杂度](#1.3.2 空间复杂度)
  • 二、宽度优先遍历-BFS
    • [2.1 vector数组](#2.1 vector数组)
    • [2.2 链式前向星](#2.2 链式前向星)
    • [2.3 复杂度分析](#2.3 复杂度分析)
      • [2.3.1 时间复杂度](#2.3.1 时间复杂度)
      • [2.3.2 空间复杂度](#2.3.2 空间复杂度)
  • 总结与每日励志

前言

树的遍历就是不重不漏的将树中所有的点都扫描⼀遍

在之前学过的线性结构中,遍历就很容易,直接从前往后扫描⼀遍即可。但是在树形结构中,如果不按照⼀定的规则遍历,就会漏掉或者重复遍历⼀些结点。因此,在树形结构中,要按照⼀定规则去遍历。常用的遍历方式有两种,一种是深度优先遍历(DFS),另⼀种是宽度优先遍历(BFS)

我们将以一道例题讲解这些遍历方法:

树的逻辑图

一、深度优先遍历-DFS

深度优先遍历,英文缩写为DFS,全称是DepthFirstSearch,中文名是深度优先搜索,是一种用于遍历或搜索树或图的算法。所谓深度优先,就是说每次都尝试向更深的节点走,也就是⼀条路走到黑

具体流程:

(1) 从根节点出发,依次遍历每⼀棵子树;

(2) 遍历子树的时候,重复第一步。

因此,深度优先遍历是⼀种递归形式的遍历可以用递归来实现

注:存储树结构的时候,会把相邻的所有结点都存下来,这样在扫描子树的时候会直接扫描到上一层,这不是我们想要的结果。

因此,需要一个数组来标记,哪些结点已经访问过,接下来的时候,就不去遍历那些点。

1.1 vector数组

c 复制代码
#include <iostream>
#include <vector>
using namespace std;
const int N = 1e5 + 10;
vector<int> edges[N];   // edges[i] 保存着i号结点相连的所有点
bool st[N];  // 标记当前结点是否已经被遍历过

// 当前遍历到u这棵⼦树
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;
}

运行结果:

1.2 链式前向星

c 复制代码
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int h[N],ne[N * 2], e[N * 2],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 i = h[u]; i; i = ne[i])
	{
		int v = e[i];
		if (!st[v])
			dfs(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;
}

运行结果:

1.3 复杂度分析

1.3.1 时间复杂度

简单估计一下,在 遍历的整个过程中,会把树中所有的边扫描量两遍。边的数量为n - 1 故时间复杂度为O(N)。

1.3.2 空间复杂度

最差情况下,结点个数为n的树,高度也是n ,也就是变成一个链表。此时递归的深度也是n,故一个时间复杂度O(N);

二、宽度优先遍历-BFS

宽度优先遍历,英文缩写为BFS,全称是BreadthFirstSearch,也叫广度优先遍历。也是⼀种用于遍历或搜索树或图的算法。
所谓宽度优先。就是每次都尝试访问同⼀层的节点。如果同⼀层都访问完 了,再访问下⼀层。

算法过程可以看做是在树和图中,在起点放上一个细菌,每秒向周围的那些干净的位置扩散⼀层,直到把所有位置都感染
具体实现方式借助队列

(1)初始化一个队列;

(2)根节点入队,同时标记该节点已经入队;

(3)当队列不为空时,拿出队头元素,访问,然后将队头元素的所有孩子入队,同时打上标记。

(4)重复过程,直到队列为空。

2.1 vector数组

c 复制代码
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
const int N = 1e5 + 10;
vector<int> edges[N];
queue < int> q;
bool st[N];

void bfs(int u)
{
	q.push(u);
	st[u] = true;

	while (q.size())
	{
		auto u = q.front();
		cout << u << " ";
		q.pop();

		for (auto v : edges[u])
		{
			if (!st[v])
			{
				q.push(v);
				st[v] = 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(1);
	return 0;
}

运行结果:

2.2 链式前向星

c 复制代码
#include <iostream>
#include <queue>
using namespace std;
const int N = 1e5 + 10;
queue<int> q;
int id, h[N], e[N * 2], ne[N * 2];
bool st[N];  //st[i]表示那些节点已经入队

void add(int a, int b)
{
	id++;
	e[id] = b;
	ne[id] = h[a];
	h[a] = id;
}

void bfs(int u)
{
	q.push(u);
	st[u] = true;

	while (q.size())
	{
		auto v = q.front();
		cout << v << " ";
		q.pop();

		// 让孩子入队
		for (int i = h[v]; i; i = ne[i])
		{
			int x = e[i];
			if (!st[x])
			{
				q.push(x);
				st[x] = 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(1);
	return 0;
}

运行结果:

2.3 复杂度分析

2.3.1 时间复杂度

有结点只会入队一次,然后出队一次,因此时间复杂度为空间复杂度:O(N)

2.3.2 空间复杂度

最坏情况下,所有的非根结点都在同⼀层,此时队列里面最多有n - 1个元素,故空间复杂度为O(N);

总结与每日励志

本文介绍了树遍历的两种主要方法:深度优先遍历(DFS)和宽度优先遍历(BFS)。DFS采用递归方式,每次尝试向更深节点遍历;BFS借助队列实现,按层次遍历节点。两种方法的时间复杂度均为O(N),空间复杂度在最坏情况下也是O(N)。文章通过示例代码展示了两种遍历的具体实现方式,包括vector数组和链式前向星两种存储结构。最后以"永远相信美好的事情即将发生"作为励志结语。

相关推荐
小白菜又菜7 小时前
Leetcode 3432. Count Partitions with Even Sum Difference
算法·leetcode
wuhen_n8 小时前
LeetCode -- 15. 三数之和(中等)
前端·javascript·算法·leetcode
sin_hielo9 小时前
leetcode 2483
数据结构·算法·leetcode
sevenez9 小时前
Vibe Coding 实战笔记:从“修好了C坏了AB”到企业级数据库架构重构
c语言·笔记·数据库架构
Xの哲學9 小时前
Linux多级时间轮:高精度定时器的艺术与科学
linux·服务器·网络·算法·边缘计算
大头流矢9 小时前
归并排序与计数排序详解
数据结构·算法·排序算法
阿闽ooo10 小时前
外观模式:从家庭电源控制看“简化接口“的设计智慧
c++·设计模式·外观模式
油泼辣子多加10 小时前
【信创】算法开发适配
人工智能·深度学习·算法·机器学习
一路往蓝-Anbo10 小时前
【第20期】延时的艺术:HAL_Delay vs vTaskDelay
c语言·数据结构·stm32·单片机·嵌入式硬件
Aaron158810 小时前
AD9084和Versal RF系列具体应用案例对比分析
嵌入式硬件·算法·fpga开发·硬件架构·硬件工程·信号处理·基带工程