그리디
그리디(Greedy) 알고리즘은 매 순간 지금 당장 가장 좋아 보이는 선택을 하고, 그 선택을 절대 번복하지 않는 전략입니다. algo-06(DP 기초)의 배낭 문제처럼 "모든 경우를 비교"하지 않고 바로 결정하기 때문에 훨씬 빠르지만, 항상 최적해를 보장하지는 않습니다.
핵심 개념
거스름돈 문제
거슬러줄 금액이 남아있는 한, 그 금액을 넘지 않는 가장 큰 동전을 계속 사용합니다. 한 번 사용한 동전은 되돌리지 않습니다.
핵심 개념
활동 선택 문제
겹치지 않는 활동을 최대한 많이 고르는 문제입니다. "종료 시간이 가장 빠른 활동"부터 순서대로 검토하며, 이미 선택한 활동과 겹치지 않으면 무조건 선택합니다.
핵심 개념
왜 종료 시간 기준인가
활동을 일찍 끝낼수록 남은 시간에 다른 활동을 더 많이 넣을 자리가 생깁니다. 그래서 "언제 시작하는가"가 아니라 "언제 끝나는가"가 유리한 정렬 기준입니다.
핵심 개념
되돌리지 않는다(번복 없음)
DP는 "포함 vs 제외" 두 경우를 모두 계산해 비교하지만, 그리디는 한 번 결정하면 끝입니다. 이 "번복 없음"이 그리디를 빠르게 만들지만, 동시에 최적해를 놓칠 위험이기도 합니다.
핵심 개념
그리디가 항상 통하지는 않는다
algo-06의 0/1 배낭 문제에서 "가치가 가장 높은 아이템부터 담기"는 그리디지만 최적해를 보장하지 못합니다. 반면 이 토픽의 두 문제는 이 시각화의 조건에서 그리디로 최적해가 나옵니다.
핵심 개념
시간복잡도
거스름돈은 동전 종류만큼, 활동 선택은 정렬 O(n log n) 이후 한 번 훑기만 하면 됩니다(O(n)). 모든 조합을 비교하는 DP보다 훨씬 빠릅니다.
그리디가 통하는 때와 아닐 때
그리디 알고리즘은 매 단계에서 지금 가장 좋아 보이는 선택을 하고 그것을 바꾸지 않는 방법입니다. 빠르고 단순하지만 어떤 문제에서든 최적이 되는 것은 아니어서, 쓰기 전에 그 문제에서 그리디가 맞다는 근거를 따져 봐야 합니다.
숫자로 확인하기
자주 하는 오해
면접에서 이렇게 물어요
면접 질문
그리디가 맞는지 어떻게 확인하나요?
그리디로 고른 선택을 최적해에도 포함시킬 수 있다는 것(그리디 선택 속성)과, 선택 후 남은 문제도 같은 구조라는 것(최적 부분 구조)을 보입니다. 보통 교환 논법으로 증명합니다.
면접 질문
거스름돈에서 그리디가 안 되는 반례를 들어 보세요.
동전이 500, 400, 100원이고 800원을 줄 때 그리디는 4개(500 + 100 × 3), 최적은 400원 2개입니다.
면접 질문
활동 선택에서 왜 종료 시간이 빠른 순인가요?
일찍 끝나는 활동을 고르면 이후에 쓸 수 있는 시간이 가장 많이 남습니다. 시작이 빠른 활동은 길게 이어져 뒤의 여러 활동을 막을 수 있습니다.
면접 질문
분할 가능 배낭은 그리디로 풀리는데 0/1 배낭은 왜 안 되나요?
분할 가능하면 무게 대비 가치가 높은 순으로 담다가 마지막에 쪼갤 수 있어서 최적입니다. 0/1은 쪼갤 수 없어 남는 용량이 낭비될 수 있고, 그래서 DP가 필요합니다.
실제 시스템에서는
함께 보면 좋은 토픽
참고 자료
Java — 거스름돈 (그리디)
/* ── 큰 동전부터 확인하며, 쓸 수 있으면 계속 사용 ──────────── */ public List<Integer> makeChange(int amount, int[] coins) { // coins는 반드시 큰 단위 → 작은 단위 순으로 정렬되어 있어야 함 List<Integer> used = new ArrayList<>(); for (int coin : coins) { while (amount >= coin) { used.add(coin); amount -= coin; // 결정을 절대 되돌리지 않음 } } return used; // amount가 0이 되면 끝 }
반복문이 두 겹이지만, 안쪽 while은 동전 하나당 최대 몇 번만 반복되므로 전체 시간은 동전 종류 수에 비례합니다 — DP처럼 표를 채우지 않아 훨씬 빠릅니다.
이 코드가 항상 최소 개수를 보장하는 것은 coins 배열의 단위 관계(예: 500=100×5) 덕분입니다. 단위가 애매하면 이 가정이 깨집니다.
Java — 활동 선택 (그리디)
/* ── 종료 시간 기준 정렬 후, 겹치지 않으면 무조건 선택 ────── */ public List<Activity> selectActivities(List<Activity> activities) { activities.sort((a, b) -> a.end - b.end); // 핵심: 종료 시간 오름차순 정렬 List<Activity> selected = new ArrayList<>(); int lastEnd = -1; for (Activity a : activities) { if (a.start >= lastEnd) { // 마지막 선택과 안 겹치면 selected.add(a); // 무조건 선택 (되돌리지 않음) lastEnd = a.end; } } return selected; }
a.start >= lastEnd 한 줄이 "겹치는지" 판단의 전부입니다 — 겹치지 않으면 무조건 선택하고, 그 선택을 절대 재검토하지 않습니다.
정렬에 O(n log n), 이후 한 번 훑는 데 O(n)이 걸려 전체 O(n log n)입니다. 모든 부분집합을 확인해야 하는 방식보다 압도적으로 빠릅니다.
Java — 그리디가 실패하는 예 (0/1 배낭 문제)
/* ── "가치 높은 순"으로 그리디하게 담으면? 최적해가 아닐 수 있음 ── */ public int greedyKnapsack(int[] weights, int[] values, int capacity) { // 가치 기준 내림차순 정렬 후, 담을 수 있으면 무조건 담기 (그리디) Integer[] idx = /* 가치 내림차순으로 정렬된 인덱스라고 가정 */ null; int totalValue = 0; for (int i : idx) { if (weights[i] <= capacity) { capacity -= weights[i]; totalValue += values[i]; // 한 번 담으면 되돌리지 않음 } } return totalValue; // algo-06의 DP 결과보다 작을 수 있음! }
이 코드는 문법적으로는 멀쩡하지만, "가치가 높다고 먼저 담은 아이템"이 무게를 많이 차지해 뒤에 더 효율적인 조합을 막아버릴 수 있습니다.
algo-06(DP 기초)에서 같은 문제를 dp[i][w] 표로 풀었을 때와 결과를 비교해보면, 그리디가 항상 통하지 않는다는 것을 직접 확인할 수 있습니다 — "포함/제외를 전부 비교하지 않고 하나로 확정해버린" 대가입니다.
Q1
그리디(탐욕) 알고리즘의 기본 전략은?
Q2
동전 거스름돈 문제에서 그리디가 항상 최적해를 보장하지 못하는 경우가 생기는 이유는?
Q3
활동 선택 문제(회의실 배정)에서 그리디가 최적인 이유로 자주 드는 기준은?