树的那些事儿
连通、不含圈、恰好有n-1条边和n个顶点——这就是树
而这3个条件中的任意两点都可以对出另一点
本文将深入浅出介绍各种树的相关
无根树转有根树
其实所谓无根树,指代的就是无圈连通图。我们知道树其实就是图的特例,只要这个图满足无圈连通,那么其实本质上就是树,只是没有一个根罢了。
我们姑且来看看一个最简单的例子。
A
/ \
B C
/ \
D E
毫无疑问,这是一个典型的无根树。
- 对于A来说,它有两个邻点B和C;
- 对于B来说,只有一个邻点A(是它的父节点)
- 对于C来说,有三个点D和E,还有A(父节点)
- 对于D和E,都只有一个C(父节点)
我们看到了一些端倪,每一个点(除了根节点),都只有一个父节点。那我们只需要在遍历某一个点的邻点时,把除了父节点之外的都和其连接就好。我们可以尝试使用递归来解决。
//假设已知G[][]图
//u当前点的编号
//p[i] i号点的父节点
void dfs(int u,int fa){
//遍历这个点的所有邻点
for(int i=0;i<G[u].size();i++){
int v=G[u][i];
if(v!=fa) {
p[v]=u;
dfs(v,u);}
}
}
后来得到的p[v]就可以表示出整个树!
另外这里还有一个小细节,因为root是没有父节点的,所以我们可以将其父节点设为-1来表示,即p[root]=-1。调用时的形式应该是dfs(root,-1);
表达式树
这个问题看起来要比上一个麻烦一些,顾名思义,表达式树是处理表达式的常用工具。
a*(b+c)-d
-
/ \
* d
/ \
a +
/ \
b c
我们这里提供一种思路,也是递归。
首先找出最后一个运算符,然后以这个运算符为根,将其左侧表达式和右侧表达式为左子树和右子树,然后分别对左子树和右子树进行递归(他们两者都是独立的表达式树)
//x表示的是当前表达式的开始,y表示结束位置+1 如*s=a+b,则x=0,y=3
int build_tree(char*s ,int x,int y){
if(y-x==1){
//只有一个单独的字符,建立结点
return;
}
for(int i=x;x<y;i++){
//寻找最后一个+-号
//寻找最后一个*\号
}
//找到这个操作符(a*b+c就是+)
//左子树
//右子树
}
确定了思路,我们就来具体做一下吧!
const int maxn=10000;
int lch[maxn],right[maxn];//结点i的左子树和右子树
char op[maxn];//结点i的字符
int nc;//用来记录目前已经有的结点号
这个结构是ACM比赛中较为常用的,你如果不习惯,完全可以用一个完整的结构体来合并这三个数组。
int build_tree(char* s,int x,int y){
int u;
int c1=-1/*表示最后一个+-号的位置*/,c2=-1/*最后一个乘除号的位置*/,p/*当前是否走出了括号*/;
if(y-x==1){
u=nc++;
lch[u]=rch[u]=0;op[u]=s[x];
return;
}
for(int i=x;i<y;i++){
switch(s[i]){
//括号中的运算符先不考虑
case '(':p++;break;
case ')':p--;break;
case '+':case '-':if(!p)c1=i;break;
case '*':case '/':if(!p)c2=i;break;
}
if(c1<0)c1=c2;//如果没有+-,那么就以乘除为优先级最高;
if(c1<0) return build_tree(s,x+1,y-1);//说明整个式子被括号了,重新找
u=nc++;
lch[u]=build_tree(s,x,c1);
lch[u]=build_tree(s,c1+1,y);
op[u]=s[c1];
return u;
}
}
最小生成树
最小生成树是指总权值最小。至于什么是生成树,其实就是选取一个图中的所有点构成一棵树。
Kurskal算法
这个算法的思路是,将边的权值从小到大排列,依次遍历这些边。对于一条边(u,v),如果u、v分属于两个不同的”集合“,那么就将这条变加入到MST中(这两个集合随即合并为一个集合)。
是不是看起来很简单?怎样证明其正确呢?
我们来反证一下,如果说:不加入这条边e=>能得到一个最优解T=>那么T+e必然成环(因为T已经连通了各个点)=>我们必然可以用e替换掉T中的一个边e2(且e< e2),使图依然连通。
(共有m条边)
把所有边排序
每一个点都是一个集合(连通分量)
for(int i=0;i<m;i++)
if(e[i].u和e[i].v不在一个连通分量中)
加入这条边;
合并e[i].u和e[i].v的集合;
如何查询他们是不是在一个连通分量中呢?我们可以用BFS或者DFS来判定当前这两个点时候已经连通,但是这样比较麻烦。我们有更好的工具。
并查集
把连通分量看作一个集合,包含了1,2,3,4,5,6的图有三个连通分量{1,2,3}{4,5}{6},那么这三个分量都是一棵树。
这里还有一个小细节,就是如何寻找最后的父节点呢?其实这个问题非常简单。在开始时初始化p[i](还记得这一点么,这个数组就足以说明树中各个点的连结)为i,然后对任意一个点使用这个函数
int find(int x){return x=p[x]?x:find(x)}
//p[i] i结点的父节点
//e[i] i号边
for(int i=0;i<n;i++) p[i]=i;
sort(e.begin(),e.end());
for(int i=0;i<m;i++){
int x=find(e.u);//找到e.u的父节点的父节点的。。。
int y=find(e.v);
if(x!=y){p[x]=y};
}
版权声明:本文为博主原创文章,转载请标明出处。