{ }codelper

Learn › C++

Algorithms · 31

The greedy method and priority queues

Take what looks best at each step, and use a queue that hands out the smallest value right away.

The greedy method takes whatever looks best right now at every step.
A priority queue hands out the smallest (or largest) value first, whatever order the values went in.
The example below joins 5 piles of cards, two at a time, into one.
Each join costs as much effort as the two piles have cards in total; it always joins the two smallest piles, prints the effort of each join, and prints the sum of them all at the end.

#include <functional>#include <iostream>#include <queue>#include <vector> int main() {    int n;    std::cin >> n;    // std::greater를 줘야 가장 작은 값이 먼저 나온다(기본은 가장 큰 값)    std::priority_queue<int, std::vector<int>, std::greater<int>> piles;    for (int i = 0; i < n; i++) {        int x;        std::cin >> x;        piles.push(x);    }     int total = 0;    while (piles.size() > 1) {        int a = piles.top(); // 가장 작은 두 묶음을 꺼낸다        piles.pop();        int b = piles.top();        piles.pop();        int cost = a + b;        std::cout << cost << '\n';        total += cost;        piles.push(cost); // 합친 묶음을 다시 넣는다    }    std::cout << total << '\n';}
stdin
5
4 3 2 6 1
stdout
3
6
10
16
35

Key points

  • The greedy method does not look ahead: it takes the best thing right now.
    In the example that is the two smallest piles.
    A pile joined early gets added again in later joins, so joining small ones first keeps the numbers that are added many times small.
  • Greedy is not right for every problem.
    Making 6 from coins of 4, 3 and 1, taking the biggest coin first gives 4 + 1 + 1, 3 coins, while 3 + 3 needs only 2.
    With coins of 500, 100, 50 and 10, biggest first is right.
  • So before solving something greedily, compare it on small inputs with the answer from trying every possibility.
    If they differ even once, the rule is wrong.
    The example's rule matched on all 3,000 small inputs tried.
  • The example keeps taking out the two smallest values and putting their sum back in.
    New values keep arriving, so sorting once at the start is not enough.
    This is where a priority queue is used.
  • Inside, a priority queue keeps its values in a shape called a heap.
    It only guarantees that the front value is the smallest and does not keep the rest sorted, so adding and removing never walk over everything.
  • JavaScript, TypeScript, C and Craykon have no built-in priority queue.
    The example scans the list for the smallest value on every removal.
    At this size that is fine.

How each language writes it

Pythonimport heapq — turn a list into a heap with heapq.heapify(piles), then heappush(piles, x) and heappop(piles).
It only gives the smallest first, so to take the largest first push -x and flip the sign back after popping
JavaScriptThere is no built-in priority queue.
Find the position of the smallest value and take it out with splice(at, 1) as in the example; with many values, write a heap yourself
TypeScriptSame as JavaScript
JavaPriorityQueue<Integer> — add(x), poll() (remove), peek() (look only).
Smallest first; for largest first use new PriorityQueue<>(Comparator.reverseOrder()).
poll() on an empty queue gives null
C#PriorityQueue<int, int> — Enqueue(value, priority), Dequeue(), Peek().
The smallest priority comes out first, so for largest first give -x as the priority.
Dequeue() on an empty queue throws InvalidOperationException
C++std::priority_queue<int> in <queue> gives the largest first.
For smallest first use std::priority_queue<int, std::vector<int>, std::greater<int>>.
Look with top() and remove with pop() (pop() returns nothing)
CThere is no built-in priority queue.
Find the smallest value in the array and take it out as in the example; with many values, write a heap yourself
CraykonThere is no built-in priority queue.
Find the position of the smallest value and take it out with RemoveAt(at) as in the example.
This ran with up to 5,000 piles, but at 10,000 it was stopped for taking too long.
In that case, write a heap yourself

Common mistakes

Using greedy on a problem where it does not work

const coins = [4, 3, 1];
let left = 6, count = 0;
for (const c of coins) {
  count += Math.floor(left / c);
  left %= c;
}
console.log(count);

→ 3 — that is 4 + 1 + 1. With 3 + 3 it takes only 2

Check on small examples that the biggest-first rule is right.
A coin problem like this is solved with dynamic programming, filling a table with the answer for each amount.

Expecting std::priority_queue to give the smallest firstC++

std::priority_queue<int> piles;
for (int x : {4, 3, 2, 6, 1}) piles.push(x);
std::cout << piles.top() << '\n';

→ 6 — by default the largest value comes first

For smallest first, make it std::priority_queue<int, std::vector<int>, std::greater<int>>.
Python's heapq and Java's PriorityQueue are the other way round: smallest first.

Calling heappop without heapifyPython

import heapq
piles = [4, 3, 2, 6, 1]
print(heapq.heappop(piles))

→ 4 — not the smallest, 1, but the value that was at the front. There is no error either

Call heapq.heapify(piles) first, or start from an empty list and add each value with heappush.

How languages differ
Scanning a list to remove the smallest is only for small amounts of data.
Measured on this computer, JavaScript took well under a second for 10,000 piles but about 10 seconds for 100,000, while Python's heapq finished 100,000 in under a second.
In a language with no priority queue, write a heap yourself when there are many values, as the solutions to the practice problems do.

Practice