树的简介
树常见用来表示家族谱的家族关系,他像一个倒着的树。他的构成为:
- 根节点( r o o t root root ): 所有节点都由一个 根节点( r o o t root root ) ,且只有一个 根节点( r o o t root root ) 。
- 父亲节点( f a t h e r − n o d e father-node father−node ): 每个 父亲节点( f a t h e r − n o d e father-node father−node ) 都有一个或者多个 孩子节点( c h i l d − n o d e child-node child−node )
- 孩子节点( c h i l d − n o d e child-node child−node ): 每个 孩子节点( c h i l d − n o d e child-node child−node ) 都有一个 父亲节点( f a t h e r − n o d e father-node father−node ) 或者 根节点( r o o t root root )
二叉树 - 特例
二叉树和树基本一样,但是他也和树有一点区别:
- 每一个 父亲节点( f a t h e r − n o d e father-node father−node ) 最多只能有两个或者没有 孩子节点( c h i l d − n o d e child-node child−node )
- 两个 孩子节点( c h i l d − n o d e child-node child−node ) 分别为 左孩子节点( l e f t − c h i l d − n o d e left-child-node left−child−node ) 和 右孩子节点( r i g h t − c h i l d − n o d e right-child-node right−child−node ) ,但是一个二叉树最多只能有两个孩子节点,也可以没有孩子节点
树的存储
树的存储经常使用 邻接表 进行存储,他的存储如下表格:
如果 1 1 1 是 2 2 2 的父亲节点,那么 2 2 2 就是 1 1 1 的孩子节点:
1 | 2 | 3 | |
---|---|---|---|
1 | f a l s e false false | t r u e true true | f a l s e false false |
2 | t r u e true true | f a l s e false false | f a l s e false false |
3 | f a l s e false false | f a l s e false false | f a l s e false false |
我们在代码里可以用 v e c t o r vector vector 来存储:
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
vector<int> G[N];
struct node {
int v,id;
}
vector<node> Q[N];
int main() {
int n,m;
scanf("%d%d",&n,&m);
int u,v;
for(int i=1;i<n;i++) {
scanf("%d%d",&u,&v);
G[u].push_back(v);
G[v].push_back(u);
}
for(int i=1;i<=m;i++) {
scanf("%d%d",&u,&v);
Q[u].push_back(node{v,i});
Q[v].push_back(node{u,i});
}
return 0;
}
这样可以用 v e c t o r vector vector 进行存储,我们也可以用 数组 进行存储:
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
bool a[N][N];
int b[N][N];
int main() {
int n,m;
scanf("%d%d",&n,&m);
int u,v;
for(int i=1;i<n;i++) {
scanf("%d%d",&u,&v);
a[u][v] = true;
a[v][u] = true;
}
for(int i=1;i<=m;i++) {
scanf("%d%d",&u,&v);
a[u][i] = v;
a[v][i] = u;
}
return 0;
}
我们也有可以用 t a r j a n tarjan tarjan 算法进行查找:
cpp
int find(int x) { //截枝查找(折半查找)
if(fa[x] == x) {
return x;
}
return fa[x] == find(fa[x]);
}
void tarjan(int u) {
vis[u] = 1;
for(auto v : G[u]) {
if(!vis[v]) {
tarjan(v);
fa[v] = u;
}
}
for(auto e : Q[u]) {
int v = e.v;
int id = e.id;
if(vis[v]) {
ans[id] = find(v);
}
}
}
所以我们可以写出 洛谷的【模板】最近公共祖先(LCA) 代码:
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5+5;
vector<int> G[N];
struct node {
int v,id;
};
vector<node> Q[N];
int fa[N];
bool vis[N];
int ans[N];
int find(int x) {
if(x == fa[x]) {
return x;
}
return fa[x] = find(fa[x]);
}
void tarjan(int u) {
vis[u] = 1;
for(auto v : G[u]) {
if(!vis[v]) {
tarjan(v);
fa[v] = u;
}
}
for(auto e : Q[u]) {
int v = e.v;
int id = e.id;
if(vis[v]) {
ans[id] = find(v);
}
}
}
int main()
{
int n,m,s;
scanf("%d%d%d",&n,&m,&s);
int u,v;
// 使用vector进行存储
for(int i=1;i<n;i++) {
scanf("%d%d",&u,&v);
G[u].push_back(v);
G[v].push_back(u);
}
for(int i=1;i<=m;i++) {
scanf("%d%d",&u,&v);
Q[u].push_back(node{v,i});
Q[v].push_back(node{u,i});
}
for(int i=1;i<=n;i++) {
fa[i] = i;
}
// 使用 tarjan算法 进行查找
tarjan(s);
// 输出
for(int i=1;i<=m;i++) {
printf("%d\n",ans[i]);
}
return 0;
}
这样我们就可以完成洛谷的 【模板】最近公共祖先(LCA) 了