1

Q #2 is renewed. Please answer new Q #2! --Dannyu NDos, 2017 Jan 17

I've been making an associative container named DRV, which represents a finite discrete random variable. It is a red-black tree. I got helps from standard std::map, but I also had confusion from it.

Q #1. How its copy ctor has O(n) time complexity? Shouldn't it be O(n log n)? My DRV's copy ctor has O(log n), using std::async, though.

Old Q #2. Why its default allocator is std::allocator<value_type>? Shoudn't it allocate the container's internal node type? In that case, the values won't need to be dynamically allocated individually.

New Q #2. Given that Alloc is the allocator type for the container, what allocator must the container hold, Alloc or typename std::allocator_traits<Alloc>::template rebind_alloc</*the type of the node*/>?

Dannyu NDos
  • 2,458
  • 13
  • 32
  • If your container's copy constructor has O(log n) complexity, doesn't that mean it can only copy some of the values, instead of all N? – eerorika Feb 05 '16 at 07:55
  • As to copy ctor complexity, here: http://www.cplusplus.com/reference/map/map/map/ we can read that complexity is "For unsorted sequences, linearithmic (N*logN) in that distance (sorting,copy constructions)" – Piotr Smaroń Feb 05 '16 at 08:02
  • @user2079303 Mine uses std::async, which will asyncronously copy values at same rank at same time. – Dannyu NDos Feb 05 '16 at 08:13
  • @DannyuNDos do you mean same level/depth? That won't remove any work that needs to be done. You'd need to have log(n) processor cores, where n is the size of the tree, to run all of your tasks in parallel. And the results will have to be syncronized in commonly shared memory anyway and the memory writing is the heaviest part of the copying. – eerorika Feb 05 '16 at 08:20
  • @user2079303 Woah... That must be a problem. Thank you. My computer has 8 processors, though. – Dannyu NDos Feb 05 '16 at 08:32

2 Answers2

1
  1. Copy constructor does not need to sort, it just needs to copy N nodes thus O(N)
  2. std::allocator<value_type> is "rebound" (search for "rebind" here) inside to allocate map nodes (value + tree wiring data)
bobah
  • 18,364
  • 2
  • 37
  • 70
0

My own answer to question #1: I did this like this:
1. Copy the (container size / 2) elements at the beginning recursively, with color all black.
2. Copy the following element, which is the centermost element and will be the root element, with color black.
3. Copy the remaining element at the end recursively, with color all black. 4. Connect the element copied in 2. with the root element among the elements copied in 1.
5. Connect the element copied in 2. with the root element among the elements copied in 3.
6. Paint the deepest elements red.
For example: https://i.stack.imgur.com/1wMrQ.jpg
This will make a perfectly balanced red-black tree in O(n) time.
And even if I parallelize this using std::async, it still has O(n) time complexity, due to the Amdahl's Law.
Here is the implementation:

/* includes... */
template <class T, class Comp = std::less<T>, class Alloc = std::allocator<T>> class set { // A red-black tree, motivated from std::set
public:
    /* typedefs... */
protected:
    typedef RedBlackTreeNode<T> Nodev; // The node type
    typedef Nodev *pNodev; // The pointer to node type
    typedef typename std::allocator_traits<Alloc>::template rebind_alloc<Nodev> Rebounda; // Rebound allocator type
    typedef std::allocator_traits<Rebounda> Reboundat; // Rebound allocator traits
    mutable ListNode_e endn; // The past-the-end node
    size_type sz; // Size
    value_compare comp; // comparator
    Rebounda alloc; // allocator
    pNodev initp() const noexcept { // the pointer to the node at the end
        return static_cast<pNodev>(endn.pre);
    }
    void partial_clear(const_iterator i) noexcept { // Destructs the node at i and after
        while (cend() != i) {
            pNodev p = static_cast<pNodev>(i++.refer);
            Reboundat::destroy(alloc, p);
            Reboundat::deallocate(alloc, p, 1);
        }
    }
    /// partial_copy_Lvalue, partial_assign_Lvalue
    /// @steps
    ///    1. Copy the (container size / 2) elements at the beginning recursively, with color all black.
    ///    2. Copy the following element, which is the centermost element and will be the root element, with color black.
    ///    3. Copy the remaining element at the end recursively, with color all black.
    ///    4. Connect the element copied in 2. with the root element among the elements copied in 1.
    ///    5. Connect the element copied in 2. with the root element among the elements copied in 3.
    ///    6. Paint the deepest elements red.
    /// @param
    ///    size: the size of the container
    ///    other_i: the iterator refering the element to be copied in 2.
    /// @return
    ///    pNodev: the pointer to the root of the (partial) tree
    ///    size_type: the depth of the shallowest leaf node of the (partial) tree
    std::pair<pNodev, size_type> partial_copy_Lvalue(size_type size, const_iterator &&other_i) {
        if (size == 0)
            return std::make_pair(nullptr, 0);
        else {
            std::pair<pNodev, size_type> l = partial_copy_Lvalue(size >> 1, std::move(other_i)); /// 1.
            Reboundat::construct(alloc, Reboundat::allocate(alloc, 1), endn, Nodev::color_t::black, *other_i++); /// 2.
            pNodev resultp = initp();
            std::pair<pNodev, size_type> r = partial_copy_Lvalue(size - (size >> 1) - 1, std::move(other_i)); /// 3.
            resultp->left = l.first; /// 4.
            resultp->right = r.first; /// 5.
            if (resultp->left)
                resultp->left->parent = resultp; /// 4.
            if (resultp->right)
                resultp->right->parent = resultp; /// 5.
            if (l.second < r.second && resultp->right)
                resultp->right->colorleavesred(); /// 6, case 1
            else if (l.second > r.second && resultp->left)
                resultp->left->colorleavesred(); /// 6, case 2
            return std::make_pair(resultp, std::min(l.second, r.second) + 1);
        }
    }
    void copy(const set &other) { // Copies the nodes from other. Precondition : this is empty
        if (!other.empty())
            partial_copy_Lvalue(other.sz, other.cbegin());
    }
    std::pair<pNodev, size_type> partial_assign_Lvalue(size_type size, ListIt<T> &&this_i,
        const_iterator &&other_i, const const_iterator &other_cend) {
        if (size == 0) {
            if (other_i == other_cend)
                partial_clear(const_iterator(this_i.refer));
            return std::make_pair(nullptr, 0);
        } else {
            std::pair<pNodev, size_type> l = partial_assign_Lvalue(size >> 1, std::move(this_i), std::move(other_i), other_cend); /// 1.
            std::pair<pNodev, size_type> r;
            pNodev resultp;
            if (this_i.refer == cend().refer) {
                Reboundat::construct(alloc, Reboundat::allocate(alloc, 1), endn, Nodev::color_t::black, *other_i++); /// 2, case 1
                resultp = initp();
                r = partial_copy_Lvalue(size - (size >> 1) - 1, std::move(other_i)); /// 3, case 1
            } else {
                resultp = static_cast<pNodev>(this_i.refer);
                static_cast<pNodev>(this_i.refer)->parent = nullptr;
                static_cast<pNodev>(this_i.refer)->color = Nodev::color_t::black;
                *this_i++ = *other_i++; /// 2, case 2
                r = partial_assign_Lvalue(size - (size >> 1) - 1, std::move(this_i), std::move(other_i), other_cend); /// 3, case 2
            }
            resultp->left = l.first; /// 4.
            resultp->right = r.first; /// 5.
            if (resultp->left)
                resultp->left->parent = resultp; /// 4.
            if (resultp->right)
                resultp->right->parent = resultp; /// 5.
            if (l.second < r.second && resultp->right)
                resultp->right->colorleavesred(); /// 6, case 1
            else if (l.second > r.second && resultp->left)
                resultp->left->colorleavesred(); /// 6, case 2
            return std::make_pair(resultp, std::min(l.second, r.second) + 1);
        }
    }
public:
    /* constructors... */
    virtual ~set() {
        clear();
    }
    set &operator = (const set &other) {
        sz = other.sz;
        if (std::allocator_traits<Alloc>::propagate_on_container_copy_assignment::value && alloc != other.alloc) {
            clear();
            alloc = other.alloc;
            copy(other);
        } else
            partial_assign_Lvalue(other.sz, ListIt<T>(cbegin().refer), other.cbegin(), other.cend());
        return *this;
    }
    /* ... */
};

See also: How is allocator-aware container assignment implemented?

Community
  • 1
  • 1
Dannyu NDos
  • 2,458
  • 13
  • 32