Publicado · en mejora
Guía de Árboles y montículos · 4/6
Por ahora, este capítulo solo está disponible en inglés.
Most tree operations cost time proportional to the height h. Once you understand how height relates to the number of nodes n, the costs of BSTs and heaps follow naturally. This chapter lists time and space per operation, explains why heapify is O(n), and compares trees and heaps with other structures.
A binary tree of height h has at most 2^(h+1) - 1 nodes. Turned around, a binary tree with n nodes has height at least about log2 n and at most n - 1 (a degenerate tree). A heap is a complete binary tree, so its height is always the minimum, floor(log2 n), and O(log n) is guaranteed even in the worst case. A plain BST's height depends on the insertion order.
import random
class Node:
def __init__(self, key):
self.key, self.left, self.right = key, None, None
def insert(root, key):
if root is None:
return Node(key)
cur = root
while True:
side = "left" if key < cur.key else "right"
if getattr(cur, side) is None:
setattr(cur, side, Node(key))
return root
cur = getattr(cur, side)
def height(root):
best, stack = -1, [(root, 0)] if root else []
while stack:
node, d = stack.pop()
best = max(best, d)
stack += [(c, d + 1) for c in (node.left, node.right) if c]
return best
n = 2000
keys = list(range(n))
for name, order in [("sorted input", keys), ("random input", random.sample(keys, n))]:
root = None
for k in order:
root = insert(root, k)
print(name, height(root))
# sorted input 1999
# random input around 20-30 (varies per run)A BST built from a random order has O(log n) expected height, but real inputs are often sorted or nearly sorted. Where the worst case must be bounded, use a balanced tree such as an AVL tree or a red-black tree.
| Operation | BST average | BST worst | Balanced BST | Binary heap |
|---|---|---|---|---|
| Search | O(log n) | O(n) | O(log n) | O(n) |
| Insert | O(log n) | O(n) | O(log n) | O(log n) |
| Delete | O(log n) | O(n) | O(log n) | root only, O(log n) |
| Peek minimum | O(log n) | O(n) | O(log n) | O(1) |
| Remove minimum | O(log n) | O(n) | O(log n) | O(log n) |
| Output in sorted order | O(n) | O(n) | O(n) | O(n log n) |
| Build from n items | O(n log n) | O(n^2) | O(n log n) | O(n) |
A heap is weak at finding an arbitrary value (it may have to scan everything) and strong when only the minimum matters. A BST gives O(log n) across the board but has larger constant factors and more memory per element.
Every traversal visits each node once, so time is O(n). The extra space is the size of the recursion stack or explicit stack: O(h) for depth-first traversals, and for level-order the width of the widest level (about n/2 in a complete tree). In a degenerate tree h approaches n and recursion can exceed Python's limit, so it pays to know the iterative version with an explicit stack.
def inorder_iter(root):
out, stack, cur = [], [], root
while stack or cur:
while cur: # go left as far as possible, stacking nodes
stack.append(cur)
cur = cur.left
cur = stack.pop()
out.append(cur.key)
cur = cur.right
return out
root = None
for k in range(5000): # degenerate tree of height 4999
root = insert(root, k)
print(len(inorder_iter(root))) # 5000, no recursion limit issueHeapify moves a node of height k down at most k levels. A complete binary tree has about n / 2^(k+1) nodes of height k, so the total work is n x (1/4 + 2/8 + 3/16 + ...), and that series converges to a constant (1). In other words, most nodes sit near the leaves and barely move. Pushing items one at a time, by contrast, can carry each new item all the way up, which costs O(n log n).
import heapq
class Counted:
count = 0
def __init__(self, v):
self.v = v
def __lt__(self, other):
Counted.count += 1
return self.v < other.v
data = list(range(100_000, 0, -1)) # descending: worst case for push
Counted.count = 0
heapq.heapify([Counted(v) for v in data])
print("heapify", Counted.count) # about 150,000 (about 1.5n)
Counted.count = 0
h = []
for v in data:
heapq.heappush(h, Counted(v))
print("n pushes", Counted.count) # about 1,470,000 (on the order of n log n)| Structure | Remove minimum | Insert | Find key | Keeps order |
|---|---|---|---|---|
| Unsorted array | O(n) | O(1) | O(n) | no |
| Sorted array (descending) | O(1) | O(n) | O(log n) | yes |
| Binary heap | O(log n) | O(log n) | O(n) | minimum only |
| Balanced BST | O(log n) | O(log n) | O(log n) | yes |
| Hash table | O(n) | O(1) average | O(1) average | no |
All of them use O(n) space, but a heap lives in a single array with no pointers and good memory locality, so it is fast in practice. A BST node carries two child pointers, and balanced trees add a height or colour field on top.
Height drives the cost of tree operations. A heap's shape is fixed as a complete binary tree, so insert and remove-min are always O(log n) and heapify is O(n). A BST is O(log n) on average but degrades to O(n) on sorted input, so use a balanced BST when the worst case matters. For deep trees, consider an explicit stack instead of recursion.
0 comentarios
Iniciar sesión · Inicia sesión para dejar un comentario.
Sé el primero en comentar.