PAT归纳总结——关于二叉树的一些总结
今天是6月26日到下个月的这个时候已经考过试了,为了让自己考一个更高的分数,所以我打算把PAT的相关题型做一个总结。目前想到的方法就是将相关的题型整理到一起然后,针对这种题型整理出一些方法。
二叉树的构建
有很多题目需要自己构造一棵二叉树有的是给出层序遍历的结果然后自己来构造二叉树,有的是给出二叉树的前序和中序或者给出二叉树的中序和后序来构造二叉树(如果给出前序和后序遍历的结果我们是不能够确定一棵二叉树的,很少数特殊的情况下可以唯一确定)
给出层序遍历的结果来构造二叉树:
1 void buildTree() {
2 root->value = que.front();
3 que.pop();
4 nodeQue.push(root);
5 while(!que.empty()) {
6 node parent = nodeQue.front();
7 nodeQue.pop();
8 node left = new Node();
9 node right = new Node();
10 if (!que.empty()) {
11 left->value = que.front();
12 parent->left = left;
13 nodeQue.push(left);
14 que.pop();
15 }
16 if (!que.empty()) {
17 right->value = que.front();
18 parent->right = right;
19 nodeQue.push(right);
20 que.pop();
21 }
22 }
23 }
给出了中序遍历和后序遍历的结果来构建二叉树:
后一种树的构建方法是通过前序遍历和中序遍历以及后序遍历中结点的遍历次序来实现的。比如:前序遍历中第一个数字是根节点,然后找出根节点在中序遍历中的位置pos,pos左边的就是左子树,pos右边的就是右子树。然后再递归的查找左右子树,直到叶子节点为止。中序遍历和后序遍历的情况与之相同,只不过后序遍历的最后一个数字代表的是根节点。
1 // 由中序遍历和后序遍历构建二叉树
2 node buildTree(int l1, int r1, int l2, int r2) {
3 if (l1 > r1 || l2 > r2) return NULL;
4 int val = postOrder[r1];
5 node root = new Node(val);
6 int pos = 0;
7 for (int i = l2; i <= r2; ++i) {
8 if (inOrder[i] == val) {
9 pos = i;
10 break;
11 }
12 }
13 int rightLen = r2 - pos;
14 int leftLen = pos - l2;
15 root->right = buildTree(r1 - rightLen, r1 - 1, pos + 1, r2);
16 root->left = buildTree(l1, l1 + leftLen - 1, l2, l2 + leftLen - 1);
17 return root;
18 }
BST + 前序遍历构造二叉树
还有一种情况是,题目说明了这棵树是BST(二叉搜索树),然后又给出了这棵二叉树的前序遍历序列,由此来构造这棵二叉树。(例题:1143 Lowest Common Ancestor)因为BST满足左子树都比根结点小,右子树都比根结点大的条件,所以我们可以通过前序遍历,找到根结点,然后再在前序遍历序列中找到第一个比根结点大的结点,此节点右侧(包含该结点)为右子树的结点,左侧为左子树的结点。然后,通过递归函数构建左子树和右子树,直到叶节点。
(递归法)
1 node buildTree(vector<int>& v, int start, int end) {
2 if (start > end) return NULL;
3 node root = new Node(v[start]);
4 int pos = start + 1;
5 while (pos <= end && v[pos] < v[start]) ++pos;
6 if (pos == start + 1) {
7 root->right = buildTree(v, start + 1, end);
8 } else if (pos == end + 1) {
9 root->left = buildTree(v, start + 1, end);
10 } else {
11 root->left = buildTree(v, start + 1, pos - 1);
12 root->right = buildTree(v, pos, end);
13 }
14 return root;
15 }
(非递归法)
1 node buildTree(vector<node>& v) {
2 node root = v[0];
3 for (int i = 1; i < v.size(); ++i) {
4 node temp = root;
5 while (temp != NULL) {
6 if (v[i]->val < temp->val) {
7 if (temp->left != NULL)
8 temp = temp->left;
9 else {
10 temp->left = v[i];
11 break;
12 }
13 } else {
14 if (temp->right != NULL)
15 temp = temp->right;
16 else {
17 temp->right = v[i];
18 break;
19 }
20 }
21 }
22 }
23 return root;
24 }
给出前序遍历和后序遍历来构造二叉树
一般情况下由前序遍历和后序遍历是不能够构造出一颗二叉树的,但是在一些特殊条件下还是能够唯一确定这棵二叉树的。因为当每一个根结点既有左子树又有右子树时,那么就不会存在左右子树不确定的情况出现,这时这可二叉树也就是一棵确定的二叉树。但是我们要怎样判断一个根结点是不是既有左子树又有右子树呢?
PreOrder: 1 2 3 4 6 7 5
PostOrder: 2 6 7 4 5 3 1
当所给的序列是上面的这种情况的时候,以前序遍历为基准来确定根结点,可知,1是这棵树的根结点,那么1后面的2一定是它的一个孩子结点。通过在后序遍历中2的位置我们可以得出,2前面没有数字,也即2不可能作为一个根节点存在。并且,2与其父亲结点之间存在其他的数字,这说名2是其父节点的一个左子树,2和1之间的数字为右子树结点,根据前序遍历的序列可以得出2后面的数字3是右子树的根结点,然后再依据此规律递归。
PreOrder: 1 2 3 4 6 7 5
PostOrder:6 7 4 5 3 2 1
当所给的序列是上面的这种情况的时候,同样以前序遍历为基准,可以得出1为根结点,2为1的一个孩子结点,但是在后序遍历中2和1是紧挨着的。假设结点1有两个孩子结点,如果2为1的左子树,那么在后序遍历序列中2和1之间一定还会有其他的数字来充当右子树;如果2为1的右子树,那么在前序遍历的序列中,一定还会有其他的数字来充当左子树。显然,这两种情况都与所给出的序列矛盾,也即在这种情况下结点1只有一个孩子结点,这个孩子结点是右子树,还是左子树是不确定的,这也是导致了所构造出来的二叉树不唯一的根本原因。
1 int preOrder[32], postOrder[32];
2 int site = 0, num = 0, n;
3 bool isUniq = true;
4
5 void Build(Tree &node, int s, int e) {
6 if (s > e) {
7 return;
8 }
9 node = (Tree)malloc(sizeof(TreeNode));
10 node->val = postOrder[e];//后续遍历的最后一个节点肯定是当前子树的根节点
11 node->left = node->right = NULL;
12 site++;
13 int child = s;
14 for (; child < e; child++) {//寻找当前根节点的一个孩子
15 if (postOrder[child] == preOrder[site])break;
16 }
17 if (e - child == 1) {//当前根节点只有一个孩子,此时树不唯一
18 isUniq = false;
19 Build(node->left, s, e - 1);
20 }
21 else if (e - child > 1) {//当前根节点有俩个孩子,且这个孩子定是左孩子
22 Build(node->left, s, child);
23 Build(node->right, child + 1, e - 1);
24 }
25 }
26
27 // 原文链接:https://blog.csdn.net/qq_40504851/article/details/100590035
LCA问题 (值得注意)
刷题的过程中也遇到一些寻找二叉树中最近公共祖先结点的问题,这类问题需要我们根据题中所给的数据首先要构建出这棵二叉树(构建的方法上文已经给出),LCA(The Lowest Common Ancestor)这类问题有一个很好用的模板可以使用:
1 node lowestCommonAncestor(node root, int n1, int n2) {
2 if (!root || root->val == n1 || root->val == n2) return root;
3 node left = lowestCommonAncestor(root->left, n1, n2);
4 node right = lowestCommonAncestor(root->right, n1, n2);
5 return !left ? right : !right ? left : root;
6 }
从根节点对这棵二叉树的左右子树进行递归遍历,当遍历到叶子结点或者某个要查询的结点的时候,就将该结点返回,如果某个根结点的左子树和右子树都非空的话则说明该节点就是所查询结点的LCA。如果一个结点为空另一个结点非空的话,则说明该非空的结点就是所要查询的LCA,也即所查寻的两个结点中一个结点是另一个结点的祖先结点。如果所查询的两个结点都是NULL的话则返回NULL。
思考:为什么这样写是错误的?
return left ? left: right ? right: root;
堆
这类问题也在PAT考试的过程中出现过,但是所考察的形式都比较简单,首先要明白大堆和小顶堆的概念,大顶堆就是根结点的值大于左子树和右子树中结点的值,小顶堆则与之相反,根结点中的value小于左子树和右子树中的值。至于,怎么构建大顶堆现在还没有遇到这样的题,之后遇到了再补上。
PAT归纳总结——关于二叉树的一些总结的更多相关文章
- PAT L2-011 玩转二叉树
https://pintia.cn/problem-sets/994805046380707840/problems/994805065406070784 给定一棵二叉树的中序遍历和前序遍历,请你先将 ...
- PAT L2-011 玩转二叉树(二叉树层序遍历)
给定一棵二叉树的中序遍历和前序遍历,请你先将树做个镜面反转,再输出反转后的层序遍历的序列.所谓镜面反转,是指将所有非叶结点的左右孩子对换.这里假设键值都是互不相等的正整数. 输入格式: 输入第一行给出 ...
- PAT 1020 Tree Traversals[二叉树遍历]
1020 Tree Traversals (25)(25 分) Suppose that all the keys in a binary tree are distinct positive int ...
- PAT归纳总结——关于图的一些总结
在刷题的过程中经常会碰到一些关于图论的问题,下面我将根据自己刷题的经验来对这些问题做一个总结. 图的表示方法 解决图论中的问题首先要解决的问题就是图的表示方法这一问题,图的表示方法主要有两种,一种使用 ...
- PAT归纳总结——关于模拟类问题的一些总结
关于时间的模拟问题 在刷题的过程中碰到了一些关于时间先后顺序的模拟题目,刚开始做的时候确实挺麻烦的,今天把这类问题的解题思路来整理一下. 比较典型的有: 1017 Queueing at Bank 1 ...
- PAT归纳总结——关于C++输入输出格式问题的一些总结
自从使用了C++就不再想使用C语言来刷题了,C++便捷的输入输出方式,以及一些STL库函数的使用都要比使用C语言方便的多.但是使用的时候还有一些需要注意的地方,在这篇博客中写一下.(更好的教程可以参看 ...
- PAT归纳总结——一些容易记混的概念
在刷题的过程中,有时候会遇到一些数据结构中的一些概念,如果对这些概念理解不清楚,甚至理解有误的话,就很可能把题目做错.所以,专门找出在刷题过程中出现的一些概念,以免考试的时候用到想不起来. 拓扑排序 ...
- PAT甲级 二叉树 相关题_C++题解
二叉树 PAT (Advanced Level) Practice 二叉树 相关题 目录 <算法笔记> 重点摘要 1020 Tree Traversals (25) 1086 Tree T ...
- go语言浅析二叉树
Hello,各位小伙伴大家好,我是小栈君,今天给大家带来的分享是关于关于二叉树相关的知识点,并用go语言实现一个二叉树和对二叉树进行遍历. 我们主要针对二叉树的概念,go实战实现二叉树的前序遍历.中序 ...
随机推荐
- Prometheus+Grafana+Alertmanager搭建全方位的监控告警系统
prometheus安装和配置 prometheus组件介绍 1.Prometheus Server: 用于收集和存储时间序列数据. 2.Client Library: 客户端库,检测应用程序代码,当 ...
- Newbe.Claptrap 框架入门,第一步 —— 开发环境准备
Newbe.Claptrap 框架依托于一些关键性的基础组件和一些可选的辅助组件.本篇我们来介绍一下如何准备一个开发环境. Newbe.Claptrap 是一个用于轻松应对并发问题的分布式开发框架.如 ...
- JAVA基础(一)—— 基础类型与面向对象
JAVA基础(一)--基础类型与面向对象 1 数据类型 基本类型 byte short int long float double boolean char n 8 16 32 64 32 64 tr ...
- 微信小程序左右滚动公告栏效果
<view class='notice-wrap' hidden='{{hideNotice}}'> <view class='tongzhitext'> <text c ...
- java下载文件指定目录下的文件
方法一: @RequestMapping('download')def download(HttpServletRequest request, HttpServletResponse respons ...
- 全真教程:Windows环境Jupyter Notebook安装、运行和工作文件夹配置
全真教程:Windows环境Jupyter Notebook安装.运行和工作文件夹配置 @ 目录 全真教程:Windows环境Jupyter Notebook安装.运行和工作文件夹配置 一.Jupyt ...
- Java中的Set集合
Set接口简介 Set接口和List接口一样,同样继承自Collection接口,它与Collection接口中的方法基本一致,并没有对Collection接口进行功能上的扩充,它是比Collecti ...
- Gevent高并发网络库精解
进程 线程 协程 异步 并发编程(不是并行)目前有四种方式:多进程.多线程.协程和异步. 多进程编程在python中有类似C的os.fork,更高层封装的有multiprocessing标准库 多线程 ...
- 一键安装KMS服务
本文转载于 秋水逸冰 » 一键安装 KMS 服务脚本 KMS,是 Key Management System 的缩写,也就是密钥管理系统.这里所说的 KMS,毋庸置疑就是用来激活 VOL 版本的 Wi ...
- Qt 自定义 进度条 纯代码
一 结果图示 二 代码 头文件 #ifndef CPROGRESS_H #define CPROGRESS_H #include <QWidget> #include <QPaint ...