Greedy Algorithms

Greedy algorithms make locally optimal choices at each step, hoping to find a global optimum. They are simple to implement and efficient, but don't always guarantee the optimal solution for all problems.

What is a Greedy Algorithm?

A greedy algorithm makes the best choice at each step without considering future consequences. The key insight is that local optimality can sometimes lead to global optimality.

Greedy Algorithm Characteristics:

  • Greedy Choice Property: A global optimum can be reached by making locally optimal choices
  • Optimal Substructure: An optimal solution contains optimal solutions to subproblems
  • No Backtracking: Once a choice is made, it's never reconsidered

Activity Selection Problem

Select the maximum number of activities that don't overlap:


#include <vector>
#include <algorithm>

struct Activity {
    int start, finish;
    int id;
};

int activitySelection(vector<Activity>& activities) {
    // Sort by finish time (greedy choice)
    sort(activities.begin(), activities.end(), 
         [](const Activity& a, const Activity& b) {
             return a.finish < b.finish;
         });
    
    int count = 1; // First activity is always selected
    int lastFinish = activities[0].finish;
    
    cout << "Selected activities: " << activities[0].id << " ";
    
    for (int i = 1; i < activities.size(); i++) {
        // If this activity starts after the last selected activity finishes
        if (activities[i].start >= lastFinish) {
            cout << activities[i].id << " ";
            lastFinish = activities[i].finish;
            count++;
        }
    }
    cout << endl;
    
    return count;
}

int main() {
    vector<Activity> activities = {
        {1, 4, 1}, {3, 5, 2}, {0, 6, 3}, 
        {5, 7, 4}, {3, 9, 5}, {5, 9, 6}, 
        {6, 10, 7}, {8, 11, 8}, {8, 12, 9}, 
        {2, 14, 10}, {12, 16, 11}
    };
    
    int maxActivities = activitySelection(activities);
    cout << "Maximum activities: " << maxActivities << endl;
    
    return 0;
}
                    

Fractional Knapsack Problem

Maximize value in a knapsack where items can be broken into fractions:


#include <vector>
#include <algorithm>

struct Item {
    int weight, value;
    double ratio;
    
    Item(int w, int v) : weight(w), value(v) {
        ratio = (double)value / weight;
    }
};

double fractionalKnapsack(vector<Item>& items, int capacity) {
    // Sort by value-to-weight ratio (greedy choice)
    sort(items.begin(), items.end(), 
         [](const Item& a, const Item& b) {
             return a.ratio > b.ratio;
         });
    
    double totalValue = 0.0;
    int currentWeight = 0;
    
    for (const Item& item : items) {
        if (currentWeight + item.weight <= capacity) {
            // Take the whole item
            currentWeight += item.weight;
            totalValue += item.value;
            cout << "Take full item: weight=" << item.weight 
                 << ", value=" << item.value << endl;
        } else {
            // Take fraction of the item
            int remainingCapacity = capacity - currentWeight;
            double fraction = (double)remainingCapacity / item.weight;
            totalValue += item.value * fraction;
            cout << "Take " << fraction << " of item: weight=" 
                 << item.weight << ", value=" << item.value << endl;
            break;
        }
    }
    
    return totalValue;
}

int main() {
    vector<Item> items = {
        Item(10, 60), Item(20, 100), Item(30, 120)
    };
    int capacity = 50;
    
    double maxValue = fractionalKnapsack(items, capacity);
    cout << "Maximum value: " << maxValue << endl;
    
    return 0;
}
                    

Job Scheduling with Deadlines

Schedule jobs to maximize profit while meeting deadlines:


#include <vector>
#include <algorithm>

struct Job {
    char id;
    int deadline, profit;
};

vector<char> jobScheduling(vector<Job>& jobs) {
    // Sort by profit in descending order (greedy choice)
    sort(jobs.begin(), jobs.end(), 
         [](const Job& a, const Job& b) {
             return a.profit > b.profit;
         });
    
    // Find maximum deadline
    int maxDeadline = 0;
    for (const Job& job : jobs) {
        maxDeadline = max(maxDeadline, job.deadline);
    }
    
    // Create a schedule array
    vector<char> schedule(maxDeadline, '0');
    vector<bool> slot(maxDeadline, false);
    
    int totalProfit = 0;
    
    for (const Job& job : jobs) {
        // Find a free slot for this job (from deadline-1 to 0)
        for (int j = min(maxDeadline, job.deadline) - 1; j >= 0; j--) {
            if (!slot[j]) {
                slot[j] = true;
                schedule[j] = job.id;
                totalProfit += job.profit;
                break;
            }
        }
    }
    
    cout << "Total profit: " << totalProfit << endl;
    return schedule;
}

int main() {
    vector<Job> jobs = {
        {'a', 2, 100}, {'b', 1, 19}, {'c', 2, 27},
        {'d', 1, 25}, {'e', 3, 15}
    };
    
    vector<char> schedule = jobScheduling(jobs);
    
    cout << "Job sequence: ";
    for (char job : schedule) {
        if (job != '0') {
            cout << job << " ";
        }
    }
    cout << endl;
    
    return 0;
}
                    

Coin Change (Greedy Approach)

Find minimum coins needed for a given amount (works for certain coin systems):


#include <vector>

vector<int> coinChangeGreedy(vector<int>& coins, int amount) {
    vector<int> result;
    
    // Sort coins in descending order
    sort(coins.begin(), coins.end(), greater<int>());
    
    for (int coin : coins) {
        while (amount >= coin) {
            result.push_back(coin);
            amount -= coin;
        }
    }
    
    if (amount == 0) {
        return result;
    } else {
        return {}; // No solution possible
    }
}

int main() {
    // This works for canonical coin systems (like US coins: 25, 10, 5, 1)
    vector<int> coins = {25, 10, 5, 1};
    int amount = 67;
    
    vector<int> result = coinChangeGreedy(coins, amount);
    
    if (!result.empty()) {
        cout << "Coins needed: ";
        for (int coin : result) {
            cout << coin << " ";
        }
        cout << endl;
        cout << "Total coins: " << result.size() << endl;
    } else {
        cout << "No solution found" << endl;
    }
    
    return 0;
}
                    

Huffman Coding

Create optimal prefix-free codes for data compression:


#include <queue>
#include <unordered_map>

struct HuffmanNode {
    char ch;
    int freq;
    HuffmanNode* left;
    HuffmanNode* right;
    
    HuffmanNode(char c, int f) : ch(c), freq(f), left(nullptr), right(nullptr) {}
    HuffmanNode(int f) : ch(0), freq(f), left(nullptr), right(nullptr) {}
};

struct Compare {
    bool operator()(HuffmanNode* a, HuffmanNode* b) {
        return a->freq > b->freq; // Min heap
    }
};

void generateCodes(HuffmanNode* root, string code, unordered_map<char, string>& codes) {
    if (!root) return;
    
    if (root->ch != 0) { // Leaf node
        codes[root->ch] = code.empty() ? "0" : code;
        return;
    }
    
    generateCodes(root->left, code + "0", codes);
    generateCodes(root->right, code + "1", codes);
}

unordered_map<char, string> huffmanCoding(const string& text) {
    // Count frequencies
    unordered_map<char, int> freq;
    for (char c : text) {
        freq[c]++;
    }
    
    // Create priority queue
    priority_queue<HuffmanNode*, vector<HuffmanNode*>, Compare> pq;
    
    for (auto& pair : freq) {
        pq.push(new HuffmanNode(pair.first, pair.second));
    }
    
    // Build Huffman tree
    while (pq.size() > 1) {
        HuffmanNode* right = pq.top(); pq.pop();
        HuffmanNode* left = pq.top(); pq.pop();
        
        HuffmanNode* merged = new HuffmanNode(left->freq + right->freq);
        merged->left = left;
        merged->right = right;
        
        pq.push(merged);
    }
    
    // Generate codes
    unordered_map<char, string> codes;
    generateCodes(pq.top(), "", codes);
    
    return codes;
}
                    

When Greedy Works vs When It Doesn't

Greedy Works For:

  • Activity selection
  • Fractional knapsack
  • Huffman coding
  • Minimum spanning tree (Kruskal's, Prim's)
  • Dijkstra's shortest path

Greedy Fails For:

  • 0/1 Knapsack: Items cannot be broken
  • Longest path problem: Local choices don't guarantee global optimum
  • Some coin systems: Greedy doesn't always give minimum coins

Greedy Algorithm Template


// General greedy algorithm structure
template<typename T>
Solution greedyAlgorithm(vector<T>& elements) {
    // 1. Sort elements by greedy criteria
    sort(elements.begin(), elements.end(), greedyComparator);
    
    Solution solution;
    
    // 2. Make greedy choices
    for (const T& element : elements) {
        if (isSafe(solution, element)) {
            solution.add(element);
        }
    }
    
    return solution;
}
                    

CodeForces Challenge Problems

Practice greedy algorithms with these problems:

Easy

Activity Selection

Practice greedy choice with activity scheduling problems.

Greedy Scheduling
Solve Problem