PAT甲级 二叉树 相关题_C++题解

二叉树

PAT (Advanced Level) Practice 二叉树 相关题

目录

  • 《算法笔记》 重点摘要
  • 1020 Tree Traversals (25)
  • 1086 Tree Traversals Again (25)
  • 1102 Invert a Binary Tree (25)
  • 1119 Pre- and Post-order Traversals (30)
  • 1127 ZigZagging on a Tree (30)
  • 1138 Postorder Traversal (25)
  • 1151 LCA in a Binary Tree (30)
  • 附:二叉树链式实现

《算法笔记》 9.2 二叉树 重点摘要

1. 常考边界条件

  • 空树:没有结点
  • 树只有一个根节点时,它亦为叶子结点

2. 完全二叉树

  • 建立大小为 2^k 的数组,其中 k 为完全二叉树的最大高度
  • 若题目规定为完全二叉树,数组大小 n+1 即可
  • 1 号位存放根节点
  • 任一编号为 x 的结点,左孩子编号 2x,右孩子编号 2x+1
  • 判断结点是否为叶结点:2x > n
  • 判断结点是否为空:x > n

3. 二叉树静态实现 ⭐

(1) 定义
struct Node{
    typename data;
    int level;
    int lchild;
    int rchild;
} node[MAXN];
(2) 新建结点
int index = 0;
int newNode (int value){
    node[index].data = value;
    node->lchild = node->rchild = -1;
    return index++;
}
(3) 插入
void insert (int &root, int value){
    if (root == -1){
        root = newNode(value);
        return;
    }
    if (由二叉树性质应插入在左子树) insert(node[root].lchild, value);
    else insert(node[root].rchild, value);
}
(4) 创建
int create(int value[], int n){
    int root = -1;
    for (int i = 0; i < n; i++) insert(root, data[i]);
    return root;
}
(5) 查找&修改
void search (int root, int value, int newvalue){
    if (root == -1) return;
    if (node[root].data == value) node[root].data = newvalue;
    search(node[root].lchild, value, newvalue);
    search(node[root].rchild, value, newvalue);
}
(6) 先序遍历
void preorder(int root){
    if (root == -1) return;
    printf("%d\n", node[root].data);
    preorder(node[root].lchild);
    preorder(node[root].rchild);
}
(7) 中序遍历
void inorder(int root){
    if (root == -1) return;
    preorder(node[root].lchild);
    printf("%d\n", node[root].data);
    preorder(node[root].rchild);
}
(8) 后序遍历
void postorder(int root){
    if (root == -1) return;
    preorder(node[root].lchild);
    preorder(node[root].rchild);
    printf("%d\n", node[root].data);
}
(10) 层序遍历
void levelorder(int root){
    queue<int> q;
    q.push(root);
    while (!q.empty()){
        int now = q.front();
        q.pop();
        printf("%d", node[now].data);
        if (node[now].lchild != -1) q.push(node[now].lchild);
        if (node[now].rchild != -1) q.push(node[now].rchild);
    }
}
(11) 根据先序+中序重建树
int create (int preL, int preR, int inL, int inR){
    if (preL > preR) return -1;
    int root = new Node(pre[preL]);
    int k;
    for (k = inL; k <= inR; k++)
        if (in[k] == pre[preL]) break;
    int numLeft = k - inL;  // 左子树结点个数
    node[root].lchild = create(preL+1, preL+numLeft, inL, k-1);
    node[root].rchild = create(preL+numLeft+1, preR, k+1, inR);
    return root;
}

1020 Tree Traversals (25)

中序后序转先序

  • 后序的最后一个即为根节点,递归每次先取到根节点再分别递归进入左子树和右子树
  • 若要获得先序遍历,只要在递归访问到根节点时存到容器中即可
#include<iostream>
#include<vector>
using namespace std;
int post[31], in[31];
vector<int> pre;
void preorder(int post_root, int inL, int inR)
{
	if (inL > inR) return;
	pre.push_back(post[post_root]);
	int k;
	for (k = inL; k <= inR; k++)
		if (in[k] == post[post_root]) break;
	preorder(post_root-1-inR+k, inL, k-1);
	preorder(post_root-1, k+1, inR);
}
int main()
{
	int n;
	scanf("%d",&n);
	for (int i = 0; i < n; i++) scanf("%d", post+i);
	for (int i = 0; i < n; i++) scanf("%d", in+i);
	preorder(n-1, 0, n-1);
	printf("%d",pre[0]);
	for (int i = 1; i < pre.size(); i++) printf(" %d",pre[i]);
	return 0;
}

中序后序转层序

  • 要按层序遍历,不可以直接在遍历时得到最终值序列的顺序,需要进行标号排序
  • 用完全二叉树的标号,即根从 1 开始,将 根标号 index 的 2*index 传给左子树,赋给左子树的根;将 2*index+1 给右子树的根
  • 若左右子树为空,直接返回未压入新节点,相当于层序遍历结点容器中对应的序号空了出来,不影响后面按序号排序的顺序
  • 递归完成后,对层序遍历结点容器按序号排序,再按序输出结点对应值
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
struct Node{
	int index, value;
};
bool cmp (Node a, Node b){ return a.index < b.index; }
int post[31], in[31];
vector<Node> level;
void levelorder(int post_root, int inL, int inR, int index)
{
	if (inL > inR) return;
	level.push_back({index,post[post_root]});
	int k;
	for (k = inL; k <= inR; k++)
		if (in[k] == post[post_root]) break;
	levelorder(post_root-1-inR+k, inL, k-1, 2 * index);
	levelorder(post_root-1, k+1, inR, 2 * index + 1);
}
int main()
{
	int n;
	scanf("%d",&n);
	for (int i = 0; i < n; i++) scanf("%d", post+i);
	for (int i = 0; i < n; i++) scanf("%d", in+i);
	levelorder(n-1, 0, n-1, 1);
	sort(level.begin(),level.end(),cmp);
	printf("%d",level[0].value);
	for (int i = 1; i < level.size(); i++) printf(" %d",level[i].value);
	return 0;
}

建树方法

#include<iostream>
#include<queue>
#include<vector>
using namespace std;
struct Node{
	int data, lchild, rchild;
} node[31];
int index = 0;
int post[31], in[31];
int newNode(int value)
{
	node[index].data = value;
	node[index].lchild = node[index].rchild = -1;
	return index++;
}
int build(int postL, int postR, int inL, int inR)
{
	if (postL > postR) return -1;
	int root = newNode(post[postR]);
	int k;
	for (k = inL; k <= inR; k++)
		if (in[k] == post[postR]) break;
	int leftnum = k - inL;
	node[root].lchild = build(postL, postL+leftnum-1, inL, k-1);
	node[root].rchild = build(postL+leftnum, postR-1, k+1, inR);
	return root;
}
void levelorder(int root){
	queue<int> q;
	q.push(root);
	vector<int> result;
	while(!q.empty()){
		int now = q.front();
		q.pop();
		result.push_back(now);
		if (node[now].lchild != -1) q.push(node[now].lchild);
		if (node[now].rchild != -1) q.push(node[now].rchild);
	}
	printf("%d", node[result[0]].data);
	for (int i = 1; i < result.size(); i++) printf(" %d", node[result[i]].data);
} 
int main()
{
	int n;
	scanf("%d",&n);
	for (int i = 0; i < n; i++) scanf("%d", post+i);
	for (int i = 0; i < n; i++) scanf("%d", in+i);
	int root = build(0, n-1, 0, n-1);
	levelorder(root);
	return 0;
}

1086 Tree Traversals Again (25)

题目思路

  • 栈实现的是二叉树的中序遍历(左根右)
  • ⭐每次 push 值的顺序是二叉树的前序遍历(根左右)
  • 前序中序转后序
#include<iostream>
#include<vector>
#include<stack>
using namespace std;
vector<int> in, pre, post;
void postorder(int preroot, int inL, int inR){
	if (inL > inR) return;
	int k;
	for (k = inL; k <= inR; k++)
		if (in[k] == pre[preroot]) break;
	postorder(preroot+1, inL, k-1);
	postorder(preroot+k-inL+1, k+1, inR);
	post.push_back(pre[preroot]);
}
int main()
{
	int n, num;
	scanf("%d", &n);
	string op;
	stack<int> s;
	for (int i = 0; i < 2 * n; i++){
		cin >> op;
		if (op.length() > 3){
			scanf("%d", &num);
			pre.push_back(num);
			s.push(num);
		} else{
			in.push_back(s.top());
			s.pop();
		}
	}
	postorder(0, 0, n-1);
	for (int i = 0; i < n; i++)
		printf("%d%c", post[i], i == n - 1 ? '\n' : ' ');
	return 0;
}

1102 Invert a Binary Tree (25)

建树 + 层序遍历 + 中序遍历

  • 只需要树的结构,没有数据,用 vector 存储左右孩子下标即可,遍历输出根的下标即可
  • invert tree 即将所有结点左右孩子对调,输入并存储时直接对调存储即可
  • 在左右孩子中没有出现过的下标即为根的下标,用 bool 数组记录是否出现过
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
vector<pair<int,int>> node;
vector<int> level, in;
void levelorder(int root)
{
	queue<int> q;
	q.push(root);
	while (!q.empty()){
		int now = q.front();
		level.push_back(now);
		q.pop();
		if (node[now].first != -1) q.push(node[now].first);
		if (node[now].second != -1) q.push(node[now].second);
	}
}
void inorder(int root)
{
	if (root == -1) return;
	inorder(node[root].first);
	in.push_back(root);
	inorder(node[root].second);
}
int main()
{
	int n;
	scanf("%d",&n);
	char a, b;
	getchar();
	bool appear[n] = {false};
	for (int i = 0; i < n; i++){
		scanf("%c %c", &a, &b);
		getchar();
		if (a != '-') appear[a-'0'] = true;
		if (b != '-') appear[b-'0'] = true;
		a = a == '-' ? -1 : a-'0';
		b = b == '-' ? -1 : b-'0';
		node.push_back({b,a});
	}
	int root = 0;
	while (appear[root]) root++;
	levelorder(root);
	printf("%d", level[0]);
	for (int i = 1; i < level.size(); i++) printf(" %d", level[i]);
	inorder(root);
	printf("\n%d", in[0]);
	for (int i = 1; i < in.size(); i++) printf(" %d", in[i]);
	return 0;
} 

1119 Pre- and Post-order Traversals (30)

题意理解

  • 二叉树的前序和后序无法唯一确定一颗二叉树,因为一个结点可能是根的左孩子也有可能是根的右孩子,题目要求不唯一时生成其中一个可行解即可
  • 前序遍历:根左右;后序遍历:左右根;从遍历顺序可知前序遍历时根节点后面面紧跟着一个子节点;同理,后序遍历的根节点前面紧贴着他的一个子节点
  • 这两个节点比较,若不同,说明根的左右节点确定,若相同,此结点既可能是左节点也可能是右结点,建树不唯一

题目思路

  • 用变量 unique 标记是否唯一,默认唯一置 true,比较发现不唯一时再标记为 false
  • 递归时需要知道树的表示范围 =》 四个变量:前序左 preL, 前序右 preR, 后序左 postL, 后序右 postR
  • preL 与 postR 对应结点相同,为根节点
  • 在前序序列中找后序右前一个子结点 (pre[k] == post[postR-1]),默认其为右子树结点,则其在先序序列中前面的结点即为左子树节点
  • 处理左子树
    • 左子树大小为 numL = k - preL - 1
    • 若左子树不为空,则左右子树确定,递归进入左子树
    • 若左子树为空,说明 post[postR-1] 这一点可能在左也可能在右,应置 unique = false
  • 左子树处理完毕后将根节点 post[postR] 压入中序序列
  • 再递归进入右子树进行处理
  • preL == preR 时说明子树仅有一个结点,则不必进行复杂处理,直接压入中序序列即可
  • 注意:格式错误时要检查所有数据输出完成后是否需要输出换行
#include<iostream>
#include<vector>
using namespace std;
vector<int> pre, post, in;
bool unique = true;
void inorder(int preL, int preR, int postL, int postR)
{
	if (preL == preR){
		in.push_back(pre[preL]);
		return;
	}
	int k;
	for (k = preL + 1; k <= preR && pre[k] != post[postR-1]; k++);
	int numL = k - preL - 1;
	if (numL > 0) inorder(preL+1, k-1, postL, postL+numL-1);
	else unique = false;
	in.push_back(post[postR]);
	inorder(k, preR, postL+numL, postR-1);
}
int main()
{
	int n;
	scanf("%d", &n);
	pre.resize(n);
	post.resize(n);
	for (int i = 0; i < n; i++) scanf("%d", &pre[i]);
	for (int i = 0; i < n; i++) scanf("%d", &post[i]);
	inorder(0, n-1, 0, n-1);
	printf("%s\n", unique ? "Yes" : "No");
	for (int i = 0; i < in.size(); i++) printf("%d%c", in[i], i+1 == in.size() ? '\n' : ' ');
	return 0;
}

1127 ZigZagging on a Tree (30)

题目思路

  • 不需要建树,用深搜思想递归,传入参数 level
  • 用 vector 数组保存每一层从左到右的结点内容
  • 每次递归根据传入 level 将所在结点压入对应层容器中
  • 再递归分别进入左右子树,找左右子树方法与中后序转先序类似
  • 递归结束后得到保存了每层内容的 vector 数组
  • 先输出根,因为根结点一层仅有一个结点
  • 从下一层开始,奇数层从左到右输出,偶数层从右到左输出
#include<iostream>
#include<vector>
using namespace std;
vector<int> in, post, leveldata[31];
void leveltrave(int root, int inL, int inR, int level){
	if (inL > inR) return;
	leveldata[level].push_back(post[root]);
	int k;
	for (k = inL; k <= inR; k++)
		if (in[k] == post[root]) break;
	leveltrave(root-(inR-k)-1, inL, k-1, level+1);
	leveltrave(root-1, k+1, inR, level+1);
}
int main()
{
	int n;
	scanf("%d", &n);
	in.resize(n);
	for (int i = 0; i < n; i++) scanf("%d", &in[i]);
	post.resize(n);
	for (int i = 0; i < n; i++) scanf("%d", &post[i]);
	leveltrave(n-1, 0, n-1, 0);
	printf("%d", leveldata[0][0]);
	for (int i = 1; i < 31; i++){
		if (i % 2)
			for (int j = 0; j < leveldata[i].size(); j++)
				printf(" %d",leveldata[i][j]);
		else
			for (int j = leveldata[i].size(); j > 0; j--)
				printf(" %d",leveldata[i][j-1]);
	}
	return 0;
}

1138 Postorder Traversal (25)

前序中序转后序

#include<iostream>
#include<vector>
using namespace std;
vector<int> pre, in, post;
void postorder(int pre_root, int inL, int inR)
{
	if (inL > inR) return;
	int k;
	for (k = inL; k <= inR; k++)
		if (pre[pre_root] == in[k]) break;
	postorder(pre_root+1, inL, k-1);
	postorder(pre_root+1+k-inL, k+1, inR);
	post.push_back(pre[pre_root]);
}
int main()
{
	int n, value;
	scanf("%d",&n);
	for (int i = 0; i < n; i++){
		scanf("%d", &value);
		pre.push_back(value);
	}
	for (int i = 0; i < n; i++){
		scanf("%d", &value);
		in.push_back(value);
	}
	postorder(0, 0, n-1);
	printf("%d", post[0]);
	return 0;
}

时间优化

  • 由于只要知道后序第一个即可,可设置标记变量
  • 还未输出过置为 false,第一个输出时改为 true
  • 进入递归时检查变量,若为true可不必再继续递归,可减少运行时间
#include<iostream>
#include<vector>
using namespace std;
vector<int> pre, in;
bool flag = false;
void postorder(int pre_root, int inL, int inR)
{
	if (inL > inR || flag) return;
	int k;
	for (k = inL; k <= inR; k++)
		if (pre[pre_root] == in[k]) break;
	postorder(pre_root+1, inL, k-1);
	postorder(pre_root+1+k-inL, k+1, inR);
	if (!flag){
		flag = true;
		printf("%d",pre[pre_root]);
	}
}
int main()
{
	int n, value;
	scanf("%d",&n);
	for (int i = 0; i < n; i++){
		scanf("%d", &value);
		pre.push_back(value);
	}
	for (int i = 0; i < n; i++){
		scanf("%d", &value);
		in.push_back(value);
	}
	postorder(0, 0, n-1);
	return 0;
}

1151 LCA in a Binary Tree (30)

题目思路

  • 已知某个树的根结点
  • 若 u 和 v 在根结点的左边,则 u 和 v 的最近公共祖先在当前子树根结点的左子树寻找
  • 若 u 和 v 在当前子树根结点的右边,则 u 和 v 的最近公共祖先就在当前子树的右子树寻找
  • 若 u 和 v 在当前子树根结点的两边,在当前子树的根结点就是 u 和 v 的最近公共祖先
  • 若 u 或 v 等于根节点,则其为另一个点的祖先
  • 每次递归都要重新定位当前根节点与两个待查结点的左右子树关系,其实中序遍历已经给出了这个关系
  • 将结点内容和中序遍历的序号构成映射,每次递归直接取出序号进行比较而不是扫描序列
#include<iostream>
#include<vector>
#include<unordered_map>
using namespace std;
vector<int> pre, in;
unordered_map<int,int> inidx;
int u, v;
void LCA(int root, int inL, int inR){
	if (inL > inR) return;
	int k = inidx[pre[root]], uin = inidx[u], vin = inidx[v];
	if (k == uin) printf("%d is an ancestor of %d.\n", u, v);
	else if (k == vin) printf("%d is an ancestor of %d.\n", v, u);
	else if (uin < k && vin < k) LCA(root+1, inL, k-1);
	else if (uin > k && vin > k) LCA(root+k-inL+1, k+1, inR);
	else printf("LCA of %d and %d is %d.\n", u, v, pre[root]);
}
int main()
{
	int m, n;
	scanf("%d%d", &m, &n);
	in.resize(n);
	for (int i = 0; i < n; i++){
		scanf("%d", &in[i]);
		inidx[in[i]] = i;
	}
	pre.resize(n);
	for (int i = 0; i < n; i++) scanf("%d", &pre[i]);
	for (int i = 0; i < m; i++){
		scanf("%d%d", &u, &v);
		if (inidx.find(u) == inidx.end() && inidx.find(v) == inidx.end())
			printf("ERROR: %d and %d are not found.\n", u, v);
		else if (inidx.find(u) == inidx.end()) printf("ERROR: %d is not found.\n", u);
		else if (inidx.find(v) == inidx.end()) printf("ERROR: %d is not found.\n", v);
		else LCA(0, 0, n-1);
	}
	return 0;
}

附:

二叉树链式实现

(1) 定义
struct Node{
    typename data;
    int level
    Node* lchild;
    Node* rchild;
}
(2) 新建结点
Node* newNode (int value){
    Node* node = new Node;
    node->data = value;
    node->lchild = node->rchild = NULL;
    return node;
}
(3) 插入
void insert (Node* &root, int value){
    if (root == NULL){
        root = newNode(value);
        return;
    }
    if (由二叉树性质应插入在左子树) insert(root->lchild, value);
    else insert(root->rchild, value);
}
(4) 创建
Node* create(int value[], int n){
    node* root = NULL;
    for (int i = 0; i < n; i++) insert(root, data[i]);
    return root;
}
(5) 查找&修改
void search (Node* root, int value, int newvalue){
    if (root == NULL) return;
    if (root->data == value) root->data = newvalue;
    search(root->lchild, value, newvalue);
    search(root->rchild, value, newvalue);
}
(6) 先序遍历
void preorder(Node* root){
    if (root == NULL) return;
    printf("%d\n", root->data);
    preorder(root->lchild);
    preorder(root->rchild);
}
(7) 中序遍历
void inorder(Node* root){
    if (root == NULL) return;
    preorder(root->lchild);
    printf("%d\n", root->data);
    preorder(root->rchild);
}
(8) 后序遍历
void postorder(Node* root){
    if (root == NULL) return;
    preorder(root->lchild);
    preorder(root->rchild);
    printf("%d\n", root->data);
}
(10) 层序遍历
void levelorder(Node* root){
    queue<Node*> q;
    root->level = 1;
    q.push(root);
    while (!q.empty()){
        Node* now = q.front();
        q.pop();
        printf("%d", now->data);
        if (now->lchild != NULL){
            now->lchild->level = now->level + 1;
            q.push(now->lchild);
        }
        if (now->rchild != NULL){
            now->rchild->level = now->level + 1;
            q.push(now->rchild);
        }
    }
}
(11) 根据先序+中序重建树
Node* create (int preL, int preR, int inL, int inR){
    if (preL > preR) return NULL;
    Node* root = new Node;
    root->data = pre[preL];
    int k;
    for (k = inL; k <= inR; k++)
        if (in[k] == pre[preL]) break;
    int numLeft = k - inL;  // 左子树结点个数
    root->lchild = create(preL+1, preL+numLeft, inL, k-1);
    root->rchild = create(preL+numLeft+1, preR, k+1, inR);
    return root;
}
posted @ 2019-09-06 14:48  鲸90830  阅读(553)  评论(0编辑  收藏  举报