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 提供一个遍历的接口。和递归版本的树的遍历一样,它也会调用自身。
相比于之前复杂到看不懂的中序迭代器逻辑,这里的迭代器的功能与实现都简洁明了。既用上了递归,使实现简化,又降低了耦合,真是两全其美。