本文爲PAT甲級分類彙編系列文章。html
AVL樹好難!(其實還好啦~)node
我原本想着今天應該作不完樹了,沒想到電腦裏有一份講義,PPT和源代碼都有,就一遍複習一遍抄碼了一遍,更沒想到的是編譯一遍經過,再沒想到的是運行也正常,最沒想到的是一遍AC。ios
其實不少題都有數,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,別提有多爽了。less
1053:函數
要求按非升序輸出權重,使其和爲給定值。ui
一開始沒看清題,覺得是根節點到任意節點,就寫了個有點像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 提供一個遍歷的接口。和遞歸版本的樹的遍歷同樣,它也會調用自身。
相比於以前複雜到看不懂的中序迭代器邏輯,這裏的迭代器的功能與實現都簡潔明瞭。既用上了遞歸,使實現簡化,又下降了耦合,真是一箭雙鵰。