PAT甲级刷题计划-树

PAT甲级刷题计划-树

树整理的共25题(持续整理中,后续会补上附上树和单链表的模板hh),后续会整理相应的题号。题目参考自acwing~,争取在8.10前完成更新!

目录

数叶子节点

家庭关系可以用家谱树来表示,给定一个家谱树,你的任务是找出其中没有孩子的成员。
输入格式

第一行包含一个整数 N表示树中结点总数以及一个整数 M表示非叶子结点数。

接下来 M行,每行的格式为:

ID K ID[1] ID[2] … ID[K]

ID是一个两位数字,表示一个非叶子结点编号,K 是一个整数,表示它的子结点数,接下来的 K 个 ID[i]也是两位数字,表示一个子结点的编号。

为了简单起见,我们将根结点固定设为 01。

所有结点的编号即为   01 , 02 , 03 , … , 31 , 32 , 33 , … , N \ 01,02,03,…,31,32,33,…,N  01,02,03,,31,32,33,,N
输出格式

输出从根结点开始,自上到下,树的每一层级分别包含多少个叶子节点。

输出占一行,整数之间用空格隔开。

数据范围

  0 < N < 100 \ 0<N<100  0<N<100

输入样例:

2 1
01 1 02

输出样例:

0 1

样例解释

该样例表示一棵只有 2个结点的树,其中 01 结点是根,而 02结点是其唯一的子节点。
因此,在根这一层级上,存在 0个叶结点;在下一个级别上,有 1个叶结点。
所以,我们应该在一行中输出0 1。

#include<iostream>
#include<cstring>
using namespace std;

const int N = 110;
int h[N],idx,e[N],ne[N];
int cnt[N];


void add(int a,int b)
{
    e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}

int maxdepth = -1;

void dfs(int u,int d)
{
    //叶子节点就是没有孩子了
    if(h[u]==-1) cnt[d]++;
    
    maxdepth = max(maxdepth,d);
    
    for(int i=h[u];~i;i=ne[i])
    {
        int j = e[i];
        dfs(j,d+1);
    }
    
}


int main()
{
    memset(h,-1,sizeof h);
    int n,m;
    cin>>n>>m;
    while (m -- )
    {
        int id,k;
        cin>>id>>k;
        while(k--)
        {
            int son;
            cin>>son;
            add(id,son);
        }
    }
    
    
    dfs(1,0);
    
    cout<<cnt[0];
    for(int i = 1;i<=maxdepth;i++)
    {
        cout<<" "<<cnt[i];
    }
    
    return 0;
}

2.树的遍历

一个二叉树,树中每个节点的权值互不相同。

现在给出它的后序遍历和中序遍历,请你输出它的层序遍历。

输入格式

第一行包含整数 N,表示二叉树的节点数。

第二行包含 N个整数,表示二叉树的后序遍历。

第三行包含 N个整数,表示二叉树的中序遍历。

输出格式

输出一行 N个整数,表示二叉树的层序遍历。

数据范围

1 ≤ N ≤ 30 1≤N≤30 1N30
官方并未给出各节点权值的取值范围,为方便起见,在本网站范围取为 1∼N。
输入样例:

7
2 3 1 5 7 6 4
1 2 3 4 5 6 7

输出样例:

4 1 6 3 5 7 2

#include<iostream>
#include <unordered_map>

using namespace std;

const int N = 35;
int post[N],in[N];
unordered_map<int,int> l,r,pos;

int build(int il,int ir,int pl,int pr)
{
    int root = post[pr];//根节点
    int k = pos[root];//根节点在中序遍历所在位置
    
    if(il<k)//还有左子树
    {
        l[root] = build(il,k-1,pl,pl+(k-1-il));
    }
    if(ir>k)//还有右子树
    {
        r[root] = build(k+1,ir,pl+k-il,pr-1);        
    }
    return root;
}

int q[N];

void bfs(int root)
{
    int hh=0,tt=0;
    q[0] = root;
    while(hh<=tt) //hh指向头节点所在位置,tt指向尾节点所在位置
    {
        int t = q[hh++];//hh++是出队
        if(l.count(t)) q[++tt] = l[t];//++tt是入队
        if(r.count(t)) q[++tt] = r[t];
    }
    
    cout<<q[0];
    for(int i=1;i<=tt;i++) cout<<" "<<q[i];
}

int main()
{
    int n;
    cin>>n;
    for (int i = 0; i < n; i ++ ) cin>>post[i];
    for (int i = 0; i < n; i ++ ) cin>>in[i],pos[in[i]] = i;//获取中序值的位置,方便定位根节点所在位置
    
    int root = build(0,n-1,0,n-1);
    
    bfs(root);
    return 0;
}

3.最深的根

一个无环连通图可以被视作一个树。

树的高度取决于所选取的根节点。

现在,你要找到可以使得树的高度最大的根节点。

它被称为最深的根。

输入格式

第一行包含整数 N,表示节点数量。

节点编号为 1∼N。

接下来 N−1

行,每行包含两个整数,表示两个节点之间存在一条边。
输出格式

输出最深的根的节点编号。

如果最深的根不唯一,则按照从小到大的顺序,将它们依次输出,每个占一行。

如果给定的图不是树,输出 Error: K components,其中 K是图中连通分量的数量。

数据范围

1 ≤ N ≤ 104 1≤N≤104 1N104

输入样例1:

5
1 2
1 3
1 4
2 5

输出样例1:

3
4
5

输入样例2:

5
1 3
1 4
2 5
3 4

输出样例2:

Error: 2 components

#include <iostream>
#include <cstring>
#include <algorithm>
#include<vector>

using namespace std;
const int N = 1e4+10,M = 2*N;

int p[N],h[N],e[M],idx,ne[M];

void add(int a,int b)
{
    e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}

//并查集
int find(int x)
{
    if(p[x]!=x) p[x] = find(p[x]);
    return p[x];
}

//求深度
int dfs(int u,int dad)
{
    int depth = 0;
    
    for(int i = h[u];~i;i=ne[i])
    {
        int j = e[i];
        if(j==dad) continue;
        depth = max(depth,dfs(j,u)+1);
    }
    
    return depth;
}

int main()
{
    int n;
    scanf("%d", &n);
    //初始化并查集
    for(int i = 1; i<= n;i++) p[i] = i;
    
    memset(h,-1,sizeof h);
    //初始时每个点都是一个集合
    int k = n;
    
    for(int i = 0; i < n-1;i++)
    {
        int a,b;
        scanf("%d%d", &a, &b);
        //存双向边
        add(a,b),add(b,a);
        
        int pa = find(a),pb = find(b);
        if(pa!=pb)
        {
            k--;
            //a的祖宗作为b的祖宗的儿子
            p[pa] = pb; //这句话千万不能写反了
        }
    }
    

    if(k!=1) printf("Error: %d components",k);
    else
    {
        vector<int> node;
        int maxdepth = -1;
        for(int i = 1;i<=n;i++)
        {
            int d = dfs(i,0);
            if(d>maxdepth)
            {
                maxdepth = d;
                node.clear();
                node.push_back(i);
            }
            else if(d==maxdepth)
            {
                node.push_back(i);
            }
        }
        for(int i = 0;i<node.size();i++) printf("%d\n",node[i]);
    }
    return 0;
}

4.判断二叉搜索树

二叉搜索树 (BST) 递归定义为具有以下属性的二叉树:

若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值
若它的右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值
它的左、右子树也分别为二叉搜索树

我们将二叉搜索树镜面翻转得到的树称为二叉搜索树的镜像。

现在,给定一个整数序列,请你判断它是否可能是某个二叉搜索树或其镜像进行前序遍历的结果。

输入格式

第一行包含整数 N,表示节点数量。

第二行包含 N个整数。

输出格式

如果给定整数序列是某个二叉搜索树或其镜像的前序遍历序列,则在第一行输出 YES,否则输出 NO。

如果你的答案是 YES,则还需要在第二行输出这棵树的后序遍历序列。
数据范围

1 ≤ N ≤ 1000 1≤N≤1000 1N1000

输入样例1:

7
8 6 5 7 10 8 11

输出样例1:

YES
5 7 6 8 11 10 8

输入样例2:

7
8 10 11 8 6 7 5

输出样例2:

YES
11 8 10 7 5 6 8

输入样例3:

7
8 6 8 5 10 9 11

输出样例3:

NO

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1010;
//因为值可能相同,所以不使用哈希存储,而且该题貌似不需要记住树的结构,hh
int pre[N],in[N],post[N];
int cnt;
bool build(int il,int ir,int pl,int pr,int type)
{
    int root = pre[pl];
    int k = 0;
    if(type == 0)
    {
        //到中序遍历中找到根节点,从前往后找第一个就是根节点,
        //因为左子树上所有结点的值均小于它的根结点的值
        for(k=il;k<=ir;k++)
            if(in[k]==root) break;
        //找不到这个点就说明搭建不出树
        if(k>ir) return false;
    }
    else
    {
        for(k=ir;k>=il;k--)
            if(in[k]==root) break;
        //找不到这个点就说明搭建不出这个树
        if(k<il) return false;
    }
    
    if(il<k)//若左子树存在
    {
        if(!build(il,k-1,pl+1,pl+1+(k-1-il),type)) return false;
    }
    if(ir>k)//若右子树存在
    {
        if(!build(k+1,ir,pl+1+(k-1-il)+1,pr,type)) return false;
    }
    
    //左右根,即是后序遍历
    post[cnt++] = root;
    return true;
}



int main()
{
    int n;
    cin>>n;
    for (int i = 0; i < n; i ++ )
    {
        cin>>pre[i];
        in[i] = pre[i];
    }
    sort(in,in+n);
    
    if(build(0,n-1,0,n-1,0))
    {
        cout<<"YES"<<endl;
        cout<<post[0];
        for(int i = 1;i<cnt;i++ ) cout<<" "<<post[i];
    }
    else
    {
        reverse(in,in+n);
        cnt = 0;
        if(build(0,n-1,0,n-1,1))
        {
            cout<<"YES"<<endl;
            cout<<post[0];
            for(int i = 1;i<cnt;i++ ) cout<<" "<<post[i];
        }
        else cout<<"NO";
    }
    return 0;
}

5.完全二叉搜索树

二叉搜索树 (BST) 递归定义为具有以下属性的二叉树:

若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值
若它的右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值
它的左、右子树也分别为二叉搜索树

完全二叉树 (CBT) 定义为除最深层外的其他层的结点数都达到最大个数,最深层的所有结点都连续集中在最左边的二叉树。

现在,给定 N个不同非负整数,表示 N 个结点的权值,用这 N个结点可以构成唯一的完全二叉搜索树。

请你输出该完全二叉搜索树的层序遍历。

输入格式

第一行包含整数 N,表示结点个数。

第二行包含 N个不同非负整数,表示每个结点的权值。

输出格式

共一行,输出给定完全二叉搜索树的层序遍历序列。

数据范围

1 ≤ N ≤ 1000 1≤N≤1000 1N1000 ,结点权值不超过 2000。

输入样例:

10
1 2 3 4 5 6 7 8 9 0

输出样例:

6 3 8 1 5 7 9 0 2 4

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1010;

int w[N],tr[N];
int n;
int cnt;

//已知中序遍历

void dfs(int u)
{
    if(u>n) return;
    //左
    dfs(2*u);
    tr[u] = w[cnt++];
    //右
    dfs(2*u+1);
}



int main()
{
    memset(w,-1,sizeof w);

    cin>>n;
    for (int i = 0; i < n; i ++ ) cin>>w[i];
    sort(w,w+n);
    dfs(1);
    
    cout<<tr[1];
    for(int i = 2;i<=n;i++) cout<<" "<<tr[i];
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值