我正在尝试编写一个玩具树实现。由于std::map
是作为树实现的,因此我想了解如何构建树迭代器。除此以外,我没有实现任何功能,只是一个插入函数和一个迭代器类。目标是,当您在从begin()
到end()
的树上进行for循环时,我必须进行有序遍历。代码如下:
#include <type_traits>
#include <utility>
#include <vector>
#include <algorithm>
#include <iostream>
#include <map>
#include <chrono>
#include <random>
namespace detail {}
template <typename T>
struct tree;
template <typename T, bool Const>
struct tree_iterator;
namespace detail {
template <typename T, bool Const>
tree_iterator<T, Const> sink{nullptr};
}
template <typename T>
struct tree_node {
T data{};
tree_node<T> *left = nullptr;
tree_node<T> *right = nullptr;
tree_node<T> *parent = nullptr;
bool visited = false;
};
template <typename T, bool Const>
struct tree_iterator {
friend class tree<T>;
friend class tree_iterator<T, !Const>;
using node_pointer = std::conditional_t<Const,
const tree_node<T>*,
tree_node<T>*>;
using reference = std::conditional_t<Const, const T&, T&>;
reference operator*() const { return ptr_->data; }
/* notice that this implementation requires begin() points
* to leftmost element of the tree
*/
tree_iterator& operator++() {
if(ptr_ == nullptr) {
return detail::sink<T, Const>;
}
ptr_->visited = true;
if(is_leaf()) {
backtrack();
} else if(is_single_parent()) {
if(ptr_->left) ptr_ = ptr_->left;
if(ptr_->right) ptr_ = ptr_->right;
}
else {
descent_to_next_leaf();
}
return *this;
}
tree_iterator operator++(int) {
auto i = *this; ++*this; return i;
}
template<bool R>
bool operator==(const tree_iterator<T, R>& rhs) const
{ return ptr_ == rhs.ptr_; }
template<bool R>
bool operator!=(const tree_iterator<T,R>& rhs) const
{ return ptr_ != rhs.ptr_; }
operator tree_iterator<T, true>() const
{ return tree_iterator<T, true>{ptr_}; }
explicit tree_iterator(node_pointer p) : ptr_(p) {}
private:
bool is_leaf() {
return ptr_->left == nullptr && ptr_->right == nullptr;
}
bool is_single_parent() {
return (ptr_->left == nullptr || ptr_->right == nullptr) &&
!(ptr_->left == nullptr && ptr_->right == nullptr);
}
void descent_to_next_leaf() {
while(!is_leaf() && !is_single_parent()) {
if(ptr_->left && !ptr_->left->visited) {
ptr_ = ptr_->left;
}
else if(ptr_->right && !ptr_->right->visited) {
ptr_ = ptr_->right;
}
}
}
void backtrack() {
while(ptr_->parent->visited) {
if(ptr_->parent->parent == nullptr) {
ptr_ = ptr_->parent;
break;
}
ptr_ = ptr_->parent;
}
ptr_ = ptr_->parent;
}
node_pointer ptr_;
};
template <typename T>
struct tree {
using const_iterator = tree_iterator<T, true>;
using iterator = tree_iterator<T, false>;
void insert(T val) {
tree_node<T> *n = new tree_node<T>;
n->data = std::move(val);
n->visited = false;
if(root == nullptr) {
n->left = nullptr;
n->right = nullptr;
n->parent = nullptr;
root = n;
smallest = n;
} else {
tree_node<T> *cur = root;
tree_node<T> * back = cur;
std::vector<bool> path; // true = left
while(cur != nullptr) {
back = cur;
if(val <= cur->data) {
cur = cur->left;
path.push_back(true);
} else {
cur = cur->right;
path.push_back(false);
}
}
n->parent = back;
n->right = nullptr;
n->left = nullptr;
if(path.back())
back->left = n;
else
back->right = n;
if(std::all_of(path.begin(), path.end(),
[](bool x){ return x; }))
smallest = n;
}
}
iterator begin() { return tree_iterator<T, false>(smallest); }
iterator end() { return detail::sink<T, false>; }
const_iterator begin() const { return tree_iterator<T, true>(smallest); }
const_iterator end() const { return detail::sink<T, true>; }
private:
tree_node<T> *root = nullptr;
tree_node<T> *smallest = nullptr;
};
const int BENCH_SIZE = 10000;
static void custom_tree_map() {
std::random_device rd;
std::mt19937 eng(rd());
std::uniform_int_distribution<> distr(0, BENCH_SIZE);
using kvp = std::pair<std::size_t, float>;
tree<kvp> map;
std::size_t count = 0;
while(count < BENCH_SIZE) {
std::size_t s = distr(eng);
kvp e{s, static_cast<float>(distr(eng))};
map.insert(e);
count++;
}
float x = 0.0;
for(auto& elem : map)
x = elem.first * elem.second;
std::cout << x << "\n";
}
static void std_tree_map() {
std::random_device rd;
std::mt19937 eng(rd());
std::uniform_int_distribution<> distr(0, BENCH_SIZE);
std::map<std::size_t, float> m;
std::size_t count = 0;
while(count < BENCH_SIZE) {
std::size_t s = distr(eng);
m[s] = static_cast<float>(distr(eng));
count++;
}
float y = 0.0;
for(auto& elem : m)
y = elem.first * elem.second;
std::cout << y << "\n";
}
int main() {
std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();
custom_tree_map();
std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now();
auto duration1 = std::chrono::duration_cast<std::chrono::microseconds>( t2 - t1 ).count();
std::cout << "Execution time of custom map " << duration1 << " ms\n";
std::chrono::high_resolution_clock::time_point tt1 = std::chrono::high_resolution_clock::now();
custom_tree_map();
std::chrono::high_resolution_clock::time_point tt2 = std::chrono::high_resolution_clock::now();
auto duration2 = std::chrono::duration_cast<std::chrono::microseconds>( tt2 - tt1 ).count();
std::cout << "Execution time of std map " << duration2 << " ms\n";
}
免责声明:代码不适用于某些随机插入顺序。我的operator++
实现中可能存在错误。我会调查一下。
我还添加了一个简单的基准和时间安排。有些结果我似乎无法理解。程序运行时,性能不会变差。在大多数情况下,结果类似于std::map
。这使我想知道树迭代器的operator++
的复杂性。如您所见,我偶尔需要回溯并找到正确的叶子等。这些取决于树的高度。因此,我的猜测是我的operator++
是O(logn)。但是,我知道有一种方法可以在O(n)时间内遍历树。这表明我应该也有一种方法来实现恒定时间增量运算符。我无法提出一个,但是我希望std::map
拥有O(1)operator++
,而且这个时机会成倍地增长。这里有什么收获,我们如何为树迭代器实现恒定时间operator++
?