PAT甲级题分类汇编——树
本文为PAT甲级分类汇编系列文章。
AVL树好难!(其实还好啦~)
我本来想着今天应该做不完树了,没想到电脑里有一份讲义,PPT和源代码都有,就一遍复习一遍抄码了一遍,更没想到的是编译一遍通过,再没想到的是运行也正常,最没想到的是一遍AC。
其实很多题都有数,std::set 之类用的是红黑树,据说很复杂,比AVL树还要复杂的那种。但是,用到这些设施的题,都不在这一分类下,这一分类下的题,因为题目要求自己建树,也就不用标准库设施了。
大多数题中,树在内存中都是连续存放的。不是像完全二叉树那样的连续,是物理上连续而逻辑上用数组下表代替指针。
题号 | 标题 | 分数 | 大意 |
1053 | Path of Equal Weight | 30 | 寻找树中一定权重的路径 |
1064 | Complete Binary Search Tree | 30 | 完全二叉搜索树 |
1066 | Root of AVL Tree | 25 | AVL树的根 |
1086 | Tree Traversals Again | 25 | 中序遍历逆推 |
1094 | The Largest Generation | 25 | 树中元素最多的层 |
1099 | Build A Binary Search Tree | 30 | 建立二叉搜索树 |
这一系列的题还是清一色地某姥姥出的。
学数据结构的时候做过1064和1086,遇到过1066,但跳过了。
这次除了1086和1094都写,毕竟不能放着30分题不管。30分题一遍AC,别提有多爽了。
1053:
要求按非升序输出权重,使其和为给定值。
一开始没看清题,以为是根节点到任意节点,就写了个有点像Dijkstra的东西,后来跑样例结果不对,才发现是根节点到叶节点,反而更简单了。
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm> struct Node
{
int index;
int weight;
std::vector<int> children;
std::vector<int> weights;
int total;
}; int main()
{
int num_node, num_nonleaf, target;
std::cin >> num_node >> num_nonleaf >> target;
std::vector<Node> nodes(num_node);
for (auto& n : nodes)
std::cin >> n.weight;
for (int i = ; i != num_node; ++i)
nodes[i].index = i;
for (int i = ; i != num_nonleaf; ++i)
{
int index;
std::cin >> index;
auto& node = nodes[index];
int count;
std::cin >> count;
node.children.resize(count);
for (auto& i : node.children)
std::cin >> i;
}
if (nodes.front().weight == target)
{
std::cout << target;
return ;
}
nodes.front().total = nodes.front().weight;
nodes.front().weights.push_back(nodes.front().weight);
std::queue<int> queue;
std::vector<int> selected;
queue.push();
while (!queue.empty())
{
auto& node = nodes[queue.front()];
queue.pop();
for (auto& i : node.children)
{
auto& n = nodes[i];
n.weights = node.weights;
n.weights.push_back(n.weight);
n.total = node.total + n.weight;
if (n.total == target && n.children.empty())
selected.push_back(n.index);
else if (n.total < target)
queue.push(n.index);
}
}
std::sort(selected.begin(), selected.end(), [&](int i, int j) {
return nodes[i].weights > nodes[j].weights;
});
for (const auto& i : selected)
{
auto& node = nodes[i];
auto end = node.weights.end() - ;
for (auto iter = node.weights.begin(); iter != end; ++iter)
std::cout << *iter << ' ';
std::cout << *end;
std::cout << std::endl;
}
}
一个小坑是根节点权重就是要求的值,而我的算法总是处理当前节点的子节点,而根节点没有前驱节点,所以要加个特殊情况讨论。一个case而已,想了两分钟就发现了。
1064:
作为标准库的狂热爱好者,我写的数据结构最好也要有迭代器,这道题是绝佳的平台。层序遍历迭代器就用 std::vector 的迭代器就行,中序遍历迭代器得自己写。
#include <iostream>
#include <vector>
#include <algorithm>
#include <queue> class InOrderIterator
{
public:
InOrderIterator(std::vector<int>& _rVector)
: data_(_rVector)
{
auto s = data_.size() - ;
int count = ;
while (s >>= )
++count;
index_ = << count;
}
int& operator*()
{
return data_[index_];
}
InOrderIterator& operator++()
{
if (index_ * + < data_.size())
{
index_ = index_ * + ;
while ((index_ *= ) < data_.size())
;
index_ /= ;
}
else
{
int count = , i = index_;
while (i % )
i >>= , ++count;
index_ >>= count;
}
return *this;
}
private:
std::vector<int>& data_;
int index_;
}; int main(int argc, char const *argv[])
{
int n;
std::cin >> n;
std::vector<int> data(n);
for (int i = ; i != n; ++i)
std::cin >> data[i]; std::sort(data.begin(), data.end());
std::vector<int> tree(n + );
InOrderIterator iter(tree);
for (auto i : data)
*iter = i, ++iter; auto size = tree.size() - ;
for (auto i = ; i != size; ++i)
std::cout << tree[i] << ' ';
std::cout << tree[size]; return ;
}
这个迭代算法我想了好久,当时觉得很优雅。现在自己都看不懂,好像是什么位操作吧。
然而,同样是迭代,1099题中的算法就比这个简单得多,后面会讲。
1066:
这道题我一直不敢做。实际上,左旋右旋什么的在我的脑海中一直都只是名词——我从未实现过一个AVL树,直到今天。在PPT与代码的帮助下,我顺利地写了一个AvlTree类模板,实现了一部分接口。
AVL树的4种旋转,学的时候听得懂,写的时候觉得烦,真的写完了也不过如此,其实没什么复杂的。
#include <iostream>
#include <utility>
#include <functional> template <typename T, typename Comp = std::less<T>>
class AvlTree
{
public:
AvlTree();
void insert(T _key);
void root(T& _target);
private:
struct Node
{
Node(T&& _key);
T key_;
Node* left_ = nullptr;
Node* right_ = nullptr;
int height_;
static int height(Node* _node);
};
Node* node_ = nullptr;
static void insert(Node*& _node, T&& _key);
static void single_left(Node*& _node);
static void single_right(Node*& _node);
static void double_left_right(Node*& _node);
static void double_right_left(Node*& _node);
}; template<typename T, typename Comp>
AvlTree<T, Comp>::AvlTree() = default;
template<typename T, typename Comp>
void AvlTree<T, Comp>::insert(T _key)
{
insert(node_, std::move(_key));
} template<typename T, typename Comp>
void AvlTree<T, Comp>::root(T& _target)
{
if (node_)
_target = node_->key_;
} template<typename T, typename Comp>
void AvlTree<T, Comp>::insert(Node*& _node, T&& _key)
{
if (!_node)
_node = new Node(std::move(_key));
else if (Comp()(_key, _node->key_))
{
insert(_node->left_, std::move(_key));
if (Node::height(_node->left_) - Node::height(_node->right_) == )
if (Comp()(_key, _node->left_->key_))
single_left(_node);
else
double_left_right(_node);
}
else if (Comp()(_node->key_, _key))
{
insert(_node->right_, std::move(_key));
if (Node::height(_node->right_) - Node::height(_node->left_) == )
if (Comp()(_node->right_->key_, _key))
single_right(_node);
else
double_right_left(_node);
}
Node::height(_node);
} template<typename T, typename Comp>
void AvlTree<T, Comp>::single_left(Node*& _node)
{
auto temp = _node->left_;
_node->left_ = temp->right_;
temp->right_ = _node;
Node::height(_node);
Node::height(temp);
_node = temp;
} template<typename T, typename Comp>
void AvlTree<T, Comp>::single_right(Node*& _node)
{
auto temp = _node->right_;
_node->right_ = temp->left_;
temp->left_ = _node;
Node::height(_node);
Node::height(temp);
_node = temp;
} template<typename T, typename Comp>
void AvlTree<T, Comp>::double_left_right(Node*& _node)
{
single_right(_node->left_);
single_left(_node);
} template<typename T, typename Comp>
void AvlTree<T, Comp>::double_right_left(Node*& _node)
{
single_left(_node->right_);
single_right(_node);
} template<typename T, typename Comp>
AvlTree<T, Comp>::Node::Node(T&& _key)
: key_(std::move(_key))
{
;
} template<typename T, typename Comp>
int AvlTree<T, Comp>::Node::height(Node* _node)
{
if (!_node)
return ;
auto left = _node->left_ ? height(_node->left_) : ;
auto right = _node->right_ ? height(_node->right_) : ;
_node->height_ = (left > right ? left : right) + ;
return _node->height_;
} int main()
{
AvlTree<int> tree;
int num;
std::cin >> num;
for (int i = ; i != num; ++i)
{
int t;
std::cin >> t;
tree.insert(t);
}
int root;
tree.root(root);
std::cout << root;
}
1099:
给定一个二叉搜索树结构和一系列数据,让你往里填,然后层序输出。
只要可以递归,前中后序遍历都不是问题。这道题讲明了N小于等于100,就算100级递归也OK,那就当然没有必要避免递归。
等等,我不是标准库的狂热爱好者吗?都用起递归了,还怎么写迭代器啊?用递归就一定不是迭代器吗?不然。
迭代是什么?数学上,迭代表现为a=f(a);程序设计中,迭代可以是 i = i + ; ,这是很数学的写法。C/C++提供了前缀++运算符以代替这一语句,《设计模式》中的迭代器用 Next() 方法来移动到下一个位置。久而久之,迭代器的数学意义上的“迭代”已经不明显了,以至于迭代器在程序设计中似乎就是指那些能遍历容器的对象。由此,《设计模式》提出了内部迭代器与外部迭代器的概念。
内部迭代器不对外开放,由类本身控制移动,接受谓词参数。优点是实现比较方便,缺点是在那个C++98都没有的年代,C++根本不支持这个,除非紧耦合——你在《设计模式》里紧耦合?
外部迭代器是交给客户使用的,有客户控制。优点是可由客户来控制,可以同时存在多个迭代器等,缺点是实现可能很复杂,比如前面那道题的中序迭代器。
分析完这些概念后,我想在这道题中,最适合的应该是内部迭代器了吧。
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm> struct Node
{
int key;
int left;
int right;
int parent;
}; template <typename F>
void traverse(std::vector<Node>& nodes, int index, F functor)
{
auto& n = nodes[index];
if (n.left != -)
traverse(nodes, n.left, functor);
functor(n.key);
if (n.right != -)
traverse(nodes, n.right, functor);
} int main()
{
int num;
std::cin >> num;
std::vector<Node> nodes(num);
for (int i = ; i != num; ++i)
{
auto& n = nodes[i];
std::cin >> n.left >> n.right;
if (n.left != -)
nodes[n.left].parent = i;
if (n.right != -)
nodes[n.right].parent = i;
}
std::vector<int> keys(num);
for (auto& i : keys)
std::cin >> i;
std::sort(keys.begin(), keys.end());
auto iter = keys.begin();
traverse(nodes, , [&](int& key) { key = *iter++; });
std::queue<int> queue;
queue.push();
int count = ;
while (!queue.empty())
{
if (count++)
std::cout << ' ';
auto i = queue.front();
queue.pop();
auto& n = nodes[i];
std::cout << n.key;
if (n.left != -)
queue.push(n.left);
if (n.right != -)
queue.push(n.right);
}
}
我没法准确指明到底哪个东西是所谓内部迭代器。迭代器用于遍历,我只能说,函数模板 traverse 提供一个遍历的接口。和递归版本的树的遍历一样,它也会调用自身。
相比于之前复杂到看不懂的中序迭代器逻辑,这里的迭代器的功能与实现都简洁明了。既用上了递归,使实现简化,又降低了耦合,真是两全其美。
PAT甲级题分类汇编——树的更多相关文章
- PAT甲级题分类汇编——理论
本文为PAT甲级分类汇编系列文章. 理论这一类,是让我觉得特别尴尬的题,纯粹是为了考数据结构而考数据结构.看那Author一栏清一色的某老师,就知道教数据结构的老师的思路就是和别人不一样. 题号 标题 ...
- PAT甲级题分类汇编——杂项
本文为PAT甲级分类汇编系列文章. 集合.散列.数学.算法,这几类的题目都比较少,放到一起讲. 题号 标题 分数 大意 类型 1063 Set Similarity 25 集合相似度 集合 1067 ...
- PAT甲级题分类汇编——图
本文为PAT甲级分类汇编系列文章. 图,就是层序遍历和Dijkstra这一套,#include<queue> 是必须的. 题号 标题 分数 大意 时间 1072 Gas Station 3 ...
- PAT甲级题分类汇编——排序
本文为PAT甲级分类汇编系列文章. 排序题,就是以排序算法为主的题.纯排序,用 std::sort 就能解决的那种,20分都算不上,只能放在乙级,甲级的排序题要么是排序的规则复杂,要么是排完序还要做点 ...
- PAT甲级题分类汇编——计算
本文为PAT甲级分类汇编系列文章. 计算类,指以数学运算为主或为背景的题. 题号 标题 分数 大意 1058 A+B in Hogwarts 20 特殊进制加法 1059 Prime Factors ...
- PAT甲级题分类汇编——线性
本文为PAT甲级分类汇编系列文章. 线性类,指线性时间复杂度可以完成的题.在1051到1100中,有7道: 题号 标题 分数 大意 时间 1054 The Dominant Color 20 寻找出现 ...
- PAT甲级题分类汇编——序言
今天开个坑,分类整理PAT甲级题目(https://pintia.cn/problem-sets/994805342720868352/problems/type/7)中1051~1100部分.语言是 ...
- 【转载】【PAT】PAT甲级题型分类整理
最短路径 Emergency (25)-PAT甲级真题(Dijkstra算法) Public Bike Management (30)-PAT甲级真题(Dijkstra + DFS) Travel P ...
- PAT甲级题解分类byZlc
专题一 字符串处理 A1001 Format(20) #include<cstdio> int main () { ]; int a,b,sum; scanf ("%d %d& ...
随机推荐
- RabbitMQ入门学习系列(七) 远程调用RPC
快速阅读 生产者和消费者启动以后,都有一个接收事件,消费者是接收事件是处理调用方法以后等待生产者的返回,生产者的接收事件是处理接收生产者发送的消息,进行处理.消费者发送的时候要在回调队列中加入一个标识 ...
- 第07组 Alpha冲刺(1/6)
队长:杨明哲 组长博客:求戳 作业博客:求再戳 队长:杨明哲 过去两天完成了哪些任务 文字/口头描述:完成了,网页后端的大部分工作.负责了很大一部分的后端工作. 展示GitHub当日代码/文档签入记录 ...
- python 图形
import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D fig = plt ...
- c#传不确定的参数个数,比如int型
a(params int[] ) 调用时a(1,2,3,4,5,6)
- 给div添加disabled属性
<div id="CompanyId" name="CompanyId" type="select" ></div> ...
- SQL Server 将查询结果集以XML形式展现 for xml path
for xml path,其实它就是将查询结果集以XML形式展现 双击打开
- Java基础 case穿透 多个case执行同一段代码
JDK :OpenJDK-11 OS :CentOS 7.6.1810 IDE :Eclipse 2019‑03 typesetting :Markdown code ...
- Docs-.NET-C#-指南-语言参考-关键字-值类型:可以 null 的值类型
ylbtech-Docs-.NET-C#-指南-语言参考-关键字-值类型:可以 null 的值类型 1.返回顶部 1. Nullable value types (C# reference) 2019 ...
- win10更新之后vmware使用失败
1.现象 2.解决:把所有更新卸载
- 【php】PHP制作QQ微信支付宝三合一收款码
分析 微信扫这个,支付宝扫那个,不仅要加载多张二维码,还要加css/js让它变的好看,作为一个又懒又不想写这些东西的程序猿来说,这可不行. 那能不能把QQ微信支付宝三合一,只需要扫一个收款码就行呢? ...