P1229 遍历问题
题目描述
我们都很熟悉二叉树的前序、中序、后序遍历,在数据结构中常提出这样的问题:已知一棵二叉树的前序和中序遍历,求它的后序遍历,相应的,已知一棵二叉树的后序遍历和中序遍历序列你也能求出它的前序遍历。然而给定一棵二叉树的前序和后序遍历,你却不能确定其中序遍历序列,考虑如下图中的几棵二叉树:
所有这些二叉树都有着相同的前序遍历和后序遍历,但中序遍历却不相同。
输入格式
输A数据共两行,第一行表示该二叉树的前序遍历结果s1,第二行表示该二叉树的后序遍历结果s2。
输出格式
输出可能的中序遍历序列的总数,结果不超过长整型数。
输入输出样例
输入 #1
abc
cba
输出 #1
4
#include<stdio.h>
#include<string.h>
#include<math.h>
int main()
{
char s1[20000],s2[20000];
scanf("%s %s",s1,s2);
int len=0,n=0,k;
len=strlen(s1);
for(int i=0;i<len;i++)
for(int j=1;j<len;j++)
if(s1[i]==s2[j]&&s1[i+1]==s2[j-1])
n++;
k=pow(2,n);
printf("%d",k);
}
看到这个题目我本来想搜索建树统计有多少种,但是只知道先序和后序我不知道要怎么建这棵树,只能确定根节点,无法判断哪个结点是左子树,哪个结点是右子树。然后看了题解才知道可以找规律。
例如前序遍历ab,后序遍历ba,a为根,b为子树,在中序遍历中如果b为左子树,则中序遍历为ba;如果b为右子树,则中序遍历为ab。所以当这种节点有n个时,中序遍历的可能性就有:2^n;
所以将题目转化成找 只有一个儿子的节点个数。前序遍历为ab,后序遍历为ba,此时无法确定b是为左子树还是右子树,那么就产生了一个特殊节点。所以规律就是(此时a,b分别为前序遍历和后序遍历的字符串):s1[i]==s2[j]时,s1[i+1]==s2[j-1]则就是只有一个儿子的特殊结点。
关于二叉树的小结
二叉树的性质
在二叉树的第i层至多有个结点(i>=1)。
深度为k的二叉树至多有-1个结点(k>=1)。
对任何一棵二叉树T,如果其终端结点数为,度为2的结点数为,则=+1。
具有n个结点的完全二叉树的深度为[]+1([x]表示不超过x的最大整数)。
如果对一棵有n个结点的完全二叉树(其深度为[]+1)的结点按层序编号(从第一层到[]+1层,每层从左到右),对任一结点i(1<=i<=n)有:
(1)如果i=1,则结点i就是二叉树的根,无双亲;如果i>1,则双亲是结点[i/2],
(2)如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子是结点2i。
(3)如果2i+1>n,则结点i无左右孩子;否则其右孩子是结点2i+1。
特殊二叉树
斜树:所有结点都只有左子树的二叉树叫左斜树,所有结点都只有右子树的二叉树叫右斜树。
满二叉树:在一棵二叉树中,如果所有分支结合都存在左子树和右子树,并且所有叶子都在同一层,这样的二叉树称为满二叉树。满二叉树的特点:
(1)叶子只能出现在最下面一层,出现在其他层不能达到平衡。
(2)非叶子结点的度一定是2。
(3)在同样深度的二叉树中,满二叉树的结点个数最多,叶子数最多。
完全二叉树:对一棵具有n个结点的二叉树按层序编号,如果编号为i的结点与同样深度的满二叉树中编号为i的结点在二叉树中的位置完全相同,则称这棵二叉树为完全二叉树。
二叉树的遍历
已知中序和前序求后序
#include<stdio.h>
#include<string.h>
char mid[30],fro[30],aft[30];
int len,n;
void dfs(int l,int r,int l1)//前序和中序
{
if (l==r)
{
aft[n++]=mid[l];
return ;
}
for (int i=l;i<=r;i++)//寻找根节点
if (mid[i]==fro[l1])
{
dfs(l,i-1,l1+1);//先累加左子树
dfs(i+1,r,l1+i-l+1);//后累加右子树
aft[n++]=mid[i];
break;
}
}
int main()
{
scanf("%s %s",mid,fro);
len=strlen(mid);
dfs(0,len-1,0);
printf("%s",aft);
}
已知中序和后序求前序
#include<stdio.h>
#include<string.h>
char mid[30],fro[30],aft[30];
int len,n;
void dfs(int l,int r,int l1)
{
if (l>r)
return ;
else
{
fro[n++]=aft[l1];
for (int i=l;i<=r;i++)//寻找根节点
if (mid[i]==aft[l1])
{
dfs(l,i-1,l1-r+i-1);//先累加左子树
dfs(i+1,r,l1-1);//后累加右子树
break;
}
}
}
int main()
{
scanf("%s %s",mid,aft);
len=strlen(mid);
dfs(0,len-1,len-1);
printf("%s",fro);
}
前序、中序,后序遍历
#include<stdio.h>
char tree[5010];
void PreTraverse(int u)
{
scanf("%c",&tree[u]);
if(tree[u]=='#')
return ;
printf("%c",tree[u]);
PreTraverse(2*u);
PreTraverse(2*u+1);
}//先序遍历
void MidTraverse(int u)
{
if(tree[u]=='#')
return ;
MidTraverse(2*u);
printf("%c",tree[u]);
MidTraverse(2*u+1);
}//中序遍历
void AftTraverse(int u)
{
if(tree[u]=='#')
return ;
AftTraverse(2*u);
AftTraverse(2*u+1);
printf("%c",tree[u]);
}//后序遍历
并查集
初始化
int fa[MAXN];
void init(int n)
{
for(int i=1;i<=n;++i)
fa[i]=i;
}
假如有编号为1, 2, 3, ..., n的n个元素,我们用一个数组fa[]来存储每个元素的父节点(因为每个元素有且只有一个父节点,所以这是可行的)。一开始,我们先将它们的父节点设为自己。
查询
int find(int x)
{
if(fa[x]==x)
return x;
else
return find(fa[x]);
}
我们用递归的写法实现对代表元素的查询:一层一层访问父节点,直至根节点(根节点的标志就是父节点是本身)。要判断两个元素是否属于同一个集合,只需要看它们的根节点是否相同即可。
合并
void merge(int i,int j)
{
fa[find(i)]=find(j);
}
合并操作也是很简单的,先找到两个集合的代表元素,然后将前者的父节点设为后者即可。当然也可以将后者的父节点设为前者,这里暂时不重要。
路径压缩
只要我们在查询的过程中,把沿途的每个节点的父节点都设为根节点即可。下一次再查询时,我们就可以省很多事。
int find(int x)
{
if(x==fa[x])
return x;
else
{
fa[x]=find(fa[x]);//父节点设为根节点
returnfa[x];//返回父节点
}
}
简便写法:
int find(int x)
{
return x == fa[x] ? x : (fa[x] = find(fa[x]));
}
按秩合并
我们应该把简单的树往复杂的树上合并,而不是相反。因为这样合并后,到根节点距离变长的节点个数比较少。
我们用一个数组rank[]记录每个根节点对应的树的深度(如果不是根节点,其rank相当于以它作为根节点的子树的深度)。一开始,把所有元素的rank(秩)设为1。合并时比较两个根节点,把rank较小者往较大者上合并。
路径压缩和按秩合并如果一起使用,时间复杂度接近 O(n) ,但是很可能会破坏rank的准确性。
初始化(按秩合并)
void init(int n)
{
for(int i=1;i<=n;++i)
{
fa[i]=i;
rank[i]=1;
}
}
合并(按秩合并)
void merge(int i,int j)
{
int x=find(i),y=find(j);//先找到两个根节点
if(rank[x]<=rank[y])
fa[x]=y;
else
fa[y]=x;
if(rank[x]==rank[y]&&x!=y)
rank[y]++;//如果深度相同且根节点不同,则新的根节点的深度+1
}
P3367 【模板】并查集
题目描述
如题,现在有一个并查集,你需要完成合并和查询操作。
输入格式
第一行包含两个整数 N,M,表示共有 N 个元素和 M个操作。
接下来 M 行,每行包含三个整数 Zi,Xi,Yi 。
当 Zi=1 时,将 Xi 与 Yi 所在的集合合并。
当 Zi=2 时,输出 Xi 与 Yi 是否在同一集合内,是的输出 Y ;否则输出 N 。
输出格式
对于每一个 Zi=2 的操作,都有一行输出,每行包含一个大写字母,为 Y 或者 N 。
输入输出样例
输入 #1
4 7
2 1 2
1 1 2
2 1 2
1 3 4
2 1 4
1 2 3
2 1 4
输出 #1
N
Y
N
Y
说明/提示
对于 30% 的数据,N≤10,M≤20。
对于 70% 的数据,N≤100,M≤103。
对于 100% 的数据,1≤N≤10^4,1≤M≤2×10^5,1≤Xi,Yi≤N,Zi∈{1,2}。
#include<stdio.h>
int fa[10000];
int find(int x)
{
if(x==fa[x])
return x;
else
{
fa[x]=find(fa[x]);
return fa[x];
}
}//查询
void merge(int i,int j)
{
int fx=find(i),fy=find(j);
if(fx!=fy)
fa[fx]=fy;
}//合并
int main()
{
int N,M;
scanf("%d %d",&N,&M);
for(int i=1;i<=N;i++)
fa[i]=i;//数组初始化
for(int i=1;i<=M;i++)
{
long long int xi,yi,zi;
scanf("%lld %lld %lld",&zi,&xi,&yi);
if(zi==1)
merge(xi,yi);
else if(zi==2)
{
int x=find(xi),y=find(yi);
if(x==y)
printf("Y\n");
else
printf("N\n");
}
}
}
按照并查集的模板写就可以了,但是要用压缩路径的方法,不然过不了。
P1551 亲戚
题目背景
若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。
题目描述
规定:x 和 y是亲戚,y和 z 是亲戚,那么 x 和 z 也是亲戚。如果 x,y 是亲戚,那么 x 的亲戚都是 y 的亲戚,y的亲戚也都是 x 的亲戚。
输入格式
第一行:三个整数 n,m,p(n,m,p≤5000),分别表示有 n个人,m个亲戚关系,询问 p 对亲戚关系。
以下 m行:每行两个数 Mi,Mj,1≤Mi, Mj≤N,表示 Mi 和 Mj 具有亲戚关系。
接下来 p行:每行两个数 Pi,Pj,询问 Pi 和 Pj 是否具有亲戚关系。
输出格式
p 行,每行一个 Yes 或 No。表示第 i个询问的答案为“具有”或“不具有”亲戚关系。
输入输出样例
输入 #1
6 5 3
1 2
1 5
3 4
5 2
1 3
1 4
2 3
5 6
输出 #1
Yes
Yes
No
#include<stdio.h>
int fa[10000];
int find(int x)
{
if(x==fa[x])
return x;
else
{
fa[x]=find(fa[x]);
return fa[x];
}
}//查询
void merge(int i,int j)
{
int fx=find(i),fy=find(j);
if(fx!=fy)
fa[fx]=fy;
}//合并
int main()
{
int n,m,p;
scanf("%d %d %d",&n,&m,&p);
for(int i=1;i<=n;i++)
fa[i]=i;//数组初始化
for(int i=1;i<=m;i++)
{
int a,b;
scanf("%d %d",&a,&b);
merge(a,b);
}
for(int i=1;i<=p;i++)
{
int x,y;
scanf("%d %d",&x,&y);
int tx=find(x),ty=find(y);
if(tx==ty)
printf("Yes\n");
else
printf("No\n");
}
}
还是和上一题一样的做法。