Spaces:
Sleeping
Sleeping
| GREEDY ALGORITHMS | |
| ================= | |
| Greedy algorithms make the locally optimal choice at each step hoping to find a global optimum. | |
| They do NOT reconsider past choices (unlike DP). | |
| When to use greedy: when local optimum leads to global optimum (greedy choice property + optimal substructure). | |
| --- Activity Selection Problem --- | |
| Select maximum number of non-overlapping activities. | |
| Sort by end time, always pick activity that ends earliest. | |
| Time: O(n log n). | |
| def activity_selection(activities): | |
| # activities: [(start, end), ...] | |
| activities.sort(key=lambda x: x[1]) # sort by end time | |
| selected = [activities[0]] | |
| last_end = activities[0][1] | |
| for start, end in activities[1:]: | |
| if start >= last_end: | |
| selected.append((start, end)) | |
| last_end = end | |
| return selected | |
| acts = [(1,4),(3,5),(0,6),(5,7),(3,9),(5,9),(6,10),(8,11),(8,12),(2,14),(12,16)] | |
| print(activity_selection(acts)) | |
| # [(1,4),(5,7),(8,11),(12,16)] — 4 activities | |
| --- Fractional Knapsack --- | |
| Take fractions of items to maximize value. Unlike 0/1 knapsack, items can be split. | |
| Sort by value/weight ratio. Greedy works here (not for 0/1). | |
| Time: O(n log n). | |
| def fractional_knapsack(weights, values, capacity): | |
| items = sorted(zip(values, weights), key=lambda x: x[0]/x[1], reverse=True) | |
| total_value = 0 | |
| for value, weight in items: | |
| if capacity >= weight: | |
| total_value += value | |
| capacity -= weight | |
| else: | |
| total_value += value * (capacity / weight) | |
| break | |
| return round(total_value, 2) | |
| weights = [10, 20, 30] | |
| values = [60, 100, 120] | |
| print(fractional_knapsack(weights, values, 50)) # Output: 240.0 | |
| --- Coin Change (Greedy — only works for canonical coin systems) --- | |
| For standard denominations (1,5,10,25 cents), greedy gives minimum coins. | |
| Note: greedy FAILS for arbitrary denominations — use DP instead. | |
| def coin_change_greedy(coins, amount): | |
| coins.sort(reverse=True) | |
| count = 0 | |
| result = [] | |
| for coin in coins: | |
| while amount >= coin: | |
| amount -= coin | |
| count += 1 | |
| result.append(coin) | |
| return count, result | |
| print(coin_change_greedy([1, 5, 10, 25], 41)) | |
| # Output: (4, [25, 10, 5, 1]) | |
| --- Job Scheduling to Maximize Profit --- | |
| Schedule jobs with deadlines to maximize total profit. Each job takes 1 unit of time. | |
| Time: O(n^2). | |
| def job_scheduling(jobs): | |
| # jobs: [(id, deadline, profit), ...] | |
| jobs.sort(key=lambda x: x[2], reverse=True) # sort by profit | |
| max_deadline = max(j[1] for j in jobs) | |
| slots = [None] * (max_deadline + 1) | |
| total_profit = 0 | |
| for job_id, deadline, profit in jobs: | |
| for t in range(min(deadline, max_deadline), 0, -1): | |
| if slots[t] is None: | |
| slots[t] = job_id | |
| total_profit += profit | |
| break | |
| scheduled = [s for s in slots if s] | |
| return scheduled, total_profit | |
| jobs = [('a',2,100),('b',1,19),('c',2,27),('d',1,25),('e',3,15)] | |
| print(job_scheduling(jobs)) # (['c', 'a', 'e'], 142) | |
| --- Huffman Encoding --- | |
| Build optimal prefix-free codes for data compression. | |
| Characters with higher frequency get shorter codes. | |
| Time: O(n log n). | |
| import heapq | |
| def huffman_encoding(freq): | |
| heap = [[f, [ch, ""]] for ch, f in freq.items()] | |
| heapq.heapify(heap) | |
| while len(heap) > 1: | |
| lo = heapq.heappop(heap) | |
| hi = heapq.heappop(heap) | |
| for pair in lo[1:]: | |
| pair[1] = '0' + pair[1] | |
| for pair in hi[1:]: | |
| pair[1] = '1' + pair[1] | |
| heapq.heappush(heap, [lo[0] + hi[0]] + lo[1:] + hi[1:]) | |
| return sorted(heap[0][1:], key=lambda x: len(x[1])) | |
| freq = {'a': 5, 'b': 9, 'c': 12, 'd': 13, 'e': 16, 'f': 45} | |
| codes = huffman_encoding(freq) | |
| for char, code in codes: | |
| print(f"{char}: {code}") | |
| # f: 0 | |
| # c: 100 | |
| # d: 101 | |
| # a: 1100 | |
| # b: 1101 | |
| # e: 111 | |
| --- Minimum Platforms (Greedy) --- | |
| Find minimum number of platforms needed at a railway station. | |
| Time: O(n log n). | |
| def min_platforms(arrivals, departures): | |
| arrivals.sort() | |
| departures.sort() | |
| platforms = 1 | |
| max_platforms = 1 | |
| i = 1 | |
| j = 0 | |
| while i < len(arrivals) and j < len(departures): | |
| if arrivals[i] <= departures[j]: | |
| platforms += 1 | |
| i += 1 | |
| else: | |
| platforms -= 1 | |
| j += 1 | |
| max_platforms = max(max_platforms, platforms) | |
| return max_platforms | |
| arr = [900, 940, 950, 1100, 1500, 1800] | |
| dep = [910, 1200, 1120, 1130, 1900, 2000] | |
| print(min_platforms(arr, dep)) # Output: 3 | |
| --- Gas Station (Circular Route) --- | |
| Find starting gas station to complete a circular route. Greedy: if total gas >= total cost, solution exists. | |
| Time: O(n). Space: O(1). | |
| def can_complete_circuit(gas, cost): | |
| total_tank = 0 | |
| curr_tank = 0 | |
| start = 0 | |
| for i in range(len(gas)): | |
| diff = gas[i] - cost[i] | |
| total_tank += diff | |
| curr_tank += diff | |
| if curr_tank < 0: | |
| start = i + 1 | |
| curr_tank = 0 | |
| return start if total_tank >= 0 else -1 | |
| gas = [1, 2, 3, 4, 5] | |
| cost = [3, 4, 5, 1, 2] | |
| print(can_complete_circuit(gas, cost)) # Output: 3 | |
| --- Greedy vs Dynamic Programming --- | |
| Greedy: fast O(n log n), works when greedy choice is provably optimal. | |
| DP: slower O(n^2) or O(n*W), works for all optimization problems. | |
| Example: Fractional Knapsack → Greedy. 0/1 Knapsack → DP. | |