Advanced Data Structures and Algorithm Design: Greedy Algorithms with Code Examples

4.2 Greedy Algorithm

1) Greedy Approach

The greedy algorithm is a method where at each step, the locally optimal choice is made in hopes of finding the global optimum. This approach divides the problem into steps and selects the best possible option at each stage. However, this does not guarantee the overall optimal solution.

Greedy algorithms are often used for optimization problems such as minimum spanning trees, knapsack problems, and activity selection.

Applications include:

  • Knapsack Problem: Maximize the value of items without exceeding the weight limit.
  • Activity Selection: Select non-overlapping activities to maximize the number of events.
  • Edit Distance: Find the minimum number of operations required to convert one string into another.
  • Network Flow: Determine the maximum flow from source to sink.
  • Coin Change: Minimize the number of coins needed for a given amount.

Common Questions:

  • Does the greedy algorithm always find the optimal solution? No, it may not, as local choices do not always lead to the global optimum.
  • How to determine if a problem can be solved using a greedy approach? If the problem can be decomposed into subproblems with clear optimal solutions, a greedy approach might work.
  • What is the time complexity of a greedy algorithm? It depends on the problem size and implemantation, but it typically ranges from O(n log n) to O(n^3).

Examples:

Dijkstra
while (!list.isEmpty()) {
    Vertex curr = chooseMinDistVertex(list);
    updateNeighboursDist(curr);
    list.remove(curr);
    curr.visited = true;
}

  • Example where it fails: when negative edges exist, leading to incorrect results.
  • Unlike Dijkstra, Bellman-Ford processes all edges repeatedly, ensuring correctness at the cost of efficiency.
Prim
while (!list.isEmpty()) {
    Vertex curr = chooseMinDistVertex(list);
    updateNeighboursDist(curr);
    list.remove(curr);
    curr.visited = true;
}

Kruskal
while (list.size() < size - 1) {
    Edge poll = queue.poll();
    int i = set.find(poll.start);
    int j = set.find(poll.end);
    if (i != j) {
        list.add(poll);
        set.union(i, j);
    }
}

Other examples include:

  • Selection sort, heap sort
  • Topological sorting
  • Union by size and union by height in disjoint sets
  • Huffman coding
  • Coin change, search keywords like 'change-making problem', 'minimum number of coins'
  • Task scheduling
  • Approximate solutions for complex problems

2) Coin Change Problem

Number of Ways to Make Change (Leetcode 518)
public class Leetcode518 {
    public int change(int[] coins, int amount) {
        return rec(0, coins, amount, new LinkedList<>(), true);
    }

    public int rec(int index, int[] coins, int remainder, LinkedList<Integer> stack, boolean first) {
        if (!first) {
            stack.push(coins[index]);
        }
        int count = 0;
        if (remainder < 0) {
            print("无解:", stack);
        } else if (remainder == 0) {
            print("有解:", stack);
            count = 1;
        } else {
            for (int i = index; i < coins.length; i++) {
                count += rec(i, coins, remainder - coins[i], stack, false);
            }
        }
        if (!stack.isEmpty()) {
            stack.pop();
        }
        return count;
    }

    private static void print(String prompt, LinkedList<Integer> stack) {
        ArrayList<Integer> print = new ArrayList<>();
        ListIterator<Integer> iterator = stack.listIterator(stack.size());
        while (iterator.hasPrevious()) {
            print.add(iterator.previous());
        }
        System.out.println(prompt + print);
    }

    public static void main(String[] args) {
        Leetcode518 leetcode = new Leetcode518();
        int count = leetcode.change(new int[]{15, 10, 1}, 21);
        System.out.println(count);
    }
}

Optimal Solution (Coin Change) - Brute Force (Leetcode 322)
public class Leetcode322 {
    static int min = -1;

    public int coinChange(int[] coins, int amount) {
        rec(0, coins, amount, new AtomicInteger(-1), new LinkedList<>(), true);
        return min;
    }

    public void rec(int index, int[] coins, int remainder, AtomicInteger count, LinkedList<Integer> stack, boolean first) {
        if (!first) {
            stack.push(coins[index]);
        }
        count.incrementAndGet();
        if (remainder == 0) {
            System.out.println(stack);
            if (min == -1) {
                min = count.get();
            } else {
                min = Integer.min(min, count.get());
            }
        } else if (remainder > 0) {
            for (int i = index; i < coins.length; i++) {
                rec(i, coins, remainder - coins[i], count, stack, false);
            }
        }
        count.decrementAndGet();
        if (!stack.isEmpty()) {
            stack.pop();
        }
    }

    public static void main(String[] args) {
        Leetcode322 leetcode = new Leetcode322();
        int count = leetcode.coinChange(new int[]{25, 10, 5, 1}, 41);
        System.out.println(count);
    }
}

Optimal Solution (Coin Change) - Greedy (Leetcode 322)
public class Leetcode322 {
    public int coinChange(int[] coins, int amount) {
        int remainder = amount;
        int count = 0;
        for (int coin : coins) {
            while (remainder - coin > 0) {
                remainder -= coin;
                count++;
            }
            if (remainder - coin == 0) {
                remainder = 0;
                count++;
                break;
            }
        }
        if (remainder > 0) {
            return -1;
        } else {
            return count;
        }
    }

    public static void main(String[] args) {
        Leetcode322 leetcode = new Leetcode322();
        int count = leetcode.coinChange(new int[]{5, 2, 1}, 5);
        System.out.println(count);
    }
}

3) Huffman Coding Problem

Introduction

Encoding is the process of mapping characters to numbers. For example, ASCII encoding maps 'a' to 0x61. The goal is to minimize the byte usage during transmission.

Huffman coding uses a binary tree structure to ensure prefix-free encoding, allowing efficient compression.

Huffman Tree
public class HuffmanTree {
    static class Node {
        Character ch;
        int freq;
        Node left;
        Node right;
        String code;

        public Node(Character ch) {
            this.ch = ch;
        }

        public Node(int freq, Node left, Node right) {
            this.freq = freq;
            this.left = left;
            this.right = right;
        }

        int freq() {
            return freq;
        }

        boolean isLeaf() {
            return left == null;
        }

        @Override
        public String toString() {
            return "Node{" +
                    "ch=" + ch +
                    ", freq=" + freq +
                    '}';
        }
    }

    String str;
    Map<Character, Node> map = new HashMap<>();

    public HuffmanTree(String str) {
        this.str = str;
        char[] chars = str.toCharArray();
        for (char c : chars) {
            Node node = map.computeIfAbsent(c, Node::new);
            node.freq++;
        }
        PriorityQueue<Node> queue = new PriorityQueue<>(Comparator.comparingInt(Node::freq));
        queue.addAll(map.values());
        while (queue.size() >= 2) {
            Node x = queue.poll();
            Node y = queue.poll();
            queue.offer(new Node(x.freq + y.freq, x, y));
        }
        Node root = queue.poll();
        int sum = dfs(root, new StringBuilder());
        for (Node node : map.values()) {
            System.out.println(node + " " + node.code);
        }
        System.out.println("总共会占用 bits:" + sum);
    }

    private int dfs(Node node, StringBuilder code) {
        int sum = 0;
        if (node.isLeaf()) {
            node.code = code.toString();
            sum = node.freq * code.length();
        } else {
            sum += dfs(node.left, code.append("0"));
            sum += dfs(node.right, code.append("1"));
        }
        if (code.length() > 0) {
            code.deleteCharAt(code.length() - 1);
        }
        return sum;
    }

    public static void main(String[] args) {
        new HuffmanTree("abbccccccc");
    }
}

Huffman Encoding and Decoding
public class HuffmanTree {
    // ... 

    public String encode() {
        char[] chars = str.toCharArray();
        StringBuilder sb = new StringBuilder();
        for (char c : chars) {
            sb.append(map.get(c).code);
        }
        return sb.toString();
    }

    public String decode(String str) {
        char[] chars = str.toCharArray();
        int i = 0;
        StringBuilder sb = new StringBuilder();
        Node node = root;
        while (i < chars.length) {
            if (!node.isLeaf()) {
                if (chars[i] == '0') {
                    node = node.left;
                } else {
                    node = node.right;
                }
                i++;
            }
            if (node.isLeaf()) {
                sb.append(node.ch);
                node = root;
            }
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        HuffmanTree tree = new HuffmanTree("abbccccccc");
        String encoded = tree.encode();
        System.out.println(encoded);
        System.out.println(tree.decode(encoded));
    }
}

Related Problems
Problem ID Problem Title Algorithm
1167 (Plus) Minimum Cost to Connect Sticks Huffman Tree, Greedy
Solution
public class Leetcode1167 {
    int connectSticks(int[] sticks) {
        PriorityQueue<Integer> queue = new PriorityQueue<>();
        for (int stick : sticks) {
            queue.offer(stick);
        }

        int sum = 0;
        while (queue.size() >= 2) {
            Integer x = queue.poll();
            Integer y = queue.poll();
            int c = x + y;
            sum += c;
            queue.offer(c);
        }
        return sum;
    }

    public static void main(String[] args) {
        Leetcode1167 leetcode = new Leetcode1167();
        System.out.println(leetcode.connectSticks(new int[]{1, 8, 3, 5}));
        System.out.println(leetcode.connectSticks(new int[]{2, 4, 3}));
    }
}

4) Activity Selection Problem

public class ActivitySelectionProblem {
    static class Activity {
        int index;
        int start;
        int finish;

        public Activity(int index, int start, int finish) {
            this.index = index;
            this.start = start;
            this.finish = finish;
        }

        @Override
        public String toString() {
            return "Activity(" + index + ")";
        }
    }

    public static void main(String[] args) {
        Activity[] activities = new Activity[]{
                new Activity(0, 1, 3),
                new Activity(1, 2, 4),
                new Activity(2, 3, 5)
        };
        select(activities, activities.length);
    }

    public static void select(Activity[] activities, int n) {
        List<Activity> result = new ArrayList<>();
        int i, j;
        i = 0;
        result.add(activities[i]);
        for (j = 1; j < n; j++) {
            if (activities[j].start >= activities[i].finish) {
                result.add(activities[j]);
                i = j;
            }
        }
        System.out.println(result);
    }
}

Non-overlappping Intervals - Leetcode 435
Problem ID Problem Title Algorithm
435 Non-overlapping Intervals Greedy
Solution
public int eraseOverlapIntervals(int[][] intervals) {
    Arrays.sort(intervals, Comparator.comparingInt(a -> a[1]));
    int i, j;
    i = 0;
    int count = 1;
    for (j = 1; j < intervals.length; j++) {
        if (intervals[j][0] >= intervals[i][1]) {
            i = j;
            count++;
        }
    }
    return intervals.length - count;
}

5) Fractional Knapsack Problem

Greedy Approach
public class FractionalKnapsackProblem {
    static class Item {
        int index;
        int weight;
        int value;

        public Item(int index, int weight, int value) {
            this.index = index;
            this.weight = weight;
            this.value = value;
        }

        int unitPrice() {
            return value / weight;
        }

        @Override
        public String toString() {
            return "Item(" + index + ")";
        }
    }

    public static void main(String[] args) {
        Item[] items = new Item[]{
                new Item(0, 4, 24),
                new Item(1, 8, 160),
                new Item(2, 2, 4000),
                new Item(3, 6, 108),
                new Item(4, 1, 4000),
        };
        select(items, 10);
    }

    static void select(Item[] items, int total) {
        Arrays.sort(items, Comparator.comparingInt(Item::unitPrice).reversed());
        int remainder = total;
        int max = 0;
        for (Item item : items) {
            if (remainder - item.weight > 0) {
                max += item.value;
                remainder -= item.weight;
            } else {
                max += remainder * item.unitPrice();
                break;
            }
        }
        System.out.println("最高价值为:" + max);
    }
}

6) 0-1 Knapsack Problem

Greedy Approach
public class KnapsackProblem {
    static class Item {
        int index;
        int weight;
        int value;

        public Item(int index, int weight, int value) {
            this.index = index;
            this.weight = weight;
            this.value = value;
        }

        public int unitValue() {
            return value / weight;
        }

        @Override
        public String toString() {
            return "Item(" + index + ")";
        }
    }

    public static void main(String[] args) {
        Item[] items = new Item[]{
                new Item(0, 1, 1_000_000),
                new Item(1, 4, 1600),
                new Item(2, 8, 2400),
                new Item(3, 5, 30)
        };
        select(items, 10);
    }

    static void select(Item[] items, int total) {
        Arrays.sort(items, Comparator.comparingInt(Item::unitValue).reversed());
        int max = 0;
        for (Item item : items) {
            System.out.println(item);
            if (total >= item.weight) {
                total -= item.weight;
                max += item.value;
            }
        }
        System.out.println("最大价值是:" + max);
    }
}

Limitations of Greedy Algorithms

Problem Name Can Greedy Find Optimal Solution? Alternative Solution
Dijkstra (No Negative Edges) ✓
Dijkstra (With Negative Edges) ✗ Bellman-Ford
Prim ✓
Kruskal ✓
Coin Change ✗ Dynamic Programming
Huffman Tree ✓
Activity Selection ✓
Fractional Knapsack ✓
0-1 Knapsack ✗ Dynamic Programming

7) Set Cover Problem

Set cover problem

Tags: Data Structures algorithms Greedy Algorithm huffman coding Knapsack Problem

Posted on Wed, 28 Oct 2026 16:16:39 +0000 by deolsabh