本文为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 #include2 #include 3 #include 4 #include 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 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 #include2 #include 3 #include 4 #include 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 #include2 #include 3 #include 4 5 template > 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 31 AvlTree ::AvlTree() = default; 32 template 33 void AvlTree ::insert(T _key) 34 { 35 insert(node_, std::move(_key)); 36 } 37 38 template 39 void AvlTree ::root(T& _target) 40 { 41 if (node_) 42 _target = node_->key_; 43 } 44 45 template 46 void AvlTree ::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 72 void AvlTree ::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 83 void AvlTree ::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 94 void AvlTree ::double_left_right(Node*& _node) 95 { 96 single_right(_node->left_); 97 single_left(_node); 98 } 99 100 template 101 void AvlTree ::double_right_left(Node*& _node) 102 { 103 single_left(_node->right_); 104 single_right(_node); 105 } 106 107 template 108 AvlTree ::Node::Node(T&& _key) 109 : key_(std::move(_key)) 110 { 111 ; 112 } 113 114 template 115 int AvlTree ::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 #include2 #include 3 #include 4 #include 5 6 struct Node 7 { 8 int key; 9 int left; 10 int right; 11 int parent; 12 }; 13 14 template 15 void traverse(std::vector & 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 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 提供一个遍历的接口。和递归版本的树的遍历一样,它也会调用自身。
相比于之前复杂到看不懂的中序迭代器逻辑,这里的迭代器的功能与实现都简洁明了。既用上了递归,使实现简化,又降低了耦合,真是两全其美。