Publicado · en mejora
Guía de Algoritmos voraces · 2/6
Por ahora, este capítulo solo está disponible en inglés.
Most greedy algorithms share the same skeleton. Put the candidates in sorted order or into a priority queue, take them out one at a time, and accept each one that does not conflict with the choices made so far. This chapter traces interval scheduling, Huffman coding and meeting-room allocation on small examples.
One meeting room has received several requests, and you want to fit in as many meetings as possible without overlaps. Each meeting is a half-open interval [start, end), so a meeting ending at 4 lets the next one start at 4. The greedy rule is "take the meeting that ends first".
def trace(intervals):
last_end = float("-inf")
for start, end in sorted(intervals, key=lambda iv: iv[1]):
ok = start >= last_end
print(f"({start}, {end}) last_end={last_end} -> {'take' if ok else 'skip'}")
if ok:
last_end = end
trace([(1, 3), (2, 5), (4, 7), (1, 8), (6, 9), (8, 10)])After sorting by end time, the scan goes like this.
| Step | Meeting | Previous end | Decision | New end |
|---|---|---|---|---|
| 1 | (1, 3) | none | take | 3 |
| 2 | (2, 5) | 3 | starts before 3, skip | 3 |
| 3 | (4, 7) | 3 | take | 7 |
| 4 | (1, 8) | 7 | skip | 7 |
| 5 | (6, 9) | 7 | skip | 7 |
| 6 | (8, 10) | 7 | take | 10 |
The result is (1, 3), (4, 7), (8, 10): three meetings, and no schedule can hold more.
Rules that sound reasonable collapse under a single counterexample.
| Rule | Counterexample | Greedy | Optimal |
|---|---|---|---|
| Earliest start | (0, 10), (1, 2), (3, 4) | 1 | 2 |
| Shortest meeting | (1, 5), (4, 7), (6, 10) | 1 | 2 |
| Fewest conflicts | larger counterexamples are known | fewer than optimal | - |
Taking the meeting that ends first leaves the most time for everything else. By induction, the k-th meeting chosen by greedy ends no later than the k-th meeting of any other valid schedule; that is the "greedy stays ahead" argument.
Huffman coding builds a prefix code that gives short bit strings to frequent symbols and long ones to rare symbols, minimizing the total length. The greedy rule is "merge the two lightest groups". Let's follow the frequencies a=5, b=9, c=12, d=13, e=16, f=45.
import heapq
freq = {"a": 5, "b": 9, "c": 12, "d": 13, "e": 16, "f": 45}
heap = [(w, ch) for ch, w in freq.items()]
heapq.heapify(heap)
total = 0
while len(heap) > 1:
w1, x = heapq.heappop(heap)
w2, y = heapq.heappop(heap)
total += w1 + w2
print(f"{x}({w1}) + {y}({w2}) = {w1 + w2}")
heapq.heappush(heap, (w1 + w2, x + y))
print("total bits:", total) # 224| Step | Two lightest | Merged weight | Running cost |
|---|---|---|---|
| 1 | a(5), b(9) | 14 | 14 |
| 2 | c(12), d(13) | 25 | 39 |
| 3 | ab(14), e(16) | 30 | 69 |
| 4 | cd(25), abe(30) | 55 | 124 |
| 5 | f(45), cdabe(55) | 100 | 224 |
In the finished tree f sits at depth 1, c, d and e at depth 3, and a and b at depth 4. Each symbol's code length equals its depth, and the sum of frequency times length is 45 + 36 + 39 + 48 + 20 + 36 = 224 bits, the same as the sum of all merged weights (14 + 25 + 30 + 55 + 100). A fixed 3-bit code would need 300 bits, so Huffman saves about 25%.
An exchange argument shows that some optimal tree places the two lightest symbols as siblings at the deepest level: if a heavier symbol were deeper, swapping the two could not increase the cost.
Now every meeting must take place, and you want the minimum number of rooms. Counting the platforms a railway station needs is the same problem. Scan meetings by start time and keep the end times of rooms in use in a min-heap. If the room that frees up first is free by the time the meeting starts, reuse it; otherwise open a new room.
import heapq
def min_rooms(meetings):
ends = [] # end times of rooms in use (min-heap)
for start, end in sorted(meetings):
if ends and ends[0] <= start:
heapq.heapreplace(ends, end) # reuse the room that frees up first
else:
heapq.heappush(ends, end) # open a new room
return len(ends)
print(min_rooms([(9, 10), (9, 12), (10, 11), (11, 13), (12, 13)])) # 2| Meeting | Heap before | Action | Heap after |
|---|---|---|---|
| (9, 10) | empty | new room | [10] |
| (9, 12) | [10] | 10 is after 9, new room | [10, 12] |
| (10, 11) | [10, 12] | reuse the room free at 10 | [11, 12] |
| (11, 13) | [11, 12] | reuse the room free at 11 | [12, 13] |
| (12, 13) | [12, 13] | reuse the room free at 12 | [13, 13] |
If, as with train platforms, an arrival and a departure at the same minute cannot share a platform, change ends[0] <= start to ends[0] < start. Whether interval endpoints are inclusive is the most common source of greedy bugs.
< versus <=) from the problem statement.
0 comentarios
Iniciar sesión · Inicia sesión para dejar un comentario.
Sé el primero en comentar.