树的概念
树是一种非线性数据结构,由节点组成,节点之间以边连接。树的定义如下:
-
树由节点(node)组成,节点包含数据元素和指向其子节点的指针。
-
一个节点可以有零个或多个子节点。
-
树中有一个特殊的节点称为根节点(root),它没有父节点,且是树的起始节点。
-
除了根节点外,每个节点都有且仅有一个父节点。
-
树中的节点之间通过边(edge)相连,边表示两个节点之间的关系。
-
树中任意两个节点之间都存在唯一的路径。
树是一种重要的数据结构,常用于表示层次关系、组织结构等。在计算机科学中,树结构被广泛应用于算法设计、数据库系统、编程语言等领域。
树上DFS与BFS
概念
DFS(Depth First Search)和BFS(Breadth First Search)是两种常用的图遍历算法,也可以用于树的遍历。
DFS(深度优先搜索):
-
从根节点开始,沿着一条路径一直往下遍历,直到遇到叶子节点。
-
当无法再继续向下遍历时,回溯到上一个节点,继续遍历其他子节点。
-
可以使用递归或栈来实现DFS算法。
-
DFS适合用于寻找深度优先路径,如寻找树的最大深度、查找路径等。
BFS(广度优先搜索):
-
从根节点开始,先访问根节点,然后依次访问根节点的所有相邻节点。
-
再依次访问这些相邻节点的相邻节点,依此类推,直到遍历完所有节点。
-
可以使用队列来实现BFS算法。
-
BFS适合用于寻找最短路径、层次遍历等。
模板题
猫猫和企鹅 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#include <iostream>
#include<vector>
#include<stack>
#include<queue>
using namespace std;
int DFS(const vector<vector<int>>& edges, int start, int d) {
stack<pair<int, int>> sta;
vector<int> vis(edges.size());
sta.push({ start,0 });
vis[start] = true;
int ans = 0;
while (!sta.empty()) {
int nowp = sta.top().first;
int cnt = sta.top().second;
sta.pop();
if (cnt == d)continue;
for (int next : edges[nowp]) {
if (vis[next]) continue;
sta.push({ next,cnt + 1 });
ans++;
vis[next] = true;
}
}
return ans;
}
int BFS(const vector<vector<int>>& edges, int start,int d) {
queue<int> q;
vector<int> vis(edges.size());
q.push(start);
vis[start] = true;
int ans = 0;
for (int i = 1; i <= d; i++) {
int k = q.size();
while (k--) {
int nowp = q.front();
q.pop();
for (int next : edges[nowp]) {
if (vis[next])continue;
q.push(next);
ans++;
vis[next] = true;
}
}
}
return ans;
}
int main() {
int n, d; cin >> n >> d;
vector<vector<int>> edges(n);
for (int i = 1; i < n; i++) {
int u, v; cin >> u >> v;
--u, --v;
edges[u].push_back(v);
edges[v].push_back(u);
}
//cout << DFS(edges, 0, d) << endl;
cout << BFS(edges, 0, d) << endl;
return 0;
}
树的直径
概念
给定一个n个结点的无根树,边带权。两点之间的唯一路径所包含的所有边权之和可以称作这两点之间的距离。所有点对中最远的两点之间的距离被称作这棵树的直径。定义偏心距ECC(F):树中距路径F最远的结点到路径F的距离。
对于给定的树和非负整数s,找到一个路径F,是某直径上的一段路径(该路径两端均为树中的结点),其长度不超过s,使偏心距ECC(F)最小,并输出这个值。必要时,F 可以退化为某个结点。这条路径被称为树网的核,在上述定义下,符合要求的路径不一定只有一个,但最小偏心距是唯一的。
模板题
#include <iostream>
#include<vector>
#include<queue>
using namespace std;
#define int long long
struct side { int to, len; };
pair<int, int> ECC(const vector<vector<side>>& edges, int start) {
const int n = edges.size();
queue<int> q;
vector<int> vis(n);
vector<int> dis(n);
q.push(start);
vis[start] = true;
while (!q.empty()) {
int nowp = q.front();
q.pop();
for (side next : edges[nowp]) {
if (vis[next.to])continue;
q.push(next.to);
vis[next.to] = true;
dis[next.to] = dis[nowp] + next.len;
}
}
pair<int, int> ans(0, 0);
for (int i = 0; i < n; i++)if (dis[i] > ans.second)ans = { i,dis[i] };
return ans;
}
signed main() {
int n; cin >> n;
vector<vector<side>> edges(n);
for (int i = 1; i < n; i++) {
int u, v, w; cin >> u >> v >> w;
--u, --v;
edges[u].push_back({ v, w });
edges[v].push_back({u, w});
}
int p = ECC(edges, 0).first;
int s = ECC(edges, p).second;
cout << s*10+s*(s+1)/2 << endl;
return 0;
}
树的重心
概念
在一棵树中,如果我们选择某个结点为根,可以使得它的所有子树中最大的子树最小,那么这个结点就被称作这棵树的重心。从这个定义中, 可以推出重心的四个性质:
-
以重心为树根时,所有子树的大小不超过全树大小的一半。
-
树中所有点到某个点的距离和中,到重心的距离和是最小的;如果有两个重心,那么到它们的距离和一样。
-
把两棵树通过一条边相连得到一棵新的树,那么新的树的重心在连接原来两棵树的重心的路径上。
-
在一棵树上添加或删除一个叶子,那么它的重心最多只移动一条边的距离。
模板题
P1395 会议 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#include <iostream>
#include<vector>
#include<queue>
#include<functional>
using namespace std;
#define int long long
pair<int, int> findCentroid(const vector<vector<int>>& edges) {
const int n = edges.size();
vector<int> size(n);
pair<int, int> ans(-1, n);
function<void(int,int)> dfs = [&](int nowp,int fa){
size[nowp] = 1;
int x = 0;
for (int next : edges[nowp]) {
if (next == fa)continue;
dfs(next, nowp);
size[nowp] += size[next];
x = max(x, size[next]);
}
x = max(x, n - size[nowp]);
if (x < ans.second||(x==ans.second&&nowp<ans.first))ans = { nowp,x };
};
dfs(0, -1);
return ans;
}
int sumDis(const vector<vector<int>>& edges,int start) {
const int n = edges.size();
queue<int> q; q.push(start);
vector<bool> vis(n); vis[start] = true;
int sum = 0;
for (int cnt = 0; !q.empty(); cnt++) {
int k = q.size();
sum += cnt * k;
while (k--) {
int nowp = q.front(); q.pop();
for (int next : edges[nowp]) {
if (vis[next])continue;
q.push(next);
vis[next] = true;
}
}
}
return sum;
}
signed main() {
int n; cin >> n;
vector<vector<int>> edges(n);
for (int i = 1; i < n; i++) {
int u, v; cin >> u >> v;
--u, --v;
edges[u].push_back(v);
edges[v].push_back(u);
}
int p = findCentroid(edges).first;
cout << p + 1 << ' ' << sumDis(edges, p) << endl;
return 0;
}