一.DFS
DFS简称Depth First Search,深度优先搜索。即通过递归,不断向深处搜索。先遍历根结点,逐个遍历每个根节点的子节点,一直走到尽头后,再return原路返回,以此往复。
1.DFS遍历vector构图树
先用vector构出树形图(前文有详细讲解),接着我们进行深度优先搜索。
cpp
void dfs(int x)//顺序从下到上从左往右
{
cout << x << " ";
st[x] = true;
for (auto e : edges[x])
{
if (!st[e])
{
dfs(e);
}
}
}
我们使用一个bool类型的数组st,每当节点被遍历过之后,我们就将他设置为true;紧接着便利edges数组(存储着结点x的子节点数据),若该子节点未被遍历,那么就进行下一轮的深度递归。以此往复,我们就按照从上到下从左往右的方式遍历了树。
下面放上完整代码
cpp
#include <iostream>
#include <vector>
using namespace std;
int n;
const int N = 1e5;
vector<int> edges[N];
bool st[N];
void dfs(int x)//顺序从下到上从左往右
{
cout << x << " ";
st[x] = true;
for (auto e : edges[x])
{
if (!st[e])
{
dfs(e);
}
}
}
int main()
{
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;
}
2.DFS遍历链式结构树
我们用链式结构构造出树(前文有讲解)进行深度搜索。
cpp
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);
}
}
}
先定义的bool类型数组st,每当结点被遍历过之后,就给该点设置为true;紧接着遍历该结点的哨兵位h,通过h我们可以找到该结点的子结点,通过子结点的ne指针域我们可以找到其他与之连接的子结点,从而达到遍历树。在遍历时需要判断该点是否为true(被遍历过)。
下面放上完整代码:
cpp
#include <iostream>
using namespace std;
int n;
const int N = 1e5;
int e[2 * N], ne[2 * N], h[N], id;
bool st[N];
void add(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 a, b;
cin >> a >> b;
add(a, b);
add(b, a);
}
dfs(1);
return 0;
}
由于该结点的存储方式是头插的形式,所以在遍历的时候是按照左中右的形式输出的结果。
3.关于DFS的时间复杂度与空间复杂度
假设一棵树上有n个数,那么他就存在着n-1条边,我们从根节点向下遍历遍历至没有子节点时才返回,所以说每一条边都被遍历了2次,那么就是2*(n-1)次一共,那么时间复杂度就是O(n)。
假设最复杂的情况,这棵树的所有结点都连成一条线,空间复杂度最差的情况下为O(n)。
二.BFS
所谓BFS(Breadth First Search),俗称广度优先搜索。广度优先搜索即对树进行逐层的遍历。
这里我们使用一个队列来实现该方法对树的遍历。
让根结点进入队列,紧接着让该根结点的所有子结点进入队列,接着输出根结点;紧接着对下一个头结点进行操作,让该头结点的所有子结点进入队列,接着输出头结点进行打印,以此往复。
1.BFS遍历vector构图树
我们用vector结构构造树,用BFS遍历。
cpp
void bfs()
{
queue<int> q;
q.push(1);
st[1] = true;
while (q.size())
{
int u = q.front(); q.pop();
cout << u << " ";
for (auto e : edges[u])
{
if (!st[e])//防止打印重复需要额外判断一步
{
q.push(e);//根结点出队列子节点进队列
st[e] = true;
}
}
}
}
同样的我们使用一个bool类型数组来判断结点是否遍历,接着就是对上述过程进行一个模拟实现,当队列中存在数的时候循环继续,第二个for循环是用来遍历该结点的子节点数据,该处需要用一个if语句来判断是否遍历,以及每当遍历完该点数据后都要设置为true。
下面是完整代码:
cpp
#include <queue>
#include <iostream>
#include <vector>
using namespace std;
int n;
const int N = 1e5;
vector<int> edges[N];
bool st[N];
void bfs()
{
queue<int> q;
q.push(1);
st[1] = true;
while (q.size())
{
int u = q.front(); q.pop();
cout << u << " ";
for (auto e : edges[u])
{
if (!st[e])//防止打印重复需要额外判断一步
{
q.push(e);//根结点出队列子节点进队列
st[e] = true;
}
}
}
}
int main()
{
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();
return 0;
}
输出的结果中,我们可以得到一份中左右的层序遍历输出。
2.BFS遍历链式结构树
用链式结构构造出树。
cpp
void bfs()
{
queue<int> q;
q.push(1);
st[1] = true;
while (q.size())
{
int u = q.front(); q.pop();
cout << u << " ";
st[u] = true;
for (int i = h[u]; i; i = ne[i])
{
int v = e[i];
if (!st[v])
{
q.push(v);
st[v] = true;
}
}
}
}
我们使用一个bool类型数组来判断结点是否遍历过,若被遍历过则为true。套入一个循环,若队列中还存在数就继续进行操作,根结点入队,打印根结点并且设置为true。接着通过根结点在h哨兵位存储的信息,找到他的子结点信息,若该子节点未被遍历过就入队列。如此循环往复。
下面是完整代码:
cpp
#include <iostream>
#include <queue>
using namespace std;
int n;
const int N = 1e5;
int e[2 * N], ne[2 * N], h[N], id;
bool st[N];
void add(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())
{
int u = q.front(); q.pop();
cout << u << " ";
st[u] = true;
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 a, b;
cin >> a >> b;
add(a, b);
add(b, a);
}
bfs();
return 0;
}
3.关于BFS的时间复杂度与空间复杂度
假如有n个数,由于每个数只会进队列一次并且出队列一次,所以时间复杂度为O(n)
假如有n个数,在最坏的情况下,所以的子结点都在同一层,那么需要入队n-1个数,空间复杂度为O(n)
三.总结
无论是DFS还是BFS他们的时间复杂度空间复杂度都是O(n),对于这两种方法,我们都是在遍历树的基础上实现的。DFS运用的递归原型也可以理解为树型遍历,BFS使用队列的方式实现效率更高且更直观。
创作不易感谢大家支持,一起加油!