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& ...
随机推荐
- Flutter扫码识别二维码内容
前面一篇写了生成二维码图片,这篇来写使用相机扫描识别二维码 识别二维码需要用到插件 barcode_scan 首先在 pubspec.yaml 文件中添加以下依赖,添加依赖后在 pubspec.yam ...
- C# ffmpeg 视频处理格式转换具体案例
C# ffmpeg 视频处理格式转换 C# ffmpeg 视频处理格式转换avi到MP4格式 1.代码如下: using System;using System.Diagnostics; namesp ...
- 【转】45个实用的JavaScript技巧、窍门和最佳实践
原文:https://colobu.com/2014/09/23/45-Useful-JavaScript-Tips,-Tricks-and-Best-Practices/ 目录 [−] 列表 第一次 ...
- [转]arcgis for server 10.2 下载及安装
转自:https://blog.csdn.net/nominior/article/details/80211963 https://blog.csdn.net/mrib/article/detail ...
- openresty开发系列12--lua介绍及常用数据类型简介
openresty开发系列12--lua介绍及常用数据类型简介 lua介绍 1993 年在巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de ...
- ISO/IEC 9899:2011 条款6.5.15——条件操作符
6.5.15 条件操作符 语法 1.conditional-expression: logical-OR-expression logical-OR-expression ? expres ...
- Dart中的类型转换总结:
1.Dart中数组转换为字符串:join var a=[1,2,3,4]; var str=a.join(',');
- wpf日期控件
/// <summary> /// Value converter to convert a datetime object to the specified string format. ...
- xml文档操作
/** * */package com.gootrip.util; import java.io.ByteArrayOutputStream;import java.io.File;import ja ...
- 【Mybatis】MyBatis之配置多数据源(十)
在做项目的过程中,有时候一个数据源是不够,那么就需要配置多个数据源.本例介绍mybatis多数据源配置 前言 一般项目单数据源,使用流程如下: 单个数据源绑定给sessionFactory,再在Dao ...