图论——树有关知识
树的遍历顺序有关
前序,中序,后序概念自行百度。
知道几种顺序来确定二叉树形态
首先先明确一点至少要知道两种顺序以上才能确定一个树,因为如果只知道前序或者后序就只能确定根节点,而不能确定根节点的左右子树。但如果知道中序,那就不能确定根节点。
然后如果知道两种顺序的话,必须要知道中序,因为另外一种顺序可以确定根节点,中序来确定根节点的左右子树,然后再根据前序后者后序确定根节点的左右儿子。
具体确定代码实现(前序+中序)
#include<bits/stdc++.h>
using namespace std;
const int N=5e3+50;
string s,t;
int n;
void solve(int l,int r,int ml,int mr)
{
if(l>r)
return;
if(l==r)
{
cout<<s[l];
return;
}
int pos;
for(int i=ml;i<=mr;i++)
if(t[i]==s[l])
pos=i;
solve(l+1,l-ml+pos,ml,pos-1);solve(l-ml+pos+1,r,pos+1,r);
cout<<s[l];
return;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>s>>t;
n=s.length();
s=" "+s;t=" "+t;
solve(1,n,1,n);
return 0;
}
结合代码还是很好理解的。
二叉树计数
但还有一种题型,就是已知前序和后序,求有多少种二叉树满足这种遍历顺序。
我们能确定根节点,但是不能确定根节点有一个儿子还是两个儿子。如果一个点有一个儿子,那么就有两种情况,儿子朝左朝右都可以。比如看前序遍历的第二个数,再在后序中找到,如果在后序的下一个位置为根,那么证明根节点只有一个儿子。否则就有两个儿子。
具体代码实现如下。
#include<bits/stdc++.h>
using namespace std;
const int N=5050;
long long ans;
int n,m;
char a[N],b[N];
int main(){
scanf("%s %s",a,b);
n=strlen(a);
for(int i=0;i<n-1;i++)
for(int j=1;j<n;j++)
if(a[i]==b[j]&&a[i+1]==b[j-1])ans++;
printf("%lld\n",(1ll<<ans));
return 0;
}
多叉树转二叉树
记住口诀 "左儿子,右兄弟"。
意义:为什么要把多叉树转为二叉树?因为普遍情况下二叉树性质比多叉树的性质好。
#include<bits/stdc++.h>
using namespace std;
const int N=150;
map<char,char> son,L,R;
map<char,bool> mp,F,xx;
bool hav[N];
char root;int n;
void pre(char u)
{
cout<<u;
if(hav[u])pre(L[u]);
if(mp[u])pre(R[u]);
return;
}
void mid(char u)
{
if(hav[u])mid(L[u]);
cout<<u;
if(mp[u])mid(R[u]);
return;
}
void su(char u)
{
if(hav[u])su(L[u]);
if(mp[u])su(R[u]);
cout<<u;
return;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)
{
char u;cin>>u;xx[u]=true;
char x;
while(1)
{
cin>>x;
if(x=='0')break;
F[x]=true;
if(!hav[u])L[u]=x;
else R[son[u]]=x,mp[son[u]]=true;
son[u]=x;hav[u]=true;
}
}
for(char i='A';i<='Z';i++)
{
if(xx[i]&&!F[i])
root=i;
}
pre(root);cout<<endl;mid(root);cout<<endl;su(root);
return 0;
}
树上差分
通常用来求两点之间的距离与经过的点数。
求两点间的距离:
树上贪心
树的重心
定义&性质&证明
定义: 以它为根的所有子树中最大的子树大小最小。则称这个点为树的重心。
性质
证明:
令表示节点 的最大子树大小。 表示以 为根时,包含节点 的子树大小。
先证明其充分性:
如果为重心,则与 直接相连的节点 的大小 。考虑反证法,假设节点 为重心,而存在一个 的子树使得 ,则 。对于 又有 (因为 如果是在以 节点 为根,节点 为子树中, ,而有如果 在 的其他子树中的话,那么 < ),所以 ,与重心定义矛盾,所以 不是重心,得证。
再证明其必要性:
如果一个节点的 ,则 为重心。 。即 。对于与 不相邻的节点 ,都有 。故得证。
性质
注: 一个偶数节点的树可以有两个重心,也可以只有一个重心,而一个奇数节点的树只可能有一个重心,不可能有两个重心。
证明:
根据上一个证明必要性时,得到,当 为偶数时且 。故得证。
性质
证明:
假设所有点到节点的距离最小,且有一个与节点 相邻的节点 使得 ,那么根据调整法所有点到节点 的距离为 。因为 ,所以 ,所以 ,即 ,此时与所有点到节点 的距离最小矛盾,所以只要一个节点的 的 ,那 一定不是最小值。而如果 ,那么与 直接相邻的节点 的 。即不管往哪里调整,都会使 值增加。所以再结合根据性质 ,即可证毕。
性质
性质
如何求重心
方法一(不能求出最大子树的大小):统计节点
方法二:根据重心的定义,是得最大的子树最小的节点即为重心,所以另
代码如下:(采用方法二)
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+50;
int head[N],cnt,n,sz[N],ans=1e9;
vector<int> G;
struct edge{
int to,nxt;
}e[N*2];
void add(int u,int v)
{
e[++cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
return;
}
void dfs(int u,int fa)
{
sz[u]=1;int res=0;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(v==fa)
continue;
dfs(v,u);
res=max(res,sz[v]);
sz[u]+=sz[v];
}
res=max(res,n-sz[u]);
if(res<ans)
{
ans=res;
G.clear();
G.push_back(u);
}
else if(res==ans)
G.push_back(u);
return;
}
int main()
{
scanf("%d",&n);
for(int i=1,u,v;i<=n-1;i++)
{
scanf("%d %d",&u,&v);
add(u,v);add(v,u);
}
dfs(0,0);
printf("%d %d\n",ans,(int)G.size());
for(int i=0;i<(int)G.size();i++)
printf("%d ",G[i]);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现