Heapify in logarithmic time using the C++ standard library

前端 未结 4 387
灰色年华
灰色年华 2021-01-06 00:35

I have a heap using std::make_heap:

std::vector v{1,2,3,5,9,20,3};
std::make_heap(v.begin(), v.end());

now I update

相关标签:
4条回答
  • 2021-01-06 00:52

    I have been facing this problem of wanting an "updateable heap" as well. However, in the end, instead of coding a custom updateable heap or anything like that, I solved it a bit differently.

    To maintain access to the best element without needing to explicitly go through the heap, you can use versioned wrappers of the elements that you want to order. Each unique, true element has a version counter, which is increased every time the element gets changed. Each wrapper inside the heap then carries a version of the element, being the version at the time the wrapper was created:

    struct HeapElemWrapper
    {
        HeapElem * e;
    
        size_t version;        
        double priority;
    
        HeapElemWrapper(HeapElem * elem)
         : e(elem), version(elem->currentVersion), priority(0.0)
        {}
    
        bool upToDate() const
        {
            return version == e->currentVersion;
        }
    
        // operator for ordering with heap / priority queue:
        // smaller error -> higher priority
        bool operator<(const HeapElemWrapper & other) const
        {
            return this->priority> other.priority;
        }
    };
    

    When popping the topmost element from the heap, you can then simply check this wrapper element to see if it's up-to-date with the original. If not, simply dispose it and pop the next one. This method is quite efficient, and I have it seen in other applications as well. The only thing you need to take care of is that you do a pass over the heap to clean it up from outdated elements, from time to time (say, every 1000 insertions or so).

    0 讨论(0)
  • 2021-01-06 01:05

    It's not possible to modify an arbitrary element of the heap in logarithmic running time without violating the heap property by just using the function templates std::pop_heap() and std::push_heap() that the standard library provides.

    However, you can define your own STL-like function template, set_heap_element(), for that purpose:

    template<typename RandomIt, typename T, typename Cmp>
    void set_heap_element(RandomIt first, RandomIt last, RandomIt pos, T value, Cmp cmp)
    {
        const auto n = last - first;
        *pos = std::move(value); // replace previous value
    
        auto i = pos - first;
        using std::swap;
    
        // percolate up
        while (i > 0) { // non-root node
            auto parent_it = first + (i-1)/2;
    
            if (cmp(*pos, *parent_it))
                break; // parent node satisfies the heap-property 
    
            swap(*pos, *parent_it); // swap with parent
            pos = parent_it;
            i = pos - first;
        }
    
        // percolate down
        while (2*i + 1 < n) { // non-leaf node, since it has a left child
            const auto lidx = 2*i + 1, ridx = 2*i + 2;
    
            auto lchild_it = first + lidx; 
            auto rchild_it = ridx < n? first + ridx: last;
    
            auto it = pos;
            if (cmp(*it, *lchild_it))
                it = lchild_it;
            if (rchild_it != last && cmp(*it, *rchild_it))
                it = rchild_it;
    
            if (pos == it)
                break; // node satisfies the heap-property
    
            swap(*pos, *it); // swap with child
            pos = it;
            i = pos - first;
        }
    }
    

    Then, you can provide the following simplified overload of set_heap_element() for a max heap:

    #include <functional> // std::less
    
    template<typename RandomIt, typename T>
    void set_heap_element(RandomIt first, RandomIt last, RandomIt pos, T value) {
        return set_heap_element(first, last, pos, value, std::less<T>{});
    }
    

    This overload uses a std::less<T> object as the comparison function object for the original function template.


    Example

    In your max-heap example, set_heap_element() could be used as follows:

    std::vector<int> v{1,2,3,5,9,20,3};
    std::make_heap(v.begin(), v.end());
    
    // set 4th element to 35 in O(log n)
    set_heap_element(v.begin(), v.end(), v.begin() + 3, 35);
    

    You could use std::is_heap(), which takes linear time, whenever you want to check whether the max-heap property is still satisfied by v after setting an element with the set_heap_element() function template above:

    assert(std::is_heap(v.begin(), v.end())); 
    

    What about min heaps?

    You can achieve the same for a min heap by passing a std::greater<int> object as the last argument of the function calls to std::make_heap(), set_heap_element() and std::is_heap():

    std::vector<int> v{1,2,3,5,9,20,3};
    // create a min heap
    std::make_heap(v.begin(), v.end(), std::greater<int>{});
    
    // set 4th element to 35 in O(log n)
    set_heap_element(v.begin(), v.end(), v.begin() + 3, 35, std::greater<int>{});
    
    // is the min-heap property satisfied?
    assert(std::is_heap(v.begin(), v.end(), std::greater<int>{}));
    
    0 讨论(0)
  • 2021-01-06 01:15

    You can just do it yourself:

    void modify_heap_element(std::vector<int> &heap, size_t index, int value)
    {
        //while value is too large for its position, bubble up
        while(index > 0 && heap[(index-1)>>1] < value)
        {
            size_t parent = (index-1)>>1;
            heap[index]=heap[parent];
            index = parent;
        }
        //while value is too large for its position sift down
        for (;;)
        {
            size_t left=index*2+1;
            size_t right=left+1;
            if (left >= heap.size())
                break;
            size_t bigchild = (right >= heap.size() || heap[right] < heap[left] ?
                               left : right );
            if (!(value < heap[bigchild]))
               break;
            heap[index]=heap[bigchild];
            index = bigchild;
        }
        heap[index] = value;
    }
    
    0 讨论(0)
  • 2021-01-06 01:19

    If we look closer at your statement:

    now I disturb heap by changing one random element of heap.

    For heapifying in O(log n) you can only directly "disturb" the back or the front of the vector (which corresponds somehow to inserting or deleting an element). In these cases, (re)heapification can be then achieved by means of the std::push_heap and std::pop_heap algorithms, which take logarithmic running time.

    That is, the back:

    v.back() = 35;
    std::push_heap(v.begin(), v.end()); // heapify in O(log n)
    

    or the front:

    v.front() = 35;
    
    // places the front at the back
    std::pop_heap(v.begin(), v.end()); // O(log n)
    // v.back() is now 35, but it does not belong to the heap anymore
    
    // make the back belong to the heap again
    std::push_heap(v.begin(), v.end()); // O(log n)
    

    Otherwise you need to reheapify the whole vector with std::make_heap, which takes linear running time.


    Summary

    It's not possible to modify an arbitrary element of the heap and achieve the heapification in logarithmic running time with the standard library (i.e., the function templates std::push_heap and std::pop_heap). However, you can always implement the heap's swim and sink operations by yourself in order to heapify in logarithmic running time.

    0 讨论(0)
提交回复
热议问题