Greedy Algorithms
Greedy Algorithm Overview
Greedy algorithms make locally optimal choices at each step with the hope of finding a globally optimal solution. They're simple and often efficient but don't always guarantee the best solution.
When to Use Greedy
- Optimal Substructure: Optimal solution contains optimal solutions to subproblems
- Greedy Choice Property: Locally optimal choice leads to globally optimal solution
- Simple Implementation: Often easier to implement than DP
- Efficiency: Usually O(n log n) or O(n) time complexity
Activity Selection Problem
// Problem: Select maximum number of activities that don't overlap
public int activitySelection(int[] start, int[] end) {
int n = start.length;
if (n == 0) return 0;
// Create pairs of (start, end) and sort by end time
int[][] activities = new int[n][2];
for (int i = 0; i < n; i++) {
activities[i][0] = start[i];
activities[i][1] = end[i];
}
Arrays.sort(activities, (a, b) -> a[1] - b[1]);
int count = 1;
int lastEnd = activities[0][1];
for (int i = 1; i < n; i++) {
if (activities[i][0] >= lastEnd) {
count++;
lastEnd = activities[i][1];
}
}
return count;
}Fractional Knapsack
// Problem: Fill knapsack with fractional items to maximize value
public double fractionalKnapsack(int[] weights, int[] values, int capacity) {
int n = weights.length;
// Create items with value per unit weight
double[][] items = new double[n][3];
for (int i = 0; i < n; i++) {
items[i][0] = weights[i];
items[i][1] = values[i];
items[i][2] = (double) values[i] / weights[i];
}
// Sort by value per unit weight (descending)
Arrays.sort(items, (a, b) -> Double.compare(b[2], a[2]));
double totalValue = 0.0;
int remainingCapacity = capacity;
for (int i = 0; i < n && remainingCapacity > 0; i++) {
double weight = items[i][0];
double value = items[i][1];
if (weight <= remainingCapacity) {
totalValue += value;
remainingCapacity -= weight;
} else {
// Take fractional part
double fraction = remainingCapacity / weight;
totalValue += value * fraction;
remainingCapacity = 0;
}
}
return totalValue;
}Huffman Coding
// Problem: Create optimal prefix code for characters
class HuffmanNode implements Comparable<HuffmanNode> {
char data;
int frequency;
HuffmanNode left, right;
public HuffmanNode(char data, int frequency) {
this.data = data;
this.frequency = frequency;
}
@Override
public int compareTo(HuffmanNode other) {
return this.frequency - other.frequency;
}
}
public HuffmanNode buildHuffmanTree(char[] chars, int[] freq) {
PriorityQueue<HuffmanNode> pq = new PriorityQueue<>();
// Create leaf nodes
for (int i = 0; i < chars.length; i++) {
pq.offer(new HuffmanNode(chars[i], freq[i]));
}
// Build tree
while (pq.size() > 1) {
HuffmanNode left = pq.poll();
HuffmanNode right = pq.poll();
HuffmanNode parent = new HuffmanNode('\0', left.frequency + right.frequency);
parent.left = left;
parent.right = right;
pq.offer(parent);
}
return pq.poll();
}Common Greedy Problems
- Scheduling: Activity selection, job scheduling
- Knapsack: Fractional knapsack (0/1 knapsack is not greedy)
- Compression: Huffman coding
- Graph: Minimum spanning tree (Kruskal's, Prim's)
- Coin Change: Making change with minimum coins (when greedy works)
- Gas Station: Circular tour problem