PAT甲級題分類彙編——樹

本文爲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 提供一個遍歷的接口。和遞歸版本的樹的遍歷同樣,它也會調用自身。

相比於以前複雜到看不懂的中序迭代器邏輯,這裏的迭代器的功能與實現都簡潔明瞭。既用上了遞歸,使實現簡化,又下降了耦合,真是一箭雙鵰。

相關文章
相關標籤/搜索