Remove an element from the middle of an std::heap
Unfortunately, the standard is missing this (fairly important) function. With g++, you can use the non-standard function std::__adjust_heap
to do this, but there's no easy portable way of doing it -- and __adjust_heap
is slightly different in different versions of g++, so you can't even do it portably over g++ versions.
c++ heap with removing any element method
When you need to remove (or change the priority of) other objects than the root, a d-heap isn't necessarily the ideal data structure: the nodes keep changing their position and you need to keep track of various moves. It is doable, however. To use a heap like this you would return a handle to the newly inserted object which identifies some sort of node which stays put. Since the d-heap algorithm relies on the tree being a perfectly balanced tree, you effectively need to implement it using an array. Since these two requirements (using an array and having nodes stay put) are mutually exclusive you need to do both and have an index from the nodes into the array (so you can find the position of the object in the array) and a pointer from the array to the node (so you can update the node when the position changes). Almost certainly you don't want to move your nodes a lot, i.e. you rather accept finding the proper direction to move a nodes by searching multiple nodes, i.e. you want to use a d > 2.
There are alternative approach to implement a heap which are inherently nodes based. In particular Fibonacci heaps which yield for certain usage patterns a better amortized complexity than the usual O(ln(n)) complexity. However, they are somewhat harder to implement and the actual efficiency only pays off if you either need to change the priority of a node frequently or you have fairly large data sets.
How to delete in a heap data structure?
Actually, you can remove an item from the middle of a heap without trouble.
The idea is to take the last item in the heap and, starting from the current position (i.e. the position that held the item you deleted), sift it up if the new item is greater than the parent of the old item. If it's not greater than the parent, then sift it down.
That's the procedure for a max heap. For a min heap, of course, you'd reverse the greater and less cases.
Finding an item in a heap is an O(n) operation, but if you already know where it is in the heap, removing it is O(log n).
I published a heap-based priority queue for DevSource a few years back. The full source is at http://www.mischel.com/pubs/priqueue.zip
Update
Several have asked if it's possible to move up after moving the last node in the heap to replace the deleted node. Consider this heap:
1
6 2
7 8 3
If you delete the node with value 7, the value 3 replaces it:
1
6 2
3 8
You now have to move it up to make a valid heap:
1
3 2
6 8
The key here is that if the item you're replacing is in a different subtree than the last item in the heap, it's possible that the replacement node will be smaller than the parent of the replaced node.
Removing elements from heap
First, I assume you mean besides the fact that you don't need it, since we have an entire heap management function set in the C++ standard library, including make_heap, push_heap, pop_heap, and even sort_heap.
That said, I think I know what your problem is. You have unnecessary movement of elements in your heap. It deals with the swapping algorithm on the heap-down: the same problem is notable on both left and right sides, so I'll show the first one:
if (l < n && arr[i] < arr[l])
{
swap(arr[i], arr[l]);
heapDown(l);
if (r < n && arr[i] < arr[r])
{
swap(arr[i], arr[r]);
heapDown(r);
}
}
The logic here is not optimal for minimum motion. The "lesser" state of the element being pushed down must fall into one of two basic categories, and different actions are taken for each:
- The element is not less than either left or right. Do nothing.
- The element is less than either left or right, swap only with the largest, then drive down into only that subtree.
The #2 above in that list is the problem in your code. you swap with the smaller, then the larger, if item < left < right. I hope that is clear. If you want a proposal to fix your logic I can provide one, but I think you may have a handle on it if you understand what I described above.
Spoiler
void Heap::heapDown(int i)
{
int l = left(i);
int r = right(i);
int x = 0;
if (l < n && arr[i] < arr[l])
{
x = l;
if (r < n && arr[l] < arr[r])
x = r;
}
else if (r < n && arr[i] < arr[r])
x = r;
if (x != 0)
{
swap(arr[i], arr[x]);
heapDown(x);
}
}
Note:; In case it wasn't obvious, this is the very definition of tail-recursive, and as such could easily be transformed into a simple iterative loop.
Deleting the topmost element in the binary heap
Your fixheap2
fails to consider the case where both children are valid but have keys smaller than the parent's. In that situation, the heap property has been restored, and you should stop swapping with children (or you'll re-invalidate it).
Related Topics
Invalid Operands Error for Addition and Integer Division
Store Hex Value as String (Arduino Project)
How to Tokenize a String in C++
Difference Between Public, Private, and Protected Inheritance in C++
How to Parse a String to an Int in C++
What Are the Evaluation Order Guarantees Introduced by C++17
Why Is My Power Operator (^) Not Working
Is "Delete This" Allowed in C++
Singleton: How Should It Be Used
How to Align Text to the Right Using Cout
How to Peek At the Next Element in a Range-For Loop
Undefined, Unspecified and Implementation-Defined Behavior
Rule-Of-Three Becomes Rule-Of-Five With C++11
Is Segmentation Fault Actual Undefined Behavior When We Refer to a Non-Static Data-Member
Why Does Reading a Record Struct Fields from Std::Istream Fail, and How to Fix It