PAT 第五章 树

1476. 数叶子结点 PAT1004

5树1 法一弄 用dps 1476. 数叶子结点 PAT1004,深度优先

//1476. 数叶子结点
//
//家庭关系可以用家谱树来表示,给定一个家谱树,你的任务是找出其中没有孩子的成员。
//
//输入格式
//第一行包含一个整数 N表示树中结点总数 
//以及一个整数 M表示非叶子结点数。
//
//接下来 M行,每行的格式为:
//
//ID K ID[1] ID[2] ... ID[K]
//
//ID是一个两位数字,表示一个非叶子结点编号,
//K是一个整数,表示它的子结点数,
//接下来的 K个 ID[i]也是两位数字,表示一个子结点的编号。
//
//为了简单起见,我们将根结点固定设为 01。
//
//所有结点的编号即为 01, 02, 03, …, 31, 32, 33, …, N。
//
//输出格式
//输出从根结点开始,自上到下,树的每一层级分别包含多少个叶子节点。
//
//输出占一行,整数之间用空格隔开。
//
//数据范围
//0 < N < 100
//
//输入样例:
//2 1		//2个结点,1个非叶子结点
//01 1 02   //01结点有1个叶子结点是02
//
//输出样例:
//0 1		//第一层有0个叶子结点,第二层有1个叶子结点
//
//样例解释
//该样例表示一棵只有 2
//个结点的树,
//其中 01结点是根,
//而 02结点是其唯一的子节点。
//
//因此,在根这一层级上,存在 0个叶结点;
//在下一个级别上,有 1个叶结点。
//
//所以,我们应该在一行中输出0 1。


//弄夫
#include<iostream>
#include<vector>
using namespace std;
//N所有结点数,M非叶子结点数
int N, M;
//定义vector数组,1号结点的孩子保存再vector[1]中,2号结点的孩子保存在vector[2]中
vector<int>child[100];

//dfs函数维护2个变量:最大层数maxleve,
int maxlevel;//全局遍历默认0
//以及每一层的无孩子结点数num_of_eachlevel[100]
int num_of_eachlevel[100] = {0};
//curID当前结点,curlevel当前的层次
void dfs(int curID,int curlevel) {
	//每到一个结点就要进行一个首要的判断
	//如果当前的层次比以前去过的层次都要大,
	//那么最大层就要更新
	if (curlevel > maxlevel) {
		maxlevel = curlevel;
	}
	//接下来 可能进入有孩结点,无孩结点,两种结点操作不同

	//如果进入有孩结点,不会更新int num_of_eachlevel[100],
	if (child[curID].size() > 0) {
		//但是要进行向下的dfs
		/*for (auto x : child[curid]) {
			dfs(x, curlevel + 1);
		}*/
		for (int i = 0; i < child[curID].size(); i++) {
			dfs(child[curID][i], curlevel + 1);
		}
	}
	//如果进入无孩结点,更新int num_of_eachlevel[当前层次],不会向下dfs,
	else {
		num_of_eachlevel[curlevel]++;
	}
}

int main() {
	int i, j, k;
	cin >> N >> M;
	
	//初始化:接下去的M行
	for (i = 0; i < M; i++) {
		//ID是本结点,k是ID有几个孩子
		int ID, k;
		cin >> ID >> k;
		//k个孩子输入进来
		while (k--) {
			cin >> j;
			//ID号结点的孩子保存在vector的child[ID]中
			child[ID].emplace_back(j);
		}
	}

	//怎么找出每一行的无孩子结点数---遍历---dfs
	//从1号结点,第1层开始dfs遍历
	dfs(1, 1);
	//输出
	for (i = 1; i <= maxlevel; i++) {
		//考虑到输出的0 1,1后面没空格
		//可以这样做
		//第一个输出的0前面没空格,后面输出1之前都要先输出空格
		if (i > 1) {
			cout << ' ';
		}
		cout << num_of_eachlevel[i];
	}
}

5树1 法二yxc用dfs 1476. 数叶子结点 PAT1004,深度优先

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

using namespace std;

const int N = 110;

int n, m;
int h[N], e[N], ne[N], idx;
//cnt[]存储每一层的无孩子结点数量
int cnt[N], max_depth;

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

void dfs(int u, int depth)
{
    if (h[u] == -1)  // 说明u是叶子节点
    {
        cnt[depth] ++;
        max_depth = max(max_depth, depth);
        return;
    }

    //不是叶子结点就遍历(单链表遍历),递归
    //~i=-i-1,~i等价于-i-1!=0,当i=-1时,-i-1=0跳出循环
    for (int i = h[u]; ~i; i = ne[i])
        dfs(e[i], depth + 1);
}

int main()
{
    cin >> n >> m;

    //memset一般是将一个数组全部初始化为0或1,设为无穷大为0x3f;
    memset(h, -1, sizeof h);
    //接下来m行
    for (int i = 0; i < m; i++)
    {
        int id, k;
        cin >> id >> k;
        while (k--)
        {
            int son;
            cin >> son;
            add(id, son);
        }
    }

    dfs(1, 0);

    //这是pat的输出行末无空格
    cout << cnt[0];
    for (int i = 1; i <= max_depth; i++) cout << ' ' << cnt[i];
    cout << endl;

    return 0;
}

5树1 法三网友:bfs 1476. 数叶子结点 PAT1004,深度优先 

#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 110;
int n, m;
//定义vector数组,1号结点的孩子保存再vector[1]中,2号结点的孩子保存在vector[2]中
vector<int> child[N];
int cnt[N], depth=0;//cnt[]存储每一层的无孩子结点数量

void bfs() {
    queue<int> q;
    q.push(-1);

    while (!q.empty()) {
        int s = q.size();
        for (int i = 0; i < s; ++i) {
            int t = q.front();
            q.pop();
            if (child[t].empty()) {
                cnt[depth]++;
            }
            else {
                for (int i = 0; i < child[t].size(); i++) {
                    q.push(child[t][i]);
                }
                //for (auto& son : child[t]) {q.push(son);}//此句效果同上
            }
        }
        depth++;
    }
}

int main() {
    cin >> n >> m;
    while (m--) {
        int id, k;
        cin >> id >> k;
        while (k--) {
            int j;
            cin >> j;
            child[id].push_back(j);
        }
    }

    bfs();
    cout << cnt[0];
    for (int i = 1; i < depth; i++) cout << " " << cnt[i];
    cout << endl;

    return 0;
}

5树1 法四网友:dfs 结合弄夫和yxc 1476. 数叶子结点 PAT1004,深度优先

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 110;

int n, m;//n是总结点数,m是非叶子节点数
vector<int> child[N];//1号结点的孩子保存再vector[1]中,2号结点的孩子保存在vector[2]中
int cnt[N], max_depth;//cnt[]存储每一层的无孩子结点数量


void dfs(int k, int depth) {
    if (child[k].empty()) {
        cnt[depth]++;
        max_depth = max(max_depth, depth);
        return;
    }

    for (auto& son : child[k]) {
        dfs(son, depth + 1);
    }
}

int main() {
    cin >> n >> m;

    while (m--) {
        int id, k;
        cin >> id >> k;
        while (k--) {
            int id_k;
            cin >> id_k;
            child[id].push_back(id_k);
        }
    }

    dfs(1, 0);

    cout << cnt[0];
    for (int i = 1; i <= max_depth; i++) cout << " " << cnt[i];
    cout << endl;

    return 0;
}

1497. 树的遍历 PAT1020

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

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

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

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

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

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

数据范围
1≤N≤30
官方并未给出各节点权值的取值范围,为方便起见,在本网站范围取为 1∼N

输入样例:
7
2 3 1 5 7 6 4
1 2 3 4 5 6 7

输出样例:
4 1 6 3 5 7 2

 

d86733deb3a849389a7e4a120cf5c006.png

5树2 法一弄 递归 1497. 树的遍历 PAT1020树的构造 背下来这题!!!常见

//1497. 树的遍历
//
//一个二叉树,树中每个节点的权值互不相同。
//
//现在给出它的后序遍历和中序遍历,请你输出它的层序遍历。
//
//输入格式
//第一行包含整数 N
//,表示二叉树的节点数。
//
//第二行包含 N
//个整数,表示二叉树的后序遍历。
//
//第三行包含 N
//个整数,表示二叉树的中序遍历。
//
//输出格式
//输出一行 N
//个整数,表示二叉树的层序遍历。
//
//数据范围
//1≤N≤30
//官方并未给出各节点权值的取值范围,为方便起见,在本网站范围取为 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>
using namespace std;
int N;//一共几个结点
int Post[40];//存贮后序遍历
int In[40];//存贮中序遍历

struct node {
	int value;
	node* left, * right;
};

node* makenode(int h1,int t1,int h2,int t2) {//h1是后序遍历队头,t1是尾,h2是中序遍历队头

	//找到递归出口
	if (h1 > t1)return NULL;

	node* p = new node();
	p->value = Post[t1];//先找到根,第一轮是4

	//2 3 1 5 7 6 4 后序
	//1 2 3 4 5 6 7	中序
	//第一轮index是4的下标
	int index;
	for (index = h2; In[index] != Post[t1]; index++) {}

	//如何确定第二个参数?
	//传入的后序序列和中序序列长度一定相同
	//初始是是t2-h2=t1-h1=6
	int u = (index - 1 - h2) + h1;
	int v = (t1 - 1) - (t2 - index - 1);
	p->left = makenode(h1, u, h2, index - 1);
	p->right = makenode(v, t1 - 1, index + 1, t2);
	return p;
}

int main() {
	cin >> N;
	int i, j, k;
	for (i = 0; i < N; i++) {//后序遍历
		cin >> Post[i];
	}
	for (i = 0; i < N; i++) {//中序遍历
		cin >> In[i];
	}
	node* root = makenode(0,N-1,0,N-1);//根据后序,中序构建树

	//层序遍历输出该树
	//手写一个队列,而没有用stl的队列
	node* Queue[40];
	int head = 0; int tail = 0;//手写队列的头好尾
	Queue[tail++] = root;
	while (head < tail) {
		//应对PAT输出末尾无空格,考虑成输出空格数据空格数据,只有第一个数据前面无空格
		if (head != 0)cout << ' ';

		node* p = Queue[head++];
		cout << p->value;
		if (p->left) {
			Queue[tail++] = p->left;
		}
		if (p->right) {
			Queue[tail++] = p->right;
		}
	}
}

 默写,加了些自己手写时候的思路

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

int N;
int Post[40];
int In[40];

struct node {
	int value;
	node* left, * right;
};

node* makenode(int h1, int t1, int h2, int t2) {
	//递归出口
	if (h1 > t1) {
		return NULL;
	}

	//找到根结点(后序序列最右边的),并且new出新节点,赋值
	node* p = new node();
	p->value = Post[t1];
	//开始递归建造左右子树,关键是如何确定函数的4个参数
	//先序后序找根,中序分左右
	//根就是后序序列最右边的
	//找到根在中序序列的下标!
	int index = 0;
	while (In[index] != Post[t1]) {
		index++;
	}//最终index=Post[t1],即根结点(后序序列最右边的结点)在中序序列的下标
	int u = (index - 1 - h2) + h1;//index-1=u-h1
	int v = (t1 - 1) - (t2 - index - 1);//t2-index-1=t1-1-v
	p->left = makenode(h1, u, h2, index - 1);
	p->right = makenode(v, t1 - 1, index + 1, t2);

	return p;
}

int main() {
	cin >> N;
	for (int i = 0; i < N; i++) {
		cin >> Post[i];
	}
	for (int i = 0; i < N; i++) {
		cin >> In[i];
	}

	node* root = makenode(0, N - 1, 0, N - 1);


	//层序遍历输出
	//根结点放入队列中,
	//出队,输出,如果有左孩子,左孩子入队,有右孩子则由孩子入队
	queue<node* > q;
	q.push(root);
	while (q.empty() == false) {
		node* s = q.front();
		q.pop();
		cout << s->value << ' ';
		if (s->left != NULL) {
			q.push(s->left);
		}
		if (s->right != NULL) {
			q.push(s->right);
		}
	}
	return 0;
}

 

5树2 法二yxc 递归 1497. 树的遍历 PAT1020树的构造

5f53531f6d4f41b1916a06286485856c.png

 

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

using namespace std;

const int N = 40;

int n;
int postorder[N];//后序遍历 序列
int inorder[N];//中序遍历 序列
unordered_map<int, int> l, r, pos;//pos是中序遍历下标,pos[4]代表中序遍历中数字4的数组下标
int q[N];

//il是中序遍历的左端点,ir是中序遍历的右端点,pl是后序遍历的左端点
int build(int il, int ir, int pl, int pr)
{
    int root = postorder[pr];//首先根结点是后序遍历的右端点
    int k = pos[root];//k是中序遍历中根结点(上面后序遍历右端点)的下标
    //il<k则左子树存在,l[root]是根的左儿子
    if (il < k) l[root] = build(il, k - 1, pl, pl + (k - 1 - il));
    if (k < ir) r[root] = build(k + 1, ir, pl + (k - 1 - il) + 1, pr - 1);
    return root;
}

void bfs(int root)//宽搜
{
    int hh = 0, tt = 0;//手写队列,hh是头,tt是尾
    q[0] = root;

    while (hh <= tt)
    {
        int t = q[hh++];
        if (l.count(t)) q[++tt] = l[t];
        if (r.count(t)) q[++tt] = r[t];
    }

    cout << q[0];
    for (int i = 1; i < n; i++) cout << ' ' << q[i];
    cout << endl;
}

int main()
{
    cin >> n;
    for (int i = 0; i < n; i++) cin >> postorder[i];//输入后序遍历序列
    for (int i = 0; i < n; i++){
        cin >> inorder[i];//输入中序遍历序列
        pos[inorder[i]] = i;//存贮下中序遍历 每个数字的 下标是多少
    }

    //构建二叉树,前两参数是中序的头尾下标,后俩参数是后序
    int root = build(0, n - 1, 0, n - 1);

    bfs(root);

    return 0;
}

 1498. 最深的根 PAT1021

 5树3 法一弄 深搜1498. 最深的根 PAT1021

//1498. 最深的根
//
//一个无环连通图可以被视作一个树。
//
//树的高度取决于所选取的根节点。
//
//现在,你要找到可以使得树的高度最大的根节点。
//
//它被称为最深的根。
//
//输入格式
//第一行包含整数 N,表示节点数量。
//
//节点编号为 1∼N。
//
//接下来 N−1行,每行包含两个整数,表示两个节点之间存在一条边。
//
//输出格式
//输出最深的根的节点编号。
//
//如果最深的根不唯一,则按照从小到大的顺序,将它们依次输出,每个占一行。
//
//如果给定的图不是树,输出 Error : K components,
//其中 K是图中连通分量的数量。
//
//数据范围
//1≤N≤104
//输入样例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<vector>
#include<set>
using namespace std;

vector<int>v[10001];
int visited[10001];//用于标记连通分量的结点是否已经遍历过

//用于判断几个连通分量的dfs
void dfs(int cur) {//cur是当前结点的编号
	//及时止损,已访问就返回
	if (visited[cur]) {
		return;
	}
	//标记已访问
	visited[cur] = 1;

	//遍历当前结点的所有邻居
	for (int i : v[cur]) {
		dfs(i);
	}
	/*for (int i = 0; i < v[cur].size(); i++) {
		dfs(i);
	}*/
}

vector<int>farthest_point;
int maxdis = 0;


void dfs2(int cur, int depth) {
	//及时止损
	if (visited[cur])return;

	//标记已访问
	visited[cur] = 1;

	if (depth > maxdis) {
		maxdis = depth;
		farthest_point.clear();
		farthest_point.emplace_back(cur);
	}
	else if (depth == maxdis) {
		farthest_point.emplace_back(cur);
	}
	for (int i : v[cur]) {
		dfs2(i, depth + 1);
	}
}

set<int>final_ans;//存放最终答案,set有自动排序功能

int main() {
	int N, i, j, k;
	cin >> N;
	for (i = 1; i < N; i++) {//输入N-1条边
		cin >> j >> k;//j和k相互连通,用vector表示
		v[j].emplace_back(k);
		v[k].emplace_back(j);
	}

	//先判断 有几个连通分量,随便找个入口dfs 就能遍历到该联通分量所有的点,用到visited[]
	//先初始化visited[]=0//N-1条边所以i从1开始
	for (i = 1; i <= N; i++) visited[i] = 0;
	

	//用于标记有几个连通分量
	int count = 0;

	//比如1,3,4连在一起,2单独,先从1遍历,会遍历到3,4,都标记已访问,这算是一个连通分量,
	//再从2开始遍历,标记已访问,算是一个连通分量。
	//在从3开始遍历,因为已访问过,不遍历,4同理
	//故一共2个连通分量
	for (i = 1; i <= N; i++) {
		if (!visited[i]) {
			count++;
			dfs(i);
		}
	}

	if (count > 1) {
		//多个连通分量
		cout << "Error : " << count << " components";
	}
	else if (count == 1) {
		//一个连通分量,是个树
		//此时寻找最深的根
		for (i = 1; i <= N; i++) visited[i] = 0;//初始化visit[]
		dfs2(1, 0);
		for (int each : farthest_point) {
			final_ans.insert(each);
		}

		farthest_point.clear();
		maxdis = 0;
		for (i = 1; i <= N; i++) visited[i] = 0;//初始化visit[]
		dfs2(*final_ans.begin(), 0);//只要头尾各dfs2遍历一次就行,不用每个结点都dfs2一遍么?

		//经过这两次头尾的dfs2,得到两个farthest_point数组,都汇聚到set中
		//此时,set中保存了左边和右边的红点
		//set有去重复功能和排序功能
		for (int each : farthest_point) {
			final_ans.insert(each);
		}

		for (int each : final_ans) {
			cout << each << '\n';
		}
	}
}
	

题目大意:给定图的n个结点和n-1条边,要求判断该图是不是树,如果是,输出最深的根节点,如果不是,输出有几个连通分量

思路大致如下:

1.题目保证图有n个顶点,n-1条边,由此可知要么是树,要么成环。

2.对图进行dfs1搜索,将搜索到的结点标记,如果dfs搜索后仍然有没有标记的(即没有搜索到)结点,就说明没有搜索到的结点和已经搜索到的结点没有道路,即属于两个不同的连通分量,再对没有搜索到的结点进行一次dfs1搜索,重复执行以上操作直到所有结点都被访问过。同时统计有几个连通分量

3.对于是树的情况(连通分量个数为1必是树),对其进行另一种dfs2搜索,找出最深的根节点,需要进行两次dfs2操作,第一次找出离1结点最远的结点,第二次找出离1结点最远的结点的最远的结点,将两次找出的结点存到set容器中,最后输出即可。

4.dfs2操作要定义一个全局变量,用来表示当前最深距离,以此为依据更新最深结点。

写题心得(总结):
1.以前没有接触过此类题目,把edge的意思理解成了边缘,实际上就是边的意思 

2.知道了如何用vector二维数组存放图,并且对其进行深度优先搜索(DFS)

3.对树的概念加深(连通分量数量为1且结点为n且边为n-1的图必定是树),如果不是,必然有连通分量成环。

4.fill函数可以将容器内一段区间的元素赋为相同值,比memset函数更强,因为赋值类型更加丰富

 

 

 

其他

5树5 1550. 完全二叉搜索树 PAT甲级真题1064

1550. 完全二叉搜索树

 

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

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

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

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

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

输入格式

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

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

输出格式

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

数据范围

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

输入样例:

10
1 2 3 4 5 6 7 8 9 0

输出样例:

6 3 8 1 5 7 9 0 2 4

 

//1550. 完全二叉搜索树
//
//二叉搜索树(BST) 递归定义为具有以下属性的二叉树:
//
//若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值
//若它的右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值
//它的左、右子树也分别为二叉搜索树
//完全二叉树(CBT) 定义为除最深层外的其他层的结点数都达到最大个数,最深层的所有结点都连续集中在最左边的二叉树。
//
//现在,给定 N个不同非负整数,
//表示 N个结点的权值,用这 N个结点可以构成唯一的完全二叉搜索树。
//
//请你输出该完全二叉搜索树的层序遍历。
//
//输入格式
//第一行包含整数 N,表示结点个数。
//
//第二行包含 N
//个不同非负整数,表示每个结点的权值。
//
//输出格式
//共一行,输出给定完全二叉搜索树的层序遍历序列。
//
//数据范围
//1≤N≤1000
//,
//结点权值不超过 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<vector>
#include<string>
#include<queue>
#include<stack>
#include<map>
#include <unordered_set>
#include <set>
#include <unordered_map>
#include<algorithm>
#include<deque>
#include <math.h>
#include<cstdio>
using namespace std;

const int N = 1010;

//yxc
class Solution {
public:
	
	int n;
	int w[N];//表示权值
	int	tr[N];//表示结点

	//全局共用一个k,所以加引用&
	void dfs(int u, int& k) //中序遍历
	{
		//递归左子树
		if (u * 2 <= n) {
			dfs(u * 2, k);
		}
		//左子树转到右子树时,给该下标(tr[u])的阶段赋值(赋予权值w[k]),
		//结点下标是按照层序遍历1,2,3,4,5,6,7,8这么下来的
		tr[u] = w[k++];
		//递归右子树
		if (u * 2 + 1 <= n) {
			dfs(u * 2 + 1, k);
		}
	}
	void func() {
		cin >> n;//读入序列
		for (int i = 0; i < n; i++) {//读入权值
			cin >> w[i];
		}

		sort(w, w + n);//得到二叉树中序遍历的结果

		//不需要写代码构建完全二叉树,数组1-n就是完全二叉树,
		//2x就是左孩子,2x+1是右孩子,x/2是父节点
		int k = 0;
		dfs(1, k);

		//输出,记得pat最后一个数字的输出后面没空格
		cout << tr[1];
		for (int i = 2; i <= n; i++) {
			cout << ' ' << tr[i];
		}
		cout << endl;

	}
};

//弄
class Solution2 {
public:
	//8 4 9 2 10 5 1 6 3 7  //下标中序遍历(堆数组的物理下标的中序遍历)
	//0 1 2 3 4  5 6 7 8 9	//对输入的数组元素的排序
	//6 3 8 1 5  7 9 0 2 4	//堆数组中的实际存贮的值
	//说白了就是把输入的序列排个序,然后存入堆数组的 中序遍历下标当中。最后输出对数组的值。
	int N;
	int a[1010];//堆数组
	vector<int> inorder;//inorder保存了中序遍历时的下标
	vector<int> data;

	void inordertraverse(int i) {//中序遍历,传入的i是下标 
		//如果下标超过了节点数,那就回退(递归终止条件)
		if (i > N) return;

		//先遍历左子树,左孩子下标是i*2
		inordertraverse(i * 2);
		//inorder保存了中序遍历时的堆数组的物理下标
		inorder.emplace_back(i);
		//遍历右子树
		inordertraverse(i * 2 + 1);
	}


	void func() {
		
		int i, j, k;
		cin >> N;
		inordertraverse(1);//从下标1的结点开始中序遍历

		//输入各结点的权值,并从小到大排序
		for (i = 0; i < N; i++) {
			cin >> j;
			data.emplace_back(j);	
		}
		sort(data.begin(), data.end());

		for (int i = 0; i < N; i++) {
			//inorder[i]是 二叉树中序遍历 时候 经历的 堆数组的 物理下标 
			//data[i]是我输入的权值,从小到大排序
			a[inorder[i]] = data[i];
		}

		//最后层序遍历输出,其实就是按序 输出堆数组的值,可用yxc方法
		//这里用了层序遍历输出(就练练层序吧)
		queue<int>q;
		q.push(1);
		while (q.size()) {
			i = q.front();
			q.pop();
			if (i > 1)cout << ' ';//控制空格,第一个不输出空格,后面的先输出空格再输出值
			cout << a[i];
			if (i * 2 <= N)q.push(i * 2);
			if (i * 2 + 1 <= N)q.push(i * 2 + 1);
		}
	}
};


int main() {
	Solution s;
	s.func();
	return 0;
}

 

5树6 1576. 再次树遍历 PAT甲级真题1086

1576. 再次树遍历

 

通过使用栈可以以非递归方式实现二叉树的中序遍历。

例如,假设遍历一个如下图所示的 66 节点的二叉树(节点编号从 11 到 66)。

则堆栈操作为:push(1); push(2); push(3); pop(); pop(); push(4); pop(); pop(); push(5); push(6); pop(); pop()。

我们可以从此操作序列中生成唯一的二叉树。

你的任务是给出这棵树的后序遍历。

b9e60cc6319b3843c650d23825a19c14.png

输入格式

第一行包含整数 N,表示树中节点个数。

树中节点编号从 1 到 N。

接下来 2N2� 行,每行包含一个栈操作,格式为:

  • Push X,将编号为 X 的节点压入栈中。
  • Pop,弹出栈顶元素。

输出格式

输出这个二叉树的后序遍历序列。

数据保证有解,数和数之间用空格隔开,末尾不能有多余空格。

数据范围

1≤N≤30

输入样例:

6
Push 1
Push 2
Push 3
Pop
Pop
Push 4
Pop
Pop
Push 5
Push 6
Pop
Pop

输出样例:

3 4 2 6 5 1

 

//1576. 再次树遍历
//
//通过使用栈可以以非递归方式实现二叉树的中序遍历。
//
//例如,假设遍历一个如下图所示的 6节点的二叉树(节点编号从 1到 6)。
//
//则堆栈操作为:push(1); push(2); push(3); pop(); pop(); push(4); pop(); pop(); push(5); push(6); 
//pop(); pop()。
//
//我们可以从此操作序列中生成唯一的二叉树。
//
//你的任务是给出这棵树的后序遍历。
//
//3.png
//
//输入格式
//第一行包含整数 N,表示树中节点个数。
//
//树中节点编号从 1到 N。
//
//接下来 2N行,每行包含一个栈操作,格式为:
//
//Push X,将编号为 X的节点压入栈中。
//Pop,弹出栈顶元素。
//
//输出格式
//输出这个二叉树的后序遍历序列。
//
//数据保证有解,数和数之间用空格隔开,末尾不能有多余空格。
//
//数据范围
//1≤N≤30
//
//输入样例:
//6
//Push 1
//Push 2
//Push 3
//Pop
//Pop
//Push 4
//Pop
//Pop
//Push 5
//Push 6
//Pop
//Pop
//
//输出样例:
//3 4 2 6 5 1
#include<iostream>
#include<vector>
#include<string>
#include<queue>
#include<stack>
#include<map>
#include <unordered_set>
#include <set>
#include <unordered_map>
#include<algorithm>
#include<deque>
#include <math.h>
#include<cstdio>
using namespace std;
const int N = 40;


//法一:push就是先序遍历,pop就是中序遍历,于是可以先序中序建树,建完树后,后续遍历输出即可
class Solution {
public:
	int N;
	int Pre[40];
	int In[40];

	struct node {
		int value;
		node* left, * right;
	};

	node* makenode(int h1, int t1, int h2, int t2) {
		//递归出口
		if (h1 > t1) {
			return NULL;
		}

		//找到根结点(后序序列最右边的),并且new出新节点,赋值
		node* p = new node();
		p->value = Pre[h1];
		//开始递归建造左右子树,关键是如何确定函数的4个参数
		//先序后序找根,中序分左右
		//根就是后序序列最右边的
		//找到根在中序序列的下标!
		int index = 0;
		while (In[index] != Pre[h1]) {
			index++;
		}//最终index=Post[t1],即根结点(后序序列最右边的结点)在中序序列的下标
		int u = index - h2 + h1;//index-1-h2=u-h1-1;u=index-1-h2+h1+1=index-h2+h1
		int v = t1 - t2 + index + 1;//t2-index-1=t1-v;v=t1-(t2-index-1)=t1-t2+index+1
		p->left = makenode(h1 + 1, u, h2, index - 1);
		p->right = makenode(v, t1, index + 1, t2);

		return p;
	}
	void func() {

	}
};




//yxc方法:上一个是push操作,则是左儿子,上一个是pop,则是右儿子
//由此重建出二叉树,再后续遍历输出即可

//左儿子和右儿子,相当于做了个映射,
//l[1]=2代表1号结点 的左孩子 是2
int l[40], r[40]; //这俩必须放在Solution2之外,不然会报错,不知道为何
class Solution2 {
public:
	
	
	void dfs(int u, int root)
	{
		if (!u) return;

		dfs(l[u], root);//这个用l[],r[]数组模拟二叉树存储,并且深度遍历的方法,emm
		dfs(r[u], root);

		cout << u;
		if (u != root) cout << ' ';
	}
	
	void func() {
		int n;//结点总数
		cin >> n;
		int root;//存入根节点,不一定是树根
		int last = 0;//上一个结点
		int	type;//上一个操作类型,push 还是 pop
		stack<int> stk;
		for (int i = 0; i < n * 2; i++){//n个结点有2n个操作
			string op;
			cin >> op;
			if (op == "Push"){
				int x;
				cin >> x;//读取当前结点的值x
				if (!last) { //第一次push(没有上一个结点,即!last=true),root=x
					root = x; 
				}
				else{//不是第一次push
					if (type == 0) {//上一次操作是push,则x是上一个结点last的左孩子
						l[last] = x;
					}
					else {
						r[last] = x;//否则是上一个结点last的右孩子
					}
				}
				stk.push(x);//x入栈
				last = x;//更新上一个结点为x
				type = 0;  // 表示push
			}
			else{//如果是pop操作
				last = stk.top();//更新上一个结点 是栈顶元素
				stk.pop();	//出栈
				type = 1;  // 表示pop
			}
		}
		dfs(root, root);//第二个root是为了控制pat最后输出无空格
	}
};

//法三:修改yxc,将树的存储方式从root,l[40],r[40]改成结构体TreeNode
class Solution3 {
public:
	struct TreeNode {
		int val;
		TreeNode* left;
		TreeNode* right;
		TreeNode() : val(0), left(nullptr), right(nullptr) {}
		TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
		TreeNode(int x, TreeNode* left, TreeNode* right) : val(x), left(left), right(right) {}
	};
	void dfs(TreeNode* u, TreeNode* root) {
		if (!u) return;

		dfs(u->left, root);
		dfs(u->right, root);

		cout << u->val;
		if (u != root) cout << ' ';

	}
	void func() {
		int n;//结点总数
		cin >> n;
		TreeNode* root = new TreeNode();
		TreeNode* last = NULL;
		int	type;//上一个操作类型,push 还是 pop
		stack<TreeNode*> stk;
		for (int i = 0; i < n * 2; i++) {//n个结点有2n个操作
			string op;
			cin >> op;
			if (op == "Push") {
				int x;
				cin >> x;//读取当前结点的值x
				TreeNode* t = new TreeNode(x);
				if (last==NULL) { //第一次push(没有上一个结点)
					root = t;
				}
				else {//不是第一次push
					if (type == 0) {//上一次操作是push,则x是上一个结点last的左孩子
						//l[last] = x;
						last->left = t;
					}
					else {
						//r[last] = x;//否则是上一个结点last的右孩子
						last->right = t;
					}
				}
				stk.push(t);//x入栈
				last = t;//更新上一个结点为x
				type = 0;  // 表示push
			}
			else {//如果是pop操作
				last = stk.top();//更新上一个结点 是栈顶元素
				stk.pop();	//出栈
				type = 1;  // 表示pop
			}
		}
		dfs(root, root);//第二个root是为了控制pat最后输出无空格
	}
};

int main() {
	Solution3 s;
	s.func();
}


5树7 1589. 构建二叉搜索树 PAT甲级真题1099

1589. 构建二叉搜索树

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

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

给定二叉树的具体结构以及一系列不同的整数,只有一种方法可以将这些数填充到树中,以使结果树满足 BST 的定义。

请你输出结果树的层序遍历。

示例如图 11 和图 22 所示。

d517f9da466e365e6cdf4163c1d9d0a1.jpeg

输入格式

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

所有结点的编号为 0∼N−1,并且编号为 00 的结点是根结点。

接下来 N 行,第 i 行(从 00 计数)包含结点 i 的左右子结点编号。如果该结点的某个子结点不存在,则用 −1−1 表示。

最后一行,包含 N 个不同的整数,表示要插入树中的数值。

输出格式

输出结果树的层序遍历序列。

数据范围

1≤N≤100

输入样例:

9
1 6
2 3
-1 -1
-1 4
5 -1
-1 -1
7 -1
-1 8
-1 -1
73 45 11 58 82 25 67 38 42

输出样例:

58 25 82 11 38 67 45 73 42

 

//1576. 再次树遍历
//
//通过使用栈可以以非递归方式实现二叉树的中序遍历。
//
//例如,假设遍历一个如下图所示的 6节点的二叉树(节点编号从 1到 6)。
//
//则堆栈操作为:push(1); push(2); push(3); pop(); pop(); push(4); pop(); pop(); push(5); push(6); 
//pop(); pop()。
//
//我们可以从此操作序列中生成唯一的二叉树。
//
//你的任务是给出这棵树的后序遍历。
//
//3.png
//
//输入格式
//第一行包含整数 N,表示树中节点个数。
//
//树中节点编号从 1到 N。
//
//接下来 2N行,每行包含一个栈操作,格式为:
//
//Push X,将编号为 X的节点压入栈中。
//Pop,弹出栈顶元素。
//
//输出格式
//输出这个二叉树的后序遍历序列。
//
//数据保证有解,数和数之间用空格隔开,末尾不能有多余空格。
//
//数据范围
//1≤N≤30
//
//输入样例:
//6
//Push 1
//Push 2
//Push 3
//Pop
//Pop
//Push 4
//Pop
//Pop
//Push 5
//Push 6
//Pop
//Pop
//
//输出样例:
//3 4 2 6 5 1
#include<iostream>
#include<vector>
#include<string>
#include<queue>
#include<stack>
#include<map>
#include <unordered_set>
#include <set>
#include <unordered_map>
#include<algorithm>
#include<deque>
#include <math.h>
#include<cstdio>
using namespace std;
const int N = 40;


//法一:push就是先序遍历,pop就是中序遍历,于是可以先序中序建树,建完树后,后续遍历输出即可
class Solution {
public:
	int N;
	int Pre[40];
	int In[40];

	struct node {
		int value;
		node* left, * right;
	};

	node* makenode(int h1, int t1, int h2, int t2) {
		//递归出口
		if (h1 > t1) {
			return NULL;
		}

		//找到根结点(后序序列最右边的),并且new出新节点,赋值
		node* p = new node();
		p->value = Pre[h1];
		//开始递归建造左右子树,关键是如何确定函数的4个参数
		//先序后序找根,中序分左右
		//根就是后序序列最右边的
		//找到根在中序序列的下标!
		int index = 0;
		while (In[index] != Pre[h1]) {
			index++;
		}//最终index=Post[t1],即根结点(后序序列最右边的结点)在中序序列的下标
		int u = index - h2 + h1;//index-1-h2=u-h1-1;u=index-1-h2+h1+1=index-h2+h1
		int v = t1 - t2 + index + 1;//t2-index-1=t1-v;v=t1-(t2-index-1)=t1-t2+index+1
		p->left = makenode(h1 + 1, u, h2, index - 1);
		p->right = makenode(v, t1, index + 1, t2);

		return p;
	}
	void func() {

	}
};




//yxc方法:上一个是push操作,则是左儿子,上一个是pop,则是右儿子
//由此重建出二叉树,再后续遍历输出即可

//左儿子和右儿子,相当于做了个映射,
//l[1]=2代表1号结点 的左孩子 是2
int l[40], r[40]; //这俩必须放在Solution2之外,不然会报错,不知道为何
class Solution2 {
public:
	
	
	void dfs(int u, int root)
	{
		if (!u) return;

		dfs(l[u], root);//这个用l[],r[]数组模拟二叉树存储,并且深度遍历的方法,emm
		dfs(r[u], root);

		cout << u;
		if (u != root) cout << ' ';
	}
	
	void func() {
		int n;//结点总数
		cin >> n;
		int root;//存入根节点,不一定是树根
		int last = 0;//上一个结点
		int	type;//上一个操作类型,push 还是 pop
		stack<int> stk;
		for (int i = 0; i < n * 2; i++){//n个结点有2n个操作
			string op;
			cin >> op;
			if (op == "Push"){
				int x;
				cin >> x;//读取当前结点的值x
				if (!last) { //第一次push(没有上一个结点,即!last=true),root=x
					root = x; 
				}
				else{//不是第一次push
					if (type == 0) {//上一次操作是push,则x是上一个结点last的左孩子
						l[last] = x;
					}
					else {
						r[last] = x;//否则是上一个结点last的右孩子
					}
				}
				stk.push(x);//x入栈
				last = x;//更新上一个结点为x
				type = 0;  // 表示push
			}
			else{//如果是pop操作
				last = stk.top();//更新上一个结点 是栈顶元素
				stk.pop();	//出栈
				type = 1;  // 表示pop
			}
		}
		dfs(root, root);//第二个root是为了控制pat最后输出无空格
	}
};

//法三:修改yxc,将树的存储方式从root,l[40],r[40]改成结构体TreeNode
class Solution3 {
public:
	struct TreeNode {
		int val;
		TreeNode* left;
		TreeNode* right;
		TreeNode() : val(0), left(nullptr), right(nullptr) {}
		TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
		TreeNode(int x, TreeNode* left, TreeNode* right) : val(x), left(left), right(right) {}
	};
	void dfs(TreeNode* u, TreeNode* root) {
		if (!u) return;

		dfs(u->left, root);
		dfs(u->right, root);

		cout << u->val;
		if (u != root) cout << ' ';

	}
	void func() {
		int n;//结点总数
		cin >> n;
		TreeNode* root = new TreeNode();
		TreeNode* last = NULL;
		int	type;//上一个操作类型,push 还是 pop
		stack<TreeNode*> stk;
		for (int i = 0; i < n * 2; i++) {//n个结点有2n个操作
			string op;
			cin >> op;
			if (op == "Push") {
				int x;
				cin >> x;//读取当前结点的值x
				TreeNode* t = new TreeNode(x);
				if (last==NULL) { //第一次push(没有上一个结点)
					root = t;
				}
				else {//不是第一次push
					if (type == 0) {//上一次操作是push,则x是上一个结点last的左孩子
						//l[last] = x;
						last->left = t;
					}
					else {
						//r[last] = x;//否则是上一个结点last的右孩子
						last->right = t;
					}
				}
				stk.push(t);//x入栈
				last = t;//更新上一个结点为x
				type = 0;  // 表示push
			}
			else {//如果是pop操作
				last = stk.top();//更新上一个结点 是栈顶元素
				stk.pop();	//出栈
				type = 1;  // 表示pop
			}
		}
		dfs(root, root);//第二个root是为了控制pat最后输出无空格
	}
};

int main() {
	Solution3 s;
	s.func();
}

 

5树8 1592. 反转二叉树 PAT甲级真题1102

1592. 反转二叉树

 

以下是来自 Max Howell @twitter 的内容:

谷歌:我们的百分之九十的工程师都使用你编写的软件,但是你连在白板上反转二叉树都做不到,还是滚吧。

现在,请你证明你会反转二叉树。

输入格式

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

所有结点编号从 0到 N−1。

接下来 N 行,每行对应一个 0∼N−1 的结点,给出该结点的左右子结点的编号,如果该结点的某个子结点不存在,则用  表示。

输出格式

输出反转后二叉树的层序遍历序列和中序遍历序列,每个序列占一行。

相邻数字之间用空格隔开,末尾不得有多余空格。

数据范围

1≤N≤10

输入样例:

8
1 -
- -
0 -
2 7
- -
- -
5 -
4 6

输出样例:

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

 

//1592. 反转二叉树
//以下是来自 Max Howell @twitter 的内容:
//
//谷歌:我们的百分之九十的工程师都使用你编写的软件,但是你连在白板上反转二叉树都做不到,还是滚吧。
//现在,请你证明你会反转二叉树。
//
//输入格式
//第一行包含一个整数 N,表示树的结点数量。
//
//所有结点编号从 0到 N−1。
//
//接下来 N行,每行对应一个 0∼N−1的结点,给出该结点的左右子结点的编号,
//如果该结点的某个子结点不存在,则用 − 表示。
//
//输出格式
//输出反转后二叉树的层序遍历序列和中序遍历序列,每个序列占一行。
//
//相邻数字之间用空格隔开,末尾不得有多余空格。
//
//数据范围
//1≤N≤10
//输入样例:
//8
//1 -
//--
//0 -
//2 7
//- -
//--
//5 -
//4 6
//输出样例:
//3 7 2 6 4 0 5 1
//6 5 7 4 3 2 0 1


#include<iostream>
#include<vector>
#include<string>
#include<queue>
#include<stack>
#include<map>
#include <unordered_set>
#include <set>
#include <unordered_map>
#include<algorithm>
#include<deque>
#include <math.h>
#include<cstdio>
using namespace std;

const int N = 15;

//法一 yxc
int n;
int l[N], r[N];
int q[N];
bool has_father[N];//用于求根节点,没有父结点的点就是根结点
class Solution {
public:
	
    void dfs_reverse(int u)
    {
        if (u == -1) return;

        dfs_reverse(l[u]);
        dfs_reverse(r[u]);
        swap(l[u], r[u]);
    }

    void bfs(int root)
    {
        int hh = 0, tt = 0;
        q[0] = root;
        while (hh <= tt)
        {
            int t = q[hh++];
            if (l[t] != -1) q[++tt] = l[t];
            if (r[t] != -1) q[++tt] = r[t];
        }

        cout << q[0];
        for (int i = 1; i < n; i++) cout << ' ' << q[i];
        cout << endl;
    }
    void dfs(int u, int& k)
    {
        if (u == -1) return;
        dfs(l[u], k);

        cout << u;
        if (++k != n) cout << ' ';

        dfs(r[u], k);
    }
	void func() {
        cin >> n;

        memset(l, -1, sizeof l);
        memset(r, -1, sizeof r);
        for (int i = 0; i < n; i++)
        {
            char lc, rc;
            cin >> lc >> rc;
            if (lc != '-') {
                l[i] = lc - '0';
                has_father[l[i]] = true;//i有左孩子,则l[i]即i的左孩子有父节点,l[i]肯定不是根结点
            }
            if (rc != '-') {
                r[i] = rc - '0';
                has_father[r[i]] = true;
            }
        }
        //找出根结点
        int root = 0;
        while (has_father[root]) {
            root++;
        }
        //深度遍历翻转树
        dfs_reverse(root);
        //层序输出
        bfs(root);

        //后序输出,k是pat用来控制最后一个输出无空格的
        int k = 0;
        dfs(root, k);
	}
};


法二 网友
//struct node{
//    int l, r;
//    int data;
//}tr[N];
int n;
//bool st[N];
//class Solution2 {
//public:
//    void level(int u)
//    {
//        queue<int>q;
//        q.push(u);
//        while (q.size())
//        {
//            auto t = q.front();
//            q.pop();
//            cout << tr[t].data << ' ';
//            if (~tr[t].l)q.push(tr[t].l);
//            if (~tr[t].r)q.push(tr[t].r);
//        }
//    }
//    void in(int u)
//    {
//        if (u == -1)return;
//        in(tr[u].l);
//        cout << tr[u].data << ' ';
//        in(tr[u].r);
//    }
//	void func() {
//        cin >> n;
//        for (int i = 0; i < n; i++)
//        {
//            char a, b;
//            cin >> a >> b;
//
//            if (b != '-'){
//                tr[i].l = b - '0';
//                st[tr[i].l] = true;
//            }
//            else tr[i].l = -1;
//
//            if (a != '-'){
//                tr[i].r = a - '0';
//                st[tr[i].r] = true;
//            }
//            else tr[i].r = -1;
//
//            tr[i].data = i;
//        }
//        int root = -1;
//        for (int i = 0; i < n; i++)//找到根结点
//            if (!st[i])
//            {
//                root = i;
//                break;
//            }
//
//
//        level(root);//好家伙,不翻转树,直接层序遍历输出翻转后的 层序遍历结果
//        cout << endl;
//        in(root);
//	}
//};


//法三:我修改yxc
struct Node {
    int val;
    int left;
    int right;
}node[N];
//int n;
//bool has_father[N];//用于求根节点,没有父结点的点就是根结点
class Solution3 {
public:
    void dfs_reverse(int u) {
        if (u == -1)return;

        dfs_reverse(node[u].left);
        dfs_reverse(node[u].right);
        swap(node[u].left, node[u].right);
    }

    vector<int> v;
    void levelorder(int u) {
        queue<int> q;
        q.push(u);
        while (q.empty() == false) {
            int i = q.front();
            q.pop();
            v.push_back(i);
            if (node[i].left != -1)q.push(node[i].left);
            if (node[i].right != -1)q.push(node[i].right);
        }
        for (int i = 0; i < v.size(); i++) {
            if (i != 0)cout << ' ';
            cout << v[i];
        }
    }

    void inorder(int u,int& k) {
        if (u == -1)return;
        inorder(node[u].left, k);
        cout << u;
        if (++k != n) cout << ' ';

        inorder(node[u].right, k);
    }

	void func() {
        cin >> n;
        for (int i = 0; i < n; i++) {
            char l, r;
            cin >> l >> r;

            if (l == '-') {
                node[i].left = -1;
            }
            else {
                int j = l - '0';
                node[i].left = j;
                has_father[j] = true;
            }

            if (r == '-') {
                node[i].right = -1;
            }
            else {
                int t = r - '0';
                node[i].right = t;
                has_father[t] = true;
            }
        }

        //找出根结点
        int root = 0;
        while (has_father[root] == true) {
            root++;
        }

        dfs_reverse(root);

        levelorder(root);
        cout << endl;
        int k = 0;
        inorder(root, k);
	}
};

int main() {
    Solution3 s;
    s.func();
    return 0;
}





//这题难在怎么构建二叉树呢
//node[i].left = l - '0';

1600. 完全二叉树

 

给定一个树,请你判断它是否是完全二叉树。

输入格式

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

树的结点编号为 0∼N−1。

接下来 N 行,每行对应一个结点,并给出该结点的左右子结点的编号,如果某个子结点不存在,则用 - 代替。

输出格式

如果是完全二叉树,则输出 YES 以及最后一个结点的编号。

如果不是,则输出 NO 以及根结点的编号。

数据范围

1≤N≤20

输入样例1:

9
7 8
- -
- -
- -
0 1
2 3
4 5
- -
- -

输出样例1:

YES 8

输入样例2:

8
- -
4 5
0 6
- -
2 3
- 7
- -
- -

输出样例2:

NO 1

 

//1600. 完全二叉树
//
//给定一个树,请你判断它是否是完全二叉树。
//
//输入格式
//第一行包含整数 N,表示树的结点个数。
//
//树的结点编号为 0∼N−1。
//
//接下来 N行,每行对应一个结点,并给出该结点的左右子结点的编号,如果某个子结点不存在,则用 - 代替。
//
//输出格式
//如果是完全二叉树,则输出 YES 以及最后一个结点的编号。
//
//如果不是,则输出 NO 以及根结点的编号。
//
//数据范围
//1≤N≤20
//输入样例1:
//9
//7 8
//- -
//--
//- -
//0 1
//2 3
//4 5
//- -
//--
//输出样例1:
//YES 8
//输入样例2:
//8
//- -
//4 5
//0 6
//- -
//2 3
//- 7
//- -
//--
//输出样例2:
//NO 1

#include<iostream>
#include<vector>
#include<string>
#include<queue>
#include<stack>
#include<map>
#include <unordered_set>
#include <set>
#include <unordered_map>
#include<algorithm>
#include<deque>
#include <math.h>
#include<cstdio>
using namespace std;
const int N = 25;

int l[N];
int r[N];

//法一:yxc
class Solution {
public:
    int maxk, maxid;//记录最后一个结点的编号
    bool has_father[N] = { false };
    
    //若结点下标最大值maxk(堆数组下标,从1到n) 等于 结点个数n,就是完全二叉树。否则不是。
    //maxid是
    
    void dfs(int u, int k)
    {
        if (u == -1) return;

        if (k > maxk)
        {
            maxk = k;
            maxid = u;
        }

        dfs(l[u], k * 2);
        dfs(r[u], k * 2 + 1);
    }
    void func() {
        int n = 0;
        //memset(l, -1, sizeof l);
        //memset(r, -1, sizeof r);
        fill(l, l + N, -1);
        fill(r, r + N, -1);
        cin >> n;
        for (int i = 0; i < n; i++)
        {
            string a, b;
            cin >> a >> b;
            if (a != "-") {
                l[i] = stoi(a);//stoi就是string to int, 反之则是 to_string()函数
                has_father[l[i]] = true;
            }
            if (b != "-") {
                r[i] = stoi(b);
                has_father[r[i]] = true;
            }
        }

        int root = 0;
        while (has_father[root]) root++;//找到根结点

        //这个1是堆数组第一元素的下标,这样才能左儿子是2x,右儿子是2x+1
        //而root是根结点编号(结点编号从0到n-1)这个是用来递归遍历的
        dfs(root, 1);

        //若结点下标最大值k 等于 结点个数n,就是完全二叉树。否则不是。
        if (maxk == n) printf("YES %d\n", maxid);
        else printf("NO %d\n", root);

    }
};


//法二:我对yxc修改下存储结构 用struct
struct Node{
    int val;
    int left;
    int right;
}node[N];
class Solution2 {
public:
    int maxk = 0;
    int maxid = 0;
    void dfs(int u, int k) {
        if (u == -1)return;
        if (k > maxk) {
            maxk = k;
            maxid = u;
        }

        dfs(node[u].left, 2 * k);
        dfs(node[u].right, 2 * k + 1);
    }

    bool has_father[N] = { false };
    void func() {
        int n = 0;
        cin >> n;
        //构建二叉树
        for (int i = 0; i < n; i++) {
            string l, r;
            cin >> l >> r;
            if (l == "-") {
                node[i].left = -1;
            }
            else {
                node[i].left = stoi(l);
                has_father[stoi(l)] = true;
            }

            if (r == "-") {
                node[i].right = -1;
            }
            else {
                node[i].right = stoi(r);
                has_father[stoi(r)] = true;
            }
        }
        //寻找根节点啊
        int root = 0;
        while (has_father[root] == true)root++;

        dfs(root, 1);

        //若maxk(堆数组时的最大下标值)等于n(结点个数),则是完全二叉树,输出yes和最后一个结点的编号id
        if (maxk == n) {
            cout << "YES" << ' ' << maxid;
        }
        else {
            cout << "NO" << ' ' << root;
        }
    }
};
int main(){
    Solution2 s;
    s.func();
    return 0;
}

1605. 二叉搜索树最后两层结点数量

 

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

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

将一系列数字按顺序插入到一个空的二叉搜索树中,然后,请你计算结果树的最低两层的结点个数。

输入格式

第一行包含整数 N,表示插入数字序列包含的数字个数。

第二行包含 N个整数,表示插入数字序列。

输出格式

以如下格式,在一行中,输出结果树的最后两层的结点数:

n1 + n2 = n

n1 是最底层结点数量,n2 是倒数第二层结点数量,n 是它们的和。

数据范围

1≤N≤1000,
−1000≤−1000≤ 插入数字 ≤1000≤1000。

输入样例:

9
25 30 42 16 20 20 35 -5 28

输出样例:

2 + 4 = 6

 

 

//1605. 二叉搜索树最后两层结点数量
//
//二叉搜索树(BST) 递归定义为具有以下属性的二叉树:
//
//若它的左子树不空,则左子树上所有结点的值均小于或等于它的根结点的值
//若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值
//它的左、右子树也分别为二叉搜索树
//将一系列数字按顺序插入到一个空的二叉搜索树中,然后,请你计算结果树的最低两层的结点个数。
//
//输入格式
//第一行包含整数 N,表示插入数字序列包含的数字个数。
//
//第二行包含 N个整数,表示插入数字序列。
//
//输出格式
//以如下格式,在一行中,输出结果树的最后两层的结点数:
//
//n1 + n2 = n
//n1 是最底层结点数量,n2 是倒数第二层结点数量,n 是它们的和。
//
//数据范围
//1≤N≤1000,−1000≤插入数字 ≤1000。
//
//输入样例:
//9
//25 30 42 16 20 20 35 - 5 28
//
//输出样例:
//2 + 4 = 6



#include<iostream>
#include<vector>
#include<string>
#include<queue>
#include<stack>
#include<map>
#include <unordered_set>
#include <set>
#include <unordered_map>
#include<algorithm>
#include<deque>
#include <math.h>
#include<cstdio>
using namespace std;
const int N = 1010;


//注意本题是小于等于放左边,大于放右边,
//pat其他题目都是小于放左边,大于等于放右边。


//法一;yxc
int n;
int l[N], r[N];
int v[N];//表示权值多少
int idx;//结点编号,从1开始,自己定义的,题目没给,是输入时候的顺序
int cnt[N];//cnt[1]=2表示第一层有俩结点
int max_depth;
class Solution {
public:
    void insert(int& u, int w){
        if (!u){//如果u是0,创建新结点,这也是递归出口
            u = ++idx;//编号+1,作为新节点的编号
            v[u] = w;//新结点权值赋为w
        }
        else if (w <= v[u]) {//如果新结点的权值小于当前结点
            insert(l[u], w);//就插入当前结点u的左孩子去
        }
        else {
            insert(r[u], w);
        }
    }

    void dfs(int u, int depth)
    {
        if (!u) return;//递归出口,u=0就return
        cnt[depth] ++;//当前深度的节点数++
        max_depth = max(max_depth, depth);//更新最大深度
        dfs(l[u], depth + 1);//往左边递归,深度加一
        dfs(r[u], depth + 1);
    }

	void func() {
        cin >> n;

        int root = 0;//根节点

        //随着输入的权值w,构建该二叉搜索树
        for (int i = 0; i < n; i++) {
            int w;
            cin >> w;//读入权值
            insert(root, w);//插入二叉搜索树中
        }

        //通过dfs找到最大深度,每个深度有多少结点
        //root是根结点,root=1,0是层数
        dfs(root, 0);

        int n1 = cnt[max_depth];
        int n2 = cnt[max_depth - 1];
        printf("%d + %d = %d\n", n1, n2, n1 + n2);

	}
};


//法二,修改
struct Node {
    int val;
    int left;
    int right;
}node[N];

class Solution2 {
public:
    void insert(int& u, int w) {
        if (u == 0) {
            u = ++idx;
            node[u].val = w;
        }
        else if (w <= node[u].val) {
            insert(node[u].left, w);//下一层函数的u的值改变了,这里的node[u].left也跟着变,因为是引用&u
        }
        else {
            insert(node[u].right, w);
        }
    }

    void dfs(int u, int depth)
    {
        if (!u) return;//递归出口,u=0就return
        cnt[depth] ++;//当前深度的节点数++
        max_depth = max(max_depth, depth);//更新最大深度
        dfs(node[u].left, depth + 1);//往左边递归,深度加一
        dfs(node[u].right, depth + 1);
    }

    void func() {
        cin >> n;
        //构建这个二叉树
        int root = 0;
        for (int i = 0; i < n; i++) {
            int w;
            cin >> w;
            //root第一次是零,之后就一直是1了,因为insert有引用,下一层函数改了,这里的root也跟着改
            insert(root, w);
        }

        dfs(root, 0);

        int n1 = cnt[max_depth];
        int n2 = cnt[max_depth - 1];
        printf("%d + %d = %d\n", n1, n2, n1 + n2);
    }
};
int main() {
    Solution2 s;
    s.func();
    return 0;
}

 

5树011 1609. 前序和后序遍历 PAT甲级真题1119

1609. 前序和后序遍历

 

假设一个二叉树上所有结点的权值都互不相同。

我们可以通过后序遍历和中序遍历来确定唯一二叉树。

也可以通过前序遍历和中序遍历来确定唯一二叉树。

但是,如果只通过前序遍历和后序遍历,则有可能无法确定唯一二叉树。

现在,给定一组前序遍历和后序遍历,请你输出对应二叉树的中序遍历。

如果树不是唯一的,则输出任意一种可能树的中序遍历即可。

输入格式

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

第二行给出前序遍历序列。

第三行给出后序遍历序列。

一行中的数字都用空格隔开。

输出格式

首先第一行,如果树唯一,则输出 Yes,如果不唯一,则输出 No

然后在第二行,输出树的中序遍历。

注意,如果树不唯一,则输出任意一种可能的情况均可。

数据范围

1≤N≤30

输入样例1:

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

输出样例1:

Yes
2 1 6 4 7 3 5

输入样例2:

4
1 2 3 4
2 4 3 1

输出样例2:

No
2 1 3 4
//1609. 前序和后序遍历
//
//假设一个二叉树上所有结点的权值都互不相同。
//
//我们可以通过后序遍历和中序遍历来确定唯一二叉树。
//
//也可以通过前序遍历和中序遍历来确定唯一二叉树。
//
//但是,如果只通过前序遍历和后序遍历,则有可能无法确定唯一二叉树。
//
//现在,给定一组前序遍历和后序遍历,请你输出对应二叉树的中序遍历。
//
//如果树不是唯一的,则输出任意一种可能树的中序遍历即可。
//
//输入格式
//第一行包含整数 N,表示结点数量。
//
//第二行给出前序遍历序列。
//
//第三行给出后序遍历序列。
//
//一行中的数字都用空格隔开。
//
//输出格式
//首先第一行,如果树唯一,则输出 Yes,如果不唯一,则输出 No。
//
//然后在第二行,输出树的中序遍历。
//
//注意,如果树不唯一,则输出任意一种可能的情况均可。
//
//数据范围
//1≤N≤30
//输入样例1:
//7
//1 2 3 4 6 7 5
//2 6 7 4 5 3 1
//输出样例1:
//Yes
//2 1 6 4 7 3 5
//
//输入样例2:
//4
//1 2 3 4
//2 4 3 1
//输出样例2:
//No
//2 1 3 4
#include<iostream>
#include<vector>
#include<string>
#include<queue>
#include<stack>
#include<map>
#include <unordered_set>
#include <set>
#include <unordered_map>
#include<algorithm>
#include<deque>
#include <math.h>
#include<cstdio>
using namespace std;
const int N = 40;

int n;
int pre[N], post[N];
class Solution {
public:
    int dfs(int l1, int r1, int l2, int r2, string& in){
        if (l1 > r1) return 1;
        if (pre[l1] != post[r2]) return 0;

        int cnt = 0;
        for (int i = l1; i <= r1; i++)  // 枚举左子树包含的节点数量
        {
            string lin, rin;
            int lcnt = dfs(l1 + 1, i, l2, l2 + i - l1 - 1, lin);
            int rcnt = dfs(i + 1, r1, l2 + i - l1 - 1 + 1, r2 - 1, rin);

            if (lcnt && rcnt)
            {
                in = lin + to_string(pre[l1]) + ' ' + rin;
                cnt += lcnt * rcnt;
                if (cnt > 1) break;
            }
        }
        return cnt;
    }
    void func() {
        cin >> n;
        for (int i = 0; i < n; i++) cin >> pre[i];
        for (int i = 0; i < n; i++) cin >> post[i];

        string in;
        int cnt = dfs(0, n - 1, 0, n - 1, in);

        if (cnt > 1) puts("No");
        else puts("Yes");

        in.pop_back();//去除末尾空格
        cout << in << endl;//输出中序遍历

    }
};

int main(){
    Solution s;
    s.func();
    return 0;
}

 

5树012 1620. Z 字形遍历二叉树 PAT甲级真题1127

1620. Z 字形遍历二叉树

​​​​​​​ 

假设一个二叉树上各结点的权值互不相同。

我们就可以通过其后序遍历和中序遍历来确定唯一二叉树。

请你输出该二叉树的 Z字形遍历序列----也就是说,从根结点开始,逐层遍历,第一层从右到左遍历,第二层从左到右遍历,第三层从右到左遍历,以此类推。

例如,下图所示二叉树,其 Z字形遍历序列应该为:1 11 5 8 17 12 20 15

1970c3017b1dbe75c83cd0bb018c7d4f.jpeg

输入格式

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

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

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

输出格式

输出二叉树的 Z 字形遍历序列。

数据范围

1≤N≤30

输入样例:

8
12 11 20 17 1 15 8 5
12 20 17 11 15 8 5 1

输出样例:

1 11 5 8 17 12 20 15

 

//1620. Z 字形遍历二叉树
//
//假设一个二叉树上各结点的权值互不相同。
//
//我们就可以通过其后序遍历和中序遍历来确定唯一二叉树。
//
//请你输出该二叉树的 Z
//字形遍历序列----也就是说,从根结点开始,逐层遍历,第一层从右到左遍历,
//第二层从左到右遍历,第三层从右到左遍历,以此类推。
//
//例如,下图所示二叉树,其 Z字形遍历序列应该为:1 11 5 8 17 12 20 15。
//
//输入格式
//第一行包含整数 N,表示二叉树结点数量。
//
//第二行包含 N个整数,表示二叉树的中序遍历序列。
//
//第三行包含 N个整数,表示二叉树的后序遍历序列。
//
//输出格式
//输出二叉树的 Z字形遍历序列。
//
//数据范围
//1≤N≤30
//输入样例:
//8
//12 11 20 17 1 15 8 5
//12 20 17 11 15 8 5 1
//输出样例:
//1 11 5 8 17 12 20 15


#include<iostream>
#include<vector>
#include<string>
#include<queue>
#include<stack>
#include<map>
#include <unordered_set>
#include <set>
#include <unordered_map>
#include<algorithm>
#include<deque>
#include <math.h>
#include<cstdio>
using namespace std;

const int N = 40;

//法一 yxc

class Solution {
public:
    int n;
    unordered_map<int, int> l, r, pos;
    int in[N], post[N];
    int q[N];

    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 (k < ir) r[root] = build(k + 1, ir, pl + k - 1 - il + 1, pr - 1);

        return root;
    }

    void bfs(int root)
    {
        int hh = 0, tt = 0;
        q[0] = root;

        int step = 0;
        while (hh <= tt)
        {
            int head = hh, tail = tt;
            while (hh <= tail)
            {
                int t = q[hh++];
                if (l.count(t)) q[++tt] = l[t];
                if (r.count(t)) q[++tt] = r[t];
            }

            if (++step % 2) reverse(q + head, q + tail + 1);
        }
    }
	void func() {
        cin >> n;
        for (int i = 0; i < n; i++)
        {
            cin >> in[i];
            pos[in[i]] = i;
        }
        for (int i = 0; i < n; i++) cin >> post[i];

        int root = build(0, n - 1, 0, n - 1);

        bfs(root);

        cout << q[0];
        for (int i = 1; i < n; i++) cout << ' ' << q[i];
        cout << endl;
	}
};


//法一:我,后序中序建树,然后层序遍历放进vector<vector<int>>,
struct Node {
	int val;
	int left;
	int right;
}node[N];
int n;
unordered_map<int, int>pos;//用于寻找根节点在中序序列中的下标
int in[N], post[N];
queue<int>q;
vector<vector<int>> v;
int k = 0;
vector<int> v2;
string s = "";
int u;
class Solution2 {
public:
    //l1是后序左,r1是后序右,l2是中序左,r2是中序右
    int build(int l1, int r1, int l2, int r2) {
        if (l1 > r1 || l2 > r2) return 0;//这是递归退出条件1

        int root = post[r1];//根节点是后序序列最右边的
        int index = pos[root];
        
        //这个if也是递归退出条件,和上面的条件1选一个即可,原理一样,
        //一个是进入之前判断,一个是进入之后判断
        if (l2 <= index - 1) {
            node[root].left = build(l1, index - 1 - l2 + l1, l2, index - 1);
        }
        if (index + 1 <= r2) {
            node[root].right = build(r2 - 1 - (r2 - (index + 1)), r1 - 1, index + 1, r2);
        }
        return root;
    }

    
    void bfs(int root) {
        q.push(root);
        while (q.empty() == false) {
            int size = q.size();
            for (int i = 0; i < size; i++) {
                u = q.front(); q.pop();
                v2.push_back(u);
                if (node[u].left != 0)q.push(node[u].left);
                if (node[u].right != 0)q.push(node[u].right);
            }
            k++;
            if (k % 2 == 1) {
                reverse(v2.begin(), v2.end());
            }
            v.emplace_back(v2);
            v2.clear();
        }
    }

	void func() {
        //完成输入
        cin >> n;
        for (int i = 1; i <= n; i++) { cin >> in[i], pos[in[i]] = i;}
        for (int i = 1; i <= n; i++)cin >> post[i];
        //中序后序建树
        int root = build(1, n, 1, n);

        //层序遍历输出
        bfs(root);

        
        for (int i = 0; i < v.size(); i++) {
            for (int j = 0; j < v[i].size(); j++) {
                cout << v[i][j] << ' ';
                s = s + to_string(v[i][j]) + ' ';
            }
        }
       
        s.pop_back();//去除最后一个空格
        cout << s;
	}
};



//网友
class Solution3 {
public:
    int n;
    int l[N], r[N], max_depth;
    vector<int>cnt[N];
    int inorder[N], postorder[N];
    unordered_map<int, int>pos;

    int build(int il, int ir, int pl, int pr)
    {
        if (il > ir)return 0;
        int root = postorder[pr];
        int k = pos[root];
        l[root] = build(il, k - 1, pl, pl + k - 1 - il);
        r[root] = build(k + 1, ir, pl + k - il, pr - 1);
        return root;
    }

    void dfs(int u, int depth)
    {
        if (u == 0)return;
        max_depth = max(max_depth, depth);
        cnt[depth].push_back(u);
        dfs(l[u], depth + 1);
        dfs(r[u], depth + 1);
    }
    void func() {
        cin >> n;
        for (int i = 1; i <= n; i++)cin >> inorder[i], pos[inorder[i]] = i;
        for (int i = 1; i <= n; i++)cin >> postorder[i];
        int root = build(1, n, 1, n);
        dfs(root, 1);
        for (int i = 1; i <= max_depth; i++)
        {
            auto& v = cnt[i];
            if (i % 2)
                for (int i = v.size() - 1; i >= 0; i--)cout << v[i] << ' ';
            else
                for (int i = 0; i < v.size(); i++)cout << v[i] << ' ';
        }
    }
};

int main() {
    Solution2 s;
    s.func();
    return 0;
}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值