20
loading...
This website collects cookies to deliver better user experience
I did my best to provide some visual aid for Heaps, because it can be hard to visualize some steps. If you have trouble understanding the concept through this blog post, I suggest you also watch the videos linked at the end of the post.
Note: The naming of the heaps refers to the value of the top, or Root-node, of the Tree. This means the top of a MinHeap will always be the lowest value (min) of the nodes, and in a MaxHeap it will be the highest value (max).
Note: In this example we'll use numbers (integers) that represent the values. But that's mostly for simplicity's sake. These values can be strings, objects, whatever you want; as long as you can compare them and figure out which is the lesser or greater of the two.
$values = [2, 18, 36, 5, 2, 0, -5, 72];
72
.36
and 18
, because those the next highest two values. The position of these values doesn't really matter at this point, they can both be either left or right.5
, 2
,2
and 0
. Again there exact position doesn't matter, because they are all lower then 18
and 36
.-5
will be the last child; the most left value on the lowest level of the tree. This makes our Complete Binary Tree complete.The last node
The node that is the most right node (filled from the left) at the bottom of a Complete Binary Tree is called the last node. In our example this is -5
. We'll come back to this node when we start extracting nodes.
2
). This will become our root-node by default.18
) at the first available location (left child).36
(In our case the right child of the Root-node). We compare it to its parent. It is bigger, so we swap. 36
is now the root with 2
and 18
as its children.Note: This swapping of values with a parent to put the values in the correct spot is referred to as sifting up.
5
. Again, we insert it at the first available location in the Tree (step 2). In this case the left child of 2
. We compare it to its parent and swap (step 3), because 5
is bigger than 2
. We repeat step 3, but this parent (36
) is bigger, so we are done. Next!Note: The transforming of a Binary Tree into a Heap, is known as to Heapify.
abstract SplHeap
class. It contains all methods of an Iterator
as well as a few helper methods that are specific to a Heap:extract()
- Removes and returns the Root-node from the Heap, and reorders the Heap with a new Root node.insert($value)
- Adds a new value to the Heap and reorders it.top()
- Only returns the current Root node value; it does not change the cursor of the iterator or remove the node.isCorrupted()
- Returns whether the current Heap is in a corrupted state (this happens when the compare()
function throws an exception).recoverFromCorruption()
- resets the corrupted state of the heap and allows for further use.abstract protected function compare($value1, $value2): int
. This function is used inside the Heap algorithm to determine how it should order two values / nodes.SplMinHeap
and a SplMaxHeap
class that are concrete implementations of the SplHeap
. These classes have an implemented compare()
method. Both classes essentially use the spaceship operator to compare the values.protected function compare($value1, $value2): int {
// SplMaxHeap
return $value1 <=> $value2;
// SplMinHeap
return $value2 <=> $value1;
}
$values
array like this:$heap = new \SplMaxHeap();
foreach($values as $value) {
$heap->insert($value);
}
SplHeap
is also an Iterator
, so we can foreach
over the Heap and have it yield its values in a decreasing order.foreach($heap as $value){
var_dump($value);
}
// int(72), int(36), int(18), int(5), int(2), int(2), int(0), int(-5)
Note: Iterating over the Heap will essentially call extract()
for every value. This means that those values are gone from the heap. If you call ::rewind()
on the Heap, this will not return those values. Using ::current()
or ::top()
will return the current top value without removing it. When you call ::next()
however, this will again extract the current value.
SplMaxHeap
will (probably) not work, but you can create your own MaxHeap by extending splHeap
.Product
class. It can store a physical weight in different units of measurements; like pounds (lbs) and grams (g). To put these in a MaxHeap, it needs to be able to compare those different types of measurements. A (very simple) implementation could look like this:class Product
{
public function __construct(public $name, public string $weight_type, public int $weight_amount) {}
}
class ProductMaxHeap extends \SplHeap
{
protected function compare($value1, $value2): int
{
return $this->asGrams($value1) <=> $this->asGrams($value2);
}
private function asGrams(Product $product): int|float
{
if ($product->weight_type === 'grams') {
return $product->weight_amount;
}
if ($product->weight_type === 'pounds') {
return $product->weight_amount * 453.59237;
}
throw new InvalidArgumentException('Product has unknown weight type.');
}
}
Tip: Whenever dealing with types like these, make sure to use constants that represent that type (like public const WEIGHT_TYPE_GRAMS = 'grams';
) or use Enums
when using PHP 8.1 or higher. This provides autocompletion in IDE's and prevents typo's like gram
instead of grams
.
$heap_as_array = [ // Keys added for visual aid
0 => 72,
1 = 36,
2 => 18,
3 => 5,
4 => 2,
5 => 2,
6 => 0,
7 => -5,
];
$i
represents the current node's key, the algorithm to figure out the left child of that node is: ($i * 2) + 1
, and for the right child it is: ($i * 2) + 2
.36
. Its key is 1
. So the left key is (1 * 2) + 1 = 3
, and the right key is (1 * 2) + 2 = 4
. Which are respectively the nodes: 5
and 2
. Which in turn matches our Heap.(int) ($i -1) / 2
. For the right child of 36 (index: 4) that would be: (4 - 1) / 2) = 1.5
. The int
of 1.5 = 1
. And key 1
is indeed the parent node: 36
.Note: Since we are working with a 0-based array, swapping means we can just switch the index of these values.
2, 18, 36, 5, 2, 0, -5, 72
5
72
)5
(So the next will be 36
)72
/ \
18 36
/ \ / \
5 2 0 -5
/
2
Note: Although this technically qualifies as HeapSort this isn't the most efficient way. We'll cover HeapSort more in-depth in an upcoming post where we'll make it more efficient.
priority
value for a certain object. When adding this object to the Heap, it will end up higher or lower depending on its priority. This type of queue is called a Priority Queue.Fun fact: Did you know the letters ueue
in Queue
are not silent, but are actually just waiting their turn? - I'll let myself out; sorry.
SplPriorityQueue
based on a MaxHeap.$queue = new SplPriorityQueue();
$queue->insert('task 1', 1);
$queue->insert('task 2', 3);
$queue->insert('task 3', 2);
var_dump($queue->extract()); // string(6) "task 2"
revolt/event-loop
package.DS\Vector
class.