It's O(n), since it must check every element. If you want better performance for max, you can use the heapq module. However, you have to negate each value, since heapq provides a min heap. Inserting an element into a heap is O(log n).
It's O(n), since it must check every element. If you want better performance for max, you can use the heapq module. However, you have to negate each value, since heapq provides a min heap. Inserting an element into a heap is O(log n).
Of course it is O(n) unless you are using a different datastructure supporting the max of a value collection due to some implementation invariant.
That depends what exactly you mean by "constant sized". The time to find the minimum of a list with 917,340 elements is with a very large constant factor. The time to find the minimum of various lists of different constant sizes is
and likely
where
is the size of each list. Finding the minimum of a list of 917,340 elements takes much longer than finding the minimum of a list of 3 elements.
I found this quote from the Wikipedia article on time complexity helpful:
The time complexity is generally expressed as a function of the size of the input.
So if the size of the input doesn't vary, for example if every list is of 256 integers, the time complexity will also not vary and the time complexity is therefore O(1). This would be true of any algorithm, such as sorting, searching, etc.
The code you have in the question rearranges array elements such that they satisfy the heap property i.e. the value of the parent node is greater than that of the children nodes. The time complexity of the heapify operation is O(n).
Here's an extract from Wikipedia page on Min-max heap: for each index i from floor(length(h)/2) down to 1 do: push-down(h, i) return h
Here the function FLOYD-BUILD-HEAP is same as your build_max_heap function and push-down is same as your max_heapify function.
A suggestion: the naming of your functions is a little confusing. Your max_heapify is not actually heapifying. It is just a part of the heapify operation. A better name could be something like push_down (as used in Wikipedia) or fix_heap.
A heap is a data structure which supports operations including insertion and retrieval. Each operation has its own runtime complexity.
Maybe you were thinking of the runtime complexity of heapsort which is a sorting algorithm that uses a heap. In that case, the runtime complexity is O(n*log(n)).
It requires more careful analysis, such as you'll find here. The basic insight is that only the root of the heap actually has depth log2(len(a)). Down at the nodes one above a leaf - where half the nodes live - a leaf is hit on the first inner-loop iteration.
"Exact" derivation
Waving hands some, when the algorithm is looking at a node at the root of a subtree with N elements, there are about N/2 elements in each subtree, and then it takes work proportional to log(N) to merge the root and those sub-heaps into a single heap. So the total time T(N) required is about
T(N) = 2*T(N/2) + O(log(N))
That's an uncommon recurrence. The Akra–Bazzi method can be used to deduce that it's O(N), though.
I think more informative, and certainly more satifsying, is to derive an exact solution from scratch. Toward that end, I'll only talk about complete binary trees: as full as possible on every level. Then there 2**N - 1 elements in total, and all subtrees are also complete binary trees. This sidesteps mounds of pointless details about how to proceed when things aren't exactly balanced.
When we're looking at a subtree with 2**k - 1 elements, its two subtrees have exactly 2**(k-1) - 1 elements each, and there are k levels. For example, for a tree with 7 elements, there's 1 element at the root, 2 elements on the second level, and 4 on the third. After the subtrees are heapified, the root has to moved into place, moving it down 0, 1, or 2 levels. This requires doing comparisons between levels 0 and 1, and possibly also between levels 1 and 2 (if the root needs to move down), but no more that that: the work required is proportional to k-1. In all, then,
T(2**k - 1) = 2 * T(2**(k-1) - 1) + (k - 1)*C
for some constant C bounding the worst case for comparing elements at a pair of adjacent levels.
What about T(1)? That's free! A tree with only 1 element is a already a heap - there's nothing to do.
T(1) = 0
One level above those leaves, trees have 3 elements. It costs (no more than) C to move the smallest (for a min-heap; largest for a max-heap) to the top.
T(3) = C
One level above that trees have 7 elements. It costs T(3) to heapify each of the subtrees, and then no more than 2*C to move the root into place:
T(7) = 2*C + 2*C = 4*C
Continuing in the same way:
T(15) = 2* 4*C + 3*C = 11*C
T(31) = 2*11*C + 4*C = 26*C
T(63) = 2*26*C + 5*C = 57*C
...
T(2**k - 1) = (2**k - k - 1)*C
where the last line is a guess at the general form. You can verify that "it works" for all the specific lines before it, and then it's straightforward to prove it by induction.
So, where N = 2**k - 1,
T(N) = (N - log2(N+1)) * C
which shows that T(N) is bounded above by C*N, so is certainly O(N).
For more mathy folks: Consider a complete binary tree with L levels, labeled from 0 (root) to L-1. Such a tree has N=2^L-1 nodes. Conversely, a complete binary tree with N nodes has L=log2(N-1) levels.
For any given node at a level l, the bubble/heapify down operation makes L-1-l comparisons. For the total number of operations, we thus need to compute the sum:

which is equal to

Note: strictly speaking, the sum goes from 0 to L-2, as the leaves are not to be operated on. However, L-(L-1)-1 is zero, so we can leave the sum upper limit to be L-1
It's O(n). It's a general algorithm, you can't find the max/min in the general case without checking all of them. Python doesn't even have a built-in sorted collection type that would make the check easy to specialize.
A for loop would have the same algorithmic complexity, but would run slower in the typical case, since min/max (on CPython anyway) are running an equivalent loop at the C layer, avoiding bytecode interpreter overhead, which the for loop would incur.
To find the maximum or minimum of a sequence, you must look at each element once, thus you can't get better than O(n).
Of course, Python min and max have O(n) too: docs.
You can write your own min/max function with a for loop and it will have the same complexity, but will be slower because it is not optimized in C.
