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的东西,后来跑样例结果不对,才发现是根节点到叶节点,反而更简单了。

 1 #include <iostream>
 2 #include <vector>
 3 #include <queue>
 4 #include <algorithm>
 5
 6 struct Node
 7 {
 8     int index;
 9     int weight;
10     std::vector<int> children;
11     std::vector<int> weights;
12     int total;
13 };
14
15 int main()
16 {
17     int num_node, num_nonleaf, target;
18     std::cin >> num_node >> num_nonleaf >> target;
19     std::vector<Node> nodes(num_node);
20     for (auto& n : nodes)
21         std::cin >> n.weight;
22     for (int i = 0; i != num_node; ++i)
23         nodes[i].index = i;
24     for (int i = 0; i != num_nonleaf; ++i)
25     {
26         int index;
27         std::cin >> index;
28         auto& node = nodes[index];
29         int count;
30         std::cin >> count;
31         node.children.resize(count);
32         for (auto& i : node.children)
33             std::cin >> i;
34     }
35     if (nodes.front().weight == target)
36     {
37         std::cout << target;
38         return 0;
39     }
40     nodes.front().total = nodes.front().weight;
41     nodes.front().weights.push_back(nodes.front().weight);
42     std::queue<int> queue;
43     std::vector<int> selected;
44     queue.push(0);
45     while (!queue.empty())
46     {
47         auto& node = nodes[queue.front()];
48         queue.pop();
49         for (auto& i : node.children)
50         {
51             auto& n = nodes[i];
52             n.weights = node.weights;
53             n.weights.push_back(n.weight);
54             n.total = node.total + n.weight;
55             if (n.total == target && n.children.empty())
56                 selected.push_back(n.index);
57             else if (n.total < target)
58                 queue.push(n.index);
59         }
60     }
61     std::sort(selected.begin(), selected.end(), [&](int i, int j) {
62         return nodes[i].weights > nodes[j].weights;
63     });
64     for (const auto& i : selected)
65     {
66         auto& node = nodes[i];
67         auto end = node.weights.end() - 1;
68         for (auto iter = node.weights.begin(); iter != end; ++iter)
69             std::cout << *iter << ‘ ‘;
70         std::cout << *end;
71         std::cout << std::endl;
72     }
73 }

一个小坑是根节点权重就是要求的值,而我的算法总是处理当前节点的子节点,而根节点没有前驱节点,所以要加个特殊情况讨论。一个case而已,想了两分钟就发现了。

1064:

作为标准库的狂热爱好者,我写的数据结构最好也要有迭代器,这道题是绝佳的平台。层序遍历迭代器就用 std::vector 的迭代器就行,中序遍历迭代器得自己写。

 1 #include <iostream>
 2 #include <vector>
 3 #include <algorithm>
 4 #include <queue>
 5
 6 class InOrderIterator
 7 {
 8 public:
 9     InOrderIterator(std::vector<int>& _rVector)
10         : data_(_rVector)
11     {
12         auto s = data_.size() - 1;
13         int count = 0;
14         while (s >>= 1)
15             ++count;
16         index_ = 1 << count;
17     }
18     int& operator*()
19     {
20         return data_[index_];
21     }
22     InOrderIterator& operator++()
23     {
24         if (index_ * 2 + 1 < data_.size())
25         {
26             index_ = index_ * 2 + 1;
27             while ((index_ *= 2) < data_.size())
28                 ;
29             index_ /= 2;
30         }
31         else
32         {
33             int count = 1, i = index_;
34             while (i % 2)
35                 i >>= 1, ++count;
36             index_ >>= count;
37         }
38         return *this;
39     }
40 private:
41     std::vector<int>& data_;
42     int index_;
43 };
44
45 int main(int argc, char const *argv[])
46 {
47     int n;
48     std::cin >> n;
49     std::vector<int> data(n);
50     for (int i = 0; i != n; ++i)
51         std::cin >> data[i];
52
53     std::sort(data.begin(), data.end());
54     std::vector<int> tree(n + 1);
55     InOrderIterator iter(tree);
56     for (auto i : data)
57         *iter = i, ++iter;
58
59     auto size = tree.size() - 1;
60     for (auto i = 1; i != size; ++i)
61         std::cout << tree[i] << ‘ ‘;
62     std::cout << tree[size];
63
64     return 0;
65 }

这个迭代算法我想了好久,当时觉得很优雅。现在自己都看不懂,好像是什么位操作吧。

然而,同样是迭代,1099题中的算法就比这个简单得多,后面会讲。

1066:

这道题我一直不敢做。实际上,左旋右旋什么的在我的脑海中一直都只是名词——我从未实现过一个AVL树,直到今天。在PPT与代码的帮助下,我顺利地写了一个AvlTree类模板,实现了一部分接口。

AVL树的4种旋转,学的时候听得懂,写的时候觉得烦,真的写完了也不过如此,其实没什么复杂的。

  1 #include <iostream>
  2 #include <utility>
  3 #include <functional>
  4
  5 template <typename T, typename Comp = std::less<T>>
  6 class AvlTree
  7 {
  8 public:
  9     AvlTree();
 10     void insert(T _key);
 11     void root(T& _target);
 12 private:
 13     struct Node
 14     {
 15         Node(T&& _key);
 16         T key_;
 17         Node* left_ = nullptr;
 18         Node* right_ = nullptr;
 19         int height_;
 20         static int height(Node* _node);
 21     };
 22     Node* node_ = nullptr;
 23     static void insert(Node*& _node, T&& _key);
 24     static void single_left(Node*& _node);
 25     static void single_right(Node*& _node);
 26     static void double_left_right(Node*& _node);
 27     static void double_right_left(Node*& _node);
 28 };
 29
 30 template<typename T, typename Comp>
 31 AvlTree<T, Comp>::AvlTree() = default;
 32 template<typename T, typename Comp>
 33 void AvlTree<T, Comp>::insert(T _key)
 34 {
 35     insert(node_, std::move(_key));
 36 }
 37
 38 template<typename T, typename Comp>
 39 void AvlTree<T, Comp>::root(T& _target)
 40 {
 41     if (node_)
 42         _target = node_->key_;
 43 }
 44
 45 template<typename T, typename Comp>
 46 void AvlTree<T, Comp>::insert(Node*& _node, T&& _key)
 47 {
 48     if (!_node)
 49         _node = new Node(std::move(_key));
 50     else if (Comp()(_key, _node->key_))
 51     {
 52         insert(_node->left_, std::move(_key));
 53         if (Node::height(_node->left_) - Node::height(_node->right_) == 2)
 54             if (Comp()(_key, _node->left_->key_))
 55                 single_left(_node);
 56             else
 57                 double_left_right(_node);
 58     }
 59     else if (Comp()(_node->key_, _key))
 60     {
 61         insert(_node->right_, std::move(_key));
 62         if (Node::height(_node->right_) - Node::height(_node->left_) == 2)
 63             if (Comp()(_node->right_->key_, _key))
 64                 single_right(_node);
 65             else
 66                 double_right_left(_node);
 67     }
 68     Node::height(_node);
 69 }
 70
 71 template<typename T, typename Comp>
 72 void AvlTree<T, Comp>::single_left(Node*& _node)
 73 {
 74     auto temp = _node->left_;
 75     _node->left_ = temp->right_;
 76     temp->right_ = _node;
 77     Node::height(_node);
 78     Node::height(temp);
 79     _node = temp;
 80 }
 81
 82 template<typename T, typename Comp>
 83 void AvlTree<T, Comp>::single_right(Node*& _node)
 84 {
 85     auto temp = _node->right_;
 86     _node->right_ = temp->left_;
 87     temp->left_ = _node;
 88     Node::height(_node);
 89     Node::height(temp);
 90     _node = temp;
 91 }
 92
 93 template<typename T, typename Comp>
 94 void AvlTree<T, Comp>::double_left_right(Node*& _node)
 95 {
 96     single_right(_node->left_);
 97     single_left(_node);
 98 }
 99
100 template<typename T, typename Comp>
101 void AvlTree<T, Comp>::double_right_left(Node*& _node)
102 {
103     single_left(_node->right_);
104     single_right(_node);
105 }
106
107 template<typename T, typename Comp>
108 AvlTree<T, Comp>::Node::Node(T&& _key)
109     : key_(std::move(_key))
110 {
111     ;
112 }
113
114 template<typename T, typename Comp>
115 int AvlTree<T, Comp>::Node::height(Node* _node)
116 {
117     if (!_node)
118         return 0;
119     auto left = _node->left_ ? height(_node->left_) : 0;
120     auto right = _node->right_ ? height(_node->right_) : 0;
121     _node->height_ = (left > right ? left : right) + 1;
122     return _node->height_;
123 }
124
125 int main()
126 {
127     AvlTree<int> tree;
128     int num;
129     std::cin >> num;
130     for (int i = 0; i != num; ++i)
131     {
132         int t;
133         std::cin >> t;
134         tree.insert(t);
135     }
136     int root;
137     tree.root(root);
138     std::cout << root;
139 }

1099:

给定一个二叉搜索树结构和一系列数据,让你往里填,然后层序输出。

只要可以递归,前中后序遍历都不是问题。这道题讲明了N小于等于100,就算100级递归也OK,那就当然没有必要避免递归。

等等,我不是标准库的狂热爱好者吗?都用起递归了,还怎么写迭代器啊?用递归就一定不是迭代器吗?不然。

迭代是什么?数学上,迭代表现为a=f(a);程序设计中,迭代可以是 i = i + 1; ,这是很数学的写法。C/C++提供了前缀++运算符以代替这一语句,《设计模式》中的迭代器用 Next() 方法来移动到下一个位置。久而久之,迭代器的数学意义上的“迭代”已经不明显了,以至于迭代器在程序设计中似乎就是指那些能遍历容器的对象。由此,《设计模式》提出了内部迭代器与外部迭代器的概念。

内部迭代器不对外开放,由类本身控制移动,接受谓词参数。优点是实现比较方便,缺点是在那个C++98都没有的年代,C++根本不支持这个,除非紧耦合——你在《设计模式》里紧耦合?

外部迭代器是交给客户使用的,有客户控制。优点是可由客户来控制,可以同时存在多个迭代器等,缺点是实现可能很复杂,比如前面那道题的中序迭代器。

分析完这些概念后,我想在这道题中,最适合的应该是内部迭代器了吧。

 1 #include <iostream>
 2 #include <vector>
 3 #include <queue>
 4 #include <algorithm>
 5
 6 struct Node
 7 {
 8     int key;
 9     int left;
10     int right;
11     int parent;
12 };
13
14 template <typename F>
15 void traverse(std::vector<Node>& nodes, int index, F functor)
16 {
17     auto& n = nodes[index];
18     if (n.left != -1)
19         traverse(nodes, n.left, functor);
20     functor(n.key);
21     if (n.right != -1)
22         traverse(nodes, n.right, functor);
23 }
24
25 int main()
26 {
27     int num;
28     std::cin >> num;
29     std::vector<Node> nodes(num);
30     for (int i = 0; i != num; ++i)
31     {
32         auto& n = nodes[i];
33         std::cin >> n.left >> n.right;
34         if (n.left != -1)
35             nodes[n.left].parent = i;
36         if (n.right != -1)
37             nodes[n.right].parent = i;
38     }
39     std::vector<int> keys(num);
40     for (auto& i : keys)
41         std::cin >> i;
42     std::sort(keys.begin(), keys.end());
43     auto iter = keys.begin();
44     traverse(nodes, 0, [&](int& key) { key = *iter++; });
45     std::queue<int> queue;
46     queue.push(0);
47     int count = 0;
48     while (!queue.empty())
49     {
50         if (count++)
51             std::cout << ‘ ‘;
52         auto i = queue.front();
53         queue.pop();
54         auto& n = nodes[i];
55         std::cout << n.key;
56         if (n.left != -1)
57             queue.push(n.left);
58         if (n.right != -1)
59             queue.push(n.right);
60     }
61 }

我没法准确指明到底哪个东西是所谓内部迭代器。迭代器用于遍历,我只能说,函数模板 traverse 提供一个遍历的接口。和递归版本的树的遍历一样,它也会调用自身。

相比于之前复杂到看不懂的中序迭代器逻辑,这里的迭代器的功能与实现都简洁明了。既用上了递归,使实现简化,又降低了耦合,真是两全其美。

原文地址:https://www.cnblogs.com/jerry-fuyi/p/11450788.html

时间: 2024-10-08 21:15:37

PAT甲级题分类汇编——树的相关文章

PAT甲级题分类汇编——线性

线性类,指线性时间复杂度可以完成的题.在1051到1100中,有7道: 题号 标题 分数 大意 时间 1054 The Dominant Color 20 寻找出现最多的数 200ms 1061 Dating 20 寻找字符串中相同字符 200ms 1071 Speech Patterns 25 寻找出现最多的单词 300ms 1077 Kuchiguse 20 字符串共同后缀 150ms 1082 Read Number in Chinese 25 中文读数 400ms 1084 Broken

PAT甲级题分类汇编——理论

理论这一类,是让我觉得特别尴尬的题,纯粹是为了考数据结构而考数据结构.看那Author一栏清一色的某老师,就知道教数据结构的老师的思路就是和别人不一样. 题号 标题 分数 大意 Author 1051 Pop Sequence 25 判断一个序列是否是pop序列 CHEN, Yue 1052 Linked List Sorting 25 链表排序 CHEN, Yue 1057 Stack 30 一个有中位数功能的stack CHEN, Yue 1074 Reversing Linked List

PAT甲级题分类汇编——杂项

集合.散列.数学.算法,这几类的题目都比较少,放到一起讲. 题号 标题 分数 大意 类型 1063 Set Similarity 25 集合相似度 集合 1067 Sort with Swap(0, i) 25 通过与0号元素交换来排序 数学 1068 Find More Coins 30 子集和问题 算法 1070 Mooncake 25 背包问题 算法 1078 Hashing 25 散列 散列 1085 Perfect Sequence 25 符合约束的最大数列长度 集合 1092 To

PAT甲级题分类汇编——计算

计算类,指以数学运算为主或为背景的题. 题号 标题 分数 大意 1058 A+B in Hogwarts 20 特殊进制加法 1059 Prime Factors 25 分解素因数 1060 Are They Equal 25 一定精度下两数是否相等 1065 A+B and C (64bit) 20 大数加法与比较 1069 The Black Hole of Numbers 20 黑洞数 1073 Scientific Notation 20 科学计数法还原 1081 Rational Su

PAT甲级1057 Stack【树状数组】【二分】

题目:https://pintia.cn/problem-sets/994805342720868352/problems/994805417945710592 题意:对一个栈进行push, pop和找中位数三种操作. 思路: 好久没写题.感觉傻逼题写多了稍微有点数据结构的都不会写了. pop和push操作就不说了. 找中位数的话就二分去找某一个数前面一共有多少小于他的数,找到那个小于他的数刚好等于一半的. 找的过程中要用到前缀和,所以自然而然就应该上树状数组. 要注意树状数组的界应该是1e5而

PAT甲级题

A 1001 #include<cstdio> int main(){ int a,b,res[10]; long long sum; scanf("%d%d",&a,&b); sum=a+b; if(sum<0){ sum=-sum; printf("-"); } int i=0; if(sum==0) printf("0"); while(sum>0){ res[i++]=sum%10; sum=sum

PAT甲级考前整理

终于在考前,刷完PAT甲级130道题目,不容易!!!每天沉迷在刷题之中而不能超脱,也是一种境界.PAT甲级题目总的说卡题目的比较多,卡测试点的比较少,有些题目还会有题意混淆,这点就不吐槽了吧.静下心来耍这130道题,其实磨练的是一种态度与手感,养成的是一种习惯.热爱AC没有错!! 130道题目主要的考点: 1.排序:快速排序,直接插入排序,希尔排序,分治排序,堆排序. 2.图论:拓扑排序.最短路径.深度搜索.广度搜索. 3.树:树的遍历.完全二叉树.AVL. 4.其他:并查集,模拟,哈希.背包.

PAT甲级1005 Spell It Right

题目:PAT甲级 1005 题解:水题.看到题目的第一时间就在想一位一位的mod,最后一加一转换就完事了.结果看到了N最大为10的100的次方,吓得我赶紧放弃这个想法... 发现碰到这种情况用字符串十分好用,这道题应该考察的就是这一点.大致思路就是把数字的每一位放到字符串中,然后通过ASCII码得到每一位的相加结果num,然后把num一位一位的放到stack中,使用stack是因为它先进先出的特性,最后输出就行了. 代码: 1 #include<cstdio> 2 #include<qu

刷过的题 分类汇总

需要复习的算法题分类 二分搜索 ? 二叉树与分治part1:?    part2 ? 三种遍历 前序遍历 中序遍历 后续遍历 动态规划 ? 序列 dd dddd ddd linkedlist  ? array 中位数 median,quick sort subarray two pointers partition array 数据结构   part1 queue heap hashmap 并查集 trie树 扫描线 图搜索 回溯算法 part1  ? part2 ? 9. 各种排序 1. 基于