前言:算法竞赛中常见的树问题
- (二叉)树的遍历
- 树的重心
- 树的直径
- 最近公共祖先(LCA)
- 哈夫曼树
- 树链剖分
一、(二叉)树的遍历
这种遍历顺序和DFS入栈的顺序很像,这种二叉树遍历方式称为先序遍历。除了先序遍历外,还有另外两种遍历,中序遍历和后序遍历。
- 先序遍历:访问根节点,遍历左子树,遍历右子树;
- 中序遍历:遍历左子树,访问根节点,遍历右子树;
- 后序遍历:遍历左子树,遍历右子树,访问根节点。
对于上面的那棵树,给出三种遍历方式是:
- 先序遍历:A->B->D->F->G->H->I->E->C;
- 中序遍历:F->D->H->G->I->B->E->A->C;
- 后序遍历:F->H->I->G->D->E->B->C->A。
说了这么多,二叉树的遍历有什么用呢?答案是,没太大用。一般情况下是用来当做题目中的信息。对于OIer来说这些是常识,初赛会考的。
二、树的重心
树的重心,也叫树的质心。对于一棵树来说,删去该树的重心后,所有的子树的大小不会超过原树大小的二分之一。树的重心还有一个性质,是相对于树上的其他点而言的,就是删去重心后形成的所有子树中最大的一棵节点数最少。换句话说,就是删去重心后生成的多棵子树是最平衡的。一棵树的重心至多有两个。
#include<cstdio> #include<vector> #include<cstring> #include<algorithm> using namespace std; const int maxn=20100; int n,father; int siz[maxn];//siz保存每个节点的子树大小。 bool vist[maxn]; int CenterOfGravity=0x3f3f3f3f,minsum=-1;//minsum表示切掉重心后最大连通块的大小。 vector<int>G[maxn]; void DFS(int u,int x){//遍历到节点x,x的父亲是u。 siz[x]=1; bool flag=true; for(int i=0;i<G[x].size();i++){ int v=G[x][i]; if(!vist[v]){ vist[v]=true; DFS(x,v);//访问子节点。 siz[x]+=siz[v];//回溯计算本节点的siz if(siz[v]>n/2) flag=false;//判断节点x是不是重心。 } } if(n-siz[x]>n/2) flag=false;//判断节点x是不是重心。 if(flag && x<CenterOfGravity) CenterOfGravity=x,father=u;//这里写x<CenterOfGravity是因为本题中要求节点编号最小的重心。 } void init(){ memset(vist,false,sizeof(vist)); memset(siz,0,sizeof(siz)); minsum=-1; CenterOfGravity=0x3f3f3f3f; for(int i=0;i<maxn;i++) G[i].clear(); } int main(){ int T; scanf("%d",&T); while(T--){ scanf("%d",&n); init(); for(int i=1;i<n;i++){ int u,v; scanf("%d%d",&u,&v); G[u].push_back(v); G[v].push_back(u); } vist[1]=1; DFS(-1,1);//任意选取节点作为根,根节点的父亲是-1。 for(int i=0;i<G[CenterOfGravity].size();i++) if(G[CenterOfGravity][i]==father) minsum=max(minsum,n-siz[CenterOfGravity]); else minsum=max(minsum,siz[G[CenterOfGravity][i]]); printf("%d %d\n",CenterOfGravity,minsum); } return 0; }
三、树的直径
文章来源: 树相关算法(一)