push | $O(\operatorname{log}(N))$ |
pop | $O(\operatorname{log}(N))$ |
remove | $O(\operatorname{log}(N))$ |
change | $O(\operatorname{log}(N))$ |
top | $O(1)$ |
size | $O(1)$ |
A binary heap is defined as a binary tree with two additional constraints:
To push an element to a heap, we can perform this algorithm:
Steps 2 and 3, which restore the heap property by comparing and possibly swapping a node with its parent, are called the sift-up operation.
The procedure for popping the root from the heap while retaining the heap property is as follows:
Steps 3 and 4, which restore the heap property by comparing and possibly swapping a node with one of its children, are called the sift-down operation.
Deleting an arbitrary element can be done as follows:
top: | return root node value property |
size: | return heap size property |
Let's look at several examples of working with a max binary heap. We start with empty heap.
10
.
20
.
20
.
30
.
30
.
40
.
40
.
40
.
40
.
40
.
40
with 10
.
40
.
40
.
40
.
10
.
class BinaryHeap:
def __init__(self, container: Any = list, compare: Any = operator.ge) -> None:
self.container = container()
self.compare = compare
def sift_up(self, key: int):
if len(self.container) > key:
while (
key > 0
and self.compare(self.container[(key - 1) >> 1], self.container[key])
== False
):
(self.container[(key - 1) >> 1], self.container[key]) = (
self.container[key],
self.container[(key - 1) >> 1],
)
key >>= 1
def sift_down(self, key: int):
if len(self.container) > key:
while (
2 * key + 1 < len(self.container)
and self.compare(self.container[key], self.container[2 * key + 1])
== False
) or (
2 * key + 2 < len(self.container)
and self.compare(self.container[key], self.container[2 * key + 2])
== False
):
if 2 * key + 2 < len(self.container):
if (
self.compare(
self.container[2 * key + 1], self.container[2 * key + 2]
)
== True
):
(self.container[2 * key + 1], self.container[key]) = (
self.container[key],
self.container[2 * key + 1],
)
key = 2 * key + 1
else:
(self.container[2 * key + 2], self.container[key]) = (
self.container[key],
self.container[2 * key + 2],
)
key = 2 * key + 2
else:
(self.container[2 * key + 1], self.container[key]) = (
self.container[key],
self.container[2 * key + 1],
)
key = 2 * key + 1
def push(self, new_value: Any):
self.container.append(new_value)
self.sift_up(len(self.container) - 1)
def remove(self, key: int):
if len(self.container) > key:
(self.container[len(self.container) - 1], self.container[key]) = (
self.container[key],
self.container[len(self.container) - 1],
)
self.container.pop()
self.sift_down(key)
def pop(self):
self.remove(0)
def change(self, key: int, new_value: Any):
if len(self.container) > key and self.container[key] != new_value:
self.container[key] = new_value
if (
key > 0
and self.compare(self.container[(key - 1) >> 1], self.container[key])
== False
):
self.sift_up(key)
else:
self.sift_down(key)
def top(self) -> Any:
return self.container[0] if len(self.container) > 0 else None
def size(self) -> int:
return len(self.container)