我无法掌握的树实现的快速基准测试

时间:2018-07-22 10:33:07

标签: c++

我正在尝试编写一个玩具树实现。由于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++

0 个答案:

没有答案