树的概念
树形结构是一类重要的非线性结构。
从上往下看,每个节点可能有0个或者多个后继;从下往上看,除了根节点,每个节点都有一个前驱。因此我们可以得到一条性质:节点个数 = 边数 + 1。
树的存储
树结构相对于线性结构来说就比较复杂,存储时既要保存值域,也要保存节点与节点之间的关系。树有很多种存储方式,现阶段我们只需掌握孩子表示法即可。 所谓孩子表示法,就是对于每一个节点,只存储所有孩子的信息。如下图所示:
但是,如果这棵树是无根树 (树的根节点未知,父子关系不明确)该如何解决?我们只需要将与该节点相连的所有节点都存储下来。如下图所示:
实现方式一、用vector数组实现
1、创建一个大小足够的vector数组:vector<int> edges[10];注意这里是'[ ]',不是'( )'。
其中edges[i]里面就存着i号节点所有的孩子。
2、 对于i的孩子,直接edges[i].push_back进去即可。
#include <iostream>
#include <vector>
using namespace std;
const int N = 1e5 + 10;
int n;
vector<int> edges[N];
int main()
{
cin >> n;
for(int i = 1; i < n; i++)
{
int a, b;
cin >> a >> b;
egdes[a].push_back(b);
edges[b].push_back(a);
}
return 0;
}
实现方式二、链式前向星
链式前向星的本质就是用链表存储所有的孩子,其中链表使用数组模拟实现的。
1、创建一个足够大的数组h,作为所有节点的哨兵位。
2、创建两个足够大的数组e和ne,一个作为数据域,一个作为指针域。
3、创建一个变量id,标记新节点的存储位置。
4、当x有一个节点y的时候,就把y头插到x的链表中。
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int n;
int id;
int h[N];
int e[N * 2], ne[N * 2]; //在存储的时候,每一条边存储了两遍
void add(int a, int b)
{
id++;
e[id] = b;
ne[id] = h[a];
h[a] = id;
}
int main()
{
cin >> n;
for(int i = 1; i < n; i++)
{
int a, b;
cin >> a >> b;
//a和b之间有一条边
add(a, b);
add(b, a);
}
return 0;
}
树的遍历
树的遍历就是不重不漏地将树中所有的点都扫描一遍。在树形结构中,如果不按一定的规则遍历,就会遗漏或者重复遍历一些节点。因此,在树形结构中,我们常用以下两种规则遍历树,一种是深度优先遍历,一种是宽度优先遍历。 树的遍历是许多算法的基石,一定要重点掌握。
深度优先遍历(DFS)
深度优先遍历,又称深度优先搜索(Depth First Search),是一种用于遍历树或者图的算法。所谓深度优先,就是每次都尝试向更深的节点走,也就是一条路走到黑~
从根节点出发,依次遍历每一棵子树,遍历子树的时候,还是从子树的根节点出发去遍历。因此,深度优先遍历可以用递归来实现。这里要注意一个细节,存储树结构时,我们把相邻的所有节点都存储了下来,这样在扫描子树时会扫描到上一层导致死递归,因此我们需要一个st数组来标记哪些节点已经访问过,接下来进行DFS的时候就不会再去遍历那些节点了。
用vector数组存储树的DFS
#include <iostream>
#include <vector>
using namespace std;
const int N = 1e5 + 10;
int n;
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()
{
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 n;
int id;
int h[N];
int e[N * 2], ne[N * 2];
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()
{
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;
}
宽度优先遍历(BFS)
宽度优先遍历,又称广度优先遍历(Breadth First Search),也是一种用于遍历树或图的算法。所谓宽度优先,就是每次访问同一层的节点,如果这一层都访问完了,就再访问下一层。 这里我们借助队列来实现这一过程。
1、创建一个队列
2、根节点入队,同时标记该节点已经入队
3、若队列不为空,队头节点出队并访问该节点,然后将该点的孩子一次入队
4、重复过程3,直到队列为空
用vector数组存储树的BFS
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
const int N = 1e5 + 10;
int n;
vector<int> edges[N];
bool st[N];
void bfs()
{
queue<int> q;
q.push(1);
st[1] = true;
while(q.size())
{
auto u = q.front();
q.pop();
cout << u << " ";
for(auto v : edges[u])
{
q.push(v);
st[v] = 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(1);
return 0;
}
用链式前向星存储树的BFS
#include <iostream>
#include <queue>
using namespace std;
const int N = 1e5 + 10;
int n;
int id;
int h[N];
int e[N * 2], ne[N * 2];
bool st[N];
void add(int a, int b)
{
id++;
e[id] = b;
ne[id] = h[a];
h[a] = id;
}
void bfs()
{
queue<int> q;
q.push(1);
st[1] = true;
while(q.size())
{
auto u = q.front();
q.pop();
cout << u << " ";
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;
}
小结
关于树的内容到这里已经全部讲解完毕了 ,重难点还是在树的存储与遍历上,需要反复体会和理解。创作不易,还请大家多多点赞支持,下一章二叉树不见不散。