그리디 (Greedy)
정의
그리디 (Greedy) 알고리즘은 매 단계마다 국소 최적 (locally optimal) 선택을 하여 전역 최적해 (globally optimal solution) 에 도달하는 설계 패러다임. 핵심은 선택의 순서와 그 선택이 최적성을 해치지 않는다는 exchange argument 증명.
역사: 1950년대 Dijkstra 의 최단 경로, Kruskal/Prim 의 MST 등에서 정립. 1970년대 Huffman coding, activity selection 이론적 틀 완성.
문제 상황과 동기
“최적해를 찾아라” 유형 문제.
- naive (완전 탐색): 모든 선택 조합 탐색. O(2^N) 또는 O(N!). N=20 면 2^20 = 100만, N=50 이면 절대 불가능.
- 동적 계획법 (DP): 중복 부분 문제. O(N^2) ~ O(N^3). 메모리 많이 사용.
- greedy: 선택의 순서를 정렬 / 우선순위로 정한 뒤 한 번만 순회. O(N log N) (정렬) + O(N) (선택). 메모리 O(1) ~ O(N).
핵심 통찰: 선택을 바꿔도 (exchange) 최적성이 유지된다면 greedy 가 맞다.
자주 등장: activity selection, interval scheduling, fractional knapsack, Huffman, MST (Kruskal/Prim), Dijkstra, coin change (특정 동전 체계), load balancing.
시각화
핵심 아이디어
greedy choice property: 각 단계의 국소 최적 선택이 전역 최적 부분 문제로 이어진다.
optimal substructure: 문제의 최적해가 부분 문제의 최적해로 구성.
증명 패턴 (exchange argument):
- 최적해 OPT 가 greedy 선택과 다르다고 가정.
- OPT 의 첫 선택을 greedy 선택으로 교환.
- 교환 후 해가 여전히 최적 (또는 더 좋음) 임을 보임.
- 모든 선택을 greedy 로 바꿔도 최적성 유지 → greedy 가 최적.
예: activity selection (종료 시각 빠른 순으로 선택). 종료가 빠르면 이후 선택 가능한 구간이 최대로 남음. OPT 가 다른 걸 선택했다면 그걸 greedy 선택으로 바꿔도 충돌 없고 선택 가능 개수 유지 또는 증가.
알고리즘 (Activity Selection)
activity_selection(S):
// S: [(start, end)] 리스트
S.sort(by end time) // 종료 시각 오름차순
result = []
last_end = -∞
for (s, e) in S:
if s >= last_end:
result.append((s, e))
last_end = e
return result
시간: O(N log N) (정렬) + O(N) (선택) = O(N log N).
구현
// Activity Selection, 종료 빠른 순
#include <bits/stdc++.h>
using namespace std;
int main() {
int n; cin >> n;
vector<pair<int,int>> a(n);
for (auto& [s, e] : a) cin >> s >> e;
sort(a.begin(), a.end(), [](auto& x, auto& y){ return x.second < y.second; });
vector<pair<int,int>> res;
int last = -1e9;
for (auto [s, e] : a) {
if (s >= last) {
res.push_back({s, e});
last = e;
}
}
cout << res.size() << "\n";
for (auto [s, e] : res) cout << s << " " << e << "\n";
}6
1 3
2 5
3 7
4 6
5 8
7 93
1 3
4 6
7 9복잡도
| 항목 | 값 |
|---|---|
| 시간 (정렬) | O(N log N) |
| 시간 (선택) | O(N) |
| 전체 | O(N log N) |
| 공간 | O(1) ~ O(N) (정렬 inplace / 결과 저장) |
대표 문제와 greedy 기준
| 문제 | 정렬 기준 | 증명 핵심 |
|---|---|---|
| Activity Selection | 종료 빠른 순 | 종료 빠르면 이후 여유 최대 |
| Fractional Knapsack | 단위 가치 (v/w) 내림차순 | 단위당 비용 최고인 걸 먼저 |
| Huffman Coding | 빈도 낮은 2개 합치기 | 빈도 낮으면 depth 깊어도 전체 비용 감소 |
| Coin Change (특정 체계) | 큰 액면 먼저 | 액면이 배수 관계면 greedy 최적 |
| Load Balancing (LPT) | 작업 크기 내림차순, 가장 한가한 기계에 할당 | 1.5-근사 (최적 아닐 수 있음) |
변형 / 활용
- Interval Scheduling Maximization: activity selection.
- Minimum Spanning Tree: Kruskal, Prim 모두 greedy.
- Dijkstra: 최단 경로. 음수 간선 없으면 greedy 최적.
- Huffman: 최적 prefix-free code.
- Fractional Knapsack: 배낭 무게 제한, 물건 쪼개기 가능. 단위 가치 순.
- 0/1 Knapsack: 쪼개기 불가 → greedy 불가, 동적 계획법 필요.
함정
1. Greedy 가 최적이 아닌 경우
0/1 Knapsack: 물건 쪼개기 불가. 단위 가치 순 greedy 는 반례 존재.
예: 용량 10, 물건 [(w=6, v=30), (w=5, v=25), (w=4, v=24)]. 단위 가치: 5, 5, 6. greedy 는 (4,24) + (5,25) = 49. 최적은 (6,30) + (4,24) = 54.
2. Coin Change (일반 동전 체계)
동전이 배수 관계가 아니면 greedy 실패.
예: 동전 [1, 3, 4], 목표 6. greedy (큰 것 먼저): 4 + 1 + 1 = 3개. 최적: 3 + 3 = 2개.
미국/한국 동전 (1, 5, 10, 25, 100, …) 은 배수 관계 → greedy 최적.
3. 증명 없이 greedy 사용
직관적으로 “이게 맞을 것 같다” 만으로 greedy 쓰면 틀림. 반드시 exchange argument 또는 optimal substructure 증명 필요.
4. 정렬 기준 잘못 선택
Activity selection 에서 시작 빠른 순 / 길이 짧은 순 은 반례 존재. 종료 빠른 순만 최적.
예: [(1,10), (2,3), (4,5)]. 종료 순: (2,3), (4,5) → 2개. 시작 순: (1,10) → 1개.
BOJ 연습 문제
| 번호 | 제목 | 정답률 | 링크 |
|---|---|---|---|
| BOJ 11047 | 동전 0 | - | kokoa-lab |
| BOJ 1931 | 회의실 배정 | - | kokoa-lab |
| BOJ 2217 | 로프 | - | kokoa-lab |
| BOJ 1541 | 잃어버린 괄호 | - | kokoa-lab |
| BOJ 13305 | 주유소 | - | kokoa-lab |
참고
이 글의 용어 (4개)
- 다익스트라 알고리즘 (Dijkstra's Algorithm)algorithm
- 정의 다익스트라 알고리즘 (Dijkstra's Algorithm) 은 음이 아닌 가중치 그래프에서 단일 시작점 s 로부터 모든 정점까지의 최단 거리를 찾는 그리디 알고리즘. Ed…
- 동적 계획법 (Dynamic Programming)algorithm
- 정의 동적 계획법 (Dynamic Programming, DP) 은 큰 문제를 작은 부분 문제로 나누고, 각 부분 문제의 최적해를 저장하여 중복 계산을 제거하는 최적화 기법. R…
- 최소 신장 트리 (MST, Minimum Spanning Tree)algorithm
- 정의 최소 신장 트리 (Minimum Spanning Tree, MST) 는 연결 그래프 G = (V, E) 에서 모든 정점을 연결 하면서 간선 가중치 합이 최소 인 부분 그래프…
- Huffman Coding: 최적 접두 부호algorithm
- 정의 Huffman Coding 은 문자의 빈도가 주어졌을 때 평균 부호 길이를 최소화 하는 접두 부호 (prefix code) 를 만드는 그리디 알고리즘. David Huffm…
💬 댓글