Greedy Algorithms Visualization

Introduction to Greedy Algorithms

Greedy algorithms make locally optimal choices at each step with the hope of finding a global optimum. They are simple and efficient but don't always yield the best solution for all problems.

Greedy algorithms are ideal for problems where:

Activity Selection
Coin Change
Fractional Knapsack

Activity Selection Problem

Given a set of activities with start and finish times, select the maximum number of activities that can be performed by a single person, assuming that a person can only work on a single activity at a time.

Generate random activities and click "Solve Greedily" to see the algorithm in action.

Algorithm Explanation

The greedy approach for activity selection:

  1. Sort all activities by finish time
  2. Select the first activity (earliest finish time)
  3. For remaining activities, select the next compatible activity (start time ≥ finish time of last selected activity)
function activitySelection(activities):
    // Sort activities by finish time
    sort activities by finish time
    
    // Select first activity
    select first activity
    lastFinishTime = finish time of first activity
    
    // Consider remaining activities
    for each activity in remaining activities:
        if start time of activity ≥ lastFinishTime:
            select this activity
            lastFinishTime = finish time of this activity
    
    return selected activities

Time Complexity

O(n log n) - Dominated by the sorting step

Space Complexity

O(n) - To store the activities

Coin Change Problem

Given a set of coin denominations and a target amount, find the minimum number of coins needed to make up that amount. The greedy approach works optimally for some coin systems (like US coins) but not for all possible coin systems.

Select a coin system and amount, then click "Solve Greedily" to see the algorithm in action.

Algorithm Explanation

The greedy approach for coin change:

  1. Sort coins in descending order
  2. Start with the largest coin and use as many as possible
  3. Move to the next largest coin and repeat until the amount is reached
function coinChange(coins, amount):
    // Sort coins in descending order
    sort coins in descending order
    
    result = []
    remaining = amount
    
    for each coin in coins:
        // Use as many of this coin as possible
        while remaining >= coin:
            add coin to result
            remaining = remaining - coin
    
    return result

Time Complexity

O(amount) - In the worst case, we might need to use all 1-value coins

Space Complexity

O(amount) - To store the coins used

When Greedy Works

The greedy approach works optimally for canonical coin systems where each coin is at least twice the value of the next smaller coin.

Example: US coins (1, 5, 10, 25)

When Greedy Fails

For some coin systems, the greedy approach doesn't yield the optimal solution.

Example: With coins [1, 3, 4] and amount 6, greedy gives [4, 1, 1] (3 coins) but optimal is [3, 3] (2 coins)

Fractional Knapsack Problem

Given a set of items with weights and values, and a knapsack with a maximum capacity, determine the maximum value that can be obtained by taking fractions of items if needed.

Generate random items and set a knapsack capacity, then click "Solve Greedily" to see the algorithm in action.

Algorithm Explanation

The greedy approach for fractional knapsack:

  1. Take items with highest value-to-weight ratio first
  2. If an item can be fully included, take it all
  3. If an item can only be partially included, take the fraction that fits
function fractionalKnapsack(items, capacity):
    // Calculate value-to-weight ratio for each item
    for each item in items:
        item.ratio = item.value / item.weight
    
    // Sort items by value-to-weight ratio in descending order
    sort items by ratio in descending order
    
    totalValue = 0
    remainingCapacity = capacity
    
    for each item in items:
        if remainingCapacity >= item.weight:
            // Take the whole item
            take item completely
            totalValue += item.value
            remainingCapacity -= item.weight
        else:
            // Take a fraction of the item
            fraction = remainingCapacity / item.weight
            totalValue += item.value * fraction
            remainingCapacity = 0
            break
    
    return totalValue

Time Complexity

O(n log n) - Dominated by the sorting step

Space Complexity

O(n) - To store the items

Fractional vs. 0/1 Knapsack

In the fractional knapsack problem, we can take fractions of items, making it solvable with a greedy approach.

In contrast, the 0/1 knapsack problem (where we must take items whole or not at all) requires dynamic programming for an optimal solution.

Real-world Applications

Fractional knapsack models resource allocation problems where resources are divisible:

  • Budget allocation across projects
  • Cargo loading with divisible goods
  • Investment portfolio optimization

When to Use Greedy Algorithms

Greedy algorithms are most effective when:

Common Greedy Algorithm Applications