본 시리즈는 프로그래밍 알고리즘의 개념을 정리하고 실습을 진행해보는 시리즈입니다.
최적화 알고리즘은 주어진 문제에서 가장 효율적인 해를 찾는 알고리즘입니다.
대표적으로 백트래킹, 그리디, 동적 프로그래밍(DP) 이 세 가지 방법이 많이 사용되며, 각각의 방식은 문제에 대한 접근법과 해를 찾는 과정이 다릅니다.
탐색 공간 축소
최적화 알고리즘은 모든 경우의 수를 탐색하지 않고, 효율적으로 해를 찾기 위해 탐색 공간을 축소합니다.
해의 최적성 보장 여부
최적화 알고리즘은 해의 최적성 보장 여부가 다릅니다.
문제 유형에 맞는 최적화 방식
각 최적화 기법은 문제 유형에 따라 적합한 방식이 다릅니다.
이번 포스팅에서는 대표적인 최적화 방식 중에서 Greedy 알고리즘에 대해서 다루어 보고자 합니다.
Greedy 알고리즘은 매 단계에서 가장 최적이라고 생각되는 선택을 함으로써 문제를 해결하는 방법론입니다.
단순성
빠른 계산
전역 최적화 보장은 아님
문제의 특성에 맞는 최적화
예시: 거스름돈 문제의 그리디 알고리즘 적합성
[1, 5, 10, 50, 100]
일 때, 그리디 알고리즘은 가장 큰 동전부터 선택해도 항상 최적의 해를 보장합니다.87원
을 거슬러 줄 때, 50원
, 10원
, 10원
, 10원
, 5원
, 1원
, 1원
을 순서대로 선택하면 정확한 해가 됩니다.[1, 4, 5, 10, 40, 50, 100]
과 같은 경우, 그리디 알고리즘은 항상 최적해를 보장하지 못할 수 있습니다.8원
을 거슬러 줄 때 그리디 알고리즘은 5원
을 먼저 선택하고, 나머지 3원
을 거슬러 주려고 1원
을 세 번 선택합니다. 결과적으로 4개
의 동전이 필요합니다.4원
을 두 번 선택하는 것이며, 이 경우에는 2개
의 동전만으로 해결할 수 있습니다.거스름돈 예시에 대한 내용은 다음 파트에서 더 자세히 다루도록 하겠습니다.
결론적으로, 그리디 알고리즘은 매 순간의 선택이 전체 문제의 최적해로 이어질 수 있는 문제에 효과적이며, 그렇지 않은 경우엔 다른 최적화 기법이 필요할 수 있습니다.
Greedy 알고리즘은 여러 문제에 적용될 수 있으며, 특히 최소 비용을 구하는 문제나 최대 이익을 구하는 문제에서 효과적으로 사용됩니다.
문제: 거스름돈을 최소 개수의 동전으로 줄 수 있는 방법을 찾아보자.
- 동전 단위는
[1, 5, 10, 50, 100]
으로 가정합니다.
Java 코드
import java.util.Arrays;
public class CoinChangeGreedy {
public static void main(String[] args) {
int[] coins = {1, 5, 10, 50, 100};
int amount = 87;
int result = coinChange(coins, amount);
System.out.println("필요한 최소 동전 개수: " + result);
}
public static int coinChange(int[] coins, int amount) {
Arrays.sort(coins); // 동전 배열을 오름차순으로 정렬
int count = 0;
for (int i = coins.length - 1; i >= 0; i--) { // 큰 동전부터 선택
if (amount >= coins[i]) {
count += amount / coins[i]; // 동전 개수 추가
amount %= coins[i]; // 남은 금액 갱신
}
}
return count;
}
}
Python 코드
def coin_change(coins, amount):
coins.sort(reverse=True) # 동전 배열을 내림차순으로 정렬
count = 0
for coin in coins:
if amount >= coin:
count += amount // coin # 동전 개수 추가
amount %= coin # 남은 금액 갱신
return count
coins = [1, 5, 10, 50, 100]
amount = 87
result = coin_change(coins, amount)
print(f"필요한 최소 동전 개수: {result}")
- 출력 결과 (Java, Python 동일)
필요한 최소 동전 개수: 7
설명:
위 코드는 큰 단위의 동전부터 선택하는 그리디 방식으로 거스름돈 문제를 해결합니다.
문제: 여러 활동들이 시작 시간과 종료 시간이 주어졌을 때, 서로 겹치지 않게 가장 많은 활동을 선택하는 방법을 찾아보자.
Java 코드
import java.util.Arrays;
import java.util.Comparator;
public class ActivitySelection {
public static void main(String[] args) {
int[][] activities = {{1, 3}, {2, 5}, {4, 7}, {1, 8}, {5, 9}, {8, 10}};
int maxActivities = activitySelection(activities);
System.out.println("선택할 수 있는 최대 활동 개수: " + maxActivities);
}
public static int activitySelection(int[][] activities) {
// 종료 시간을 기준으로 활동 정렬
Arrays.sort(activities, Comparator.comparingInt(a -> a[1]));
int count = 1; // 첫 번째 활동은 항상 선택
int lastEnd = activities[0][1]; // 첫 번째 활동의 종료 시간
for (int i = 1; i < activities.length; i++) {
if (activities[i][0] >= lastEnd) { // 현재 활동의 시작 시간이 이전 활동의 종료 시간 이후라면 선택
count++;
lastEnd = activities[i][1]; // 마지막 선택한 활동의 종료 시간 갱신
}
}
return count;
}
}
Python 코드
def activity_selection(activities):
# 종료 시간을 기준으로 활동 정렬
activities.sort(key=lambda x: x[1])
count = 1 # 첫 번째 활동 선택
last_end = activities[0][1] # 첫 번째 활동의 종료 시간
for i in range(1, len(activities)):
if activities[i][0] >= last_end: # 다음 활동이 마지막 선택된 활동 이후에 시작하는 경우
count += 1
last_end = activities[i][1] # 종료 시간 갱신
return count
activities = [(1, 3), (2, 5), (4, 7), (1, 8), (5, 9), (8, 10)]
result = activity_selection(activities)
print(f"선택할 수 있는 최대 활동 개수: {result}")
- 출력 결과 (Java, Python 동일)
선택할 수 있는 최대 활동 개수: 3
설명:
위 코드는 종료 시간을 기준으로 활동을 정렬한 후, 그리디 방식으로 가장 많은 활동을 선택하는 방법을 구현한 것입니다.
그리디 알고리즘의 시간 복잡도는 문제의 특성에 따라 달라집니다.
예시 1: 거스름돈 문제
n
은 동전의 개수입니다.예시 2: 활동 선택 문제
Greedy 알고리즘은 보통 추가적인 메모리 사용이 적기 때문에 대부분 O(1)의 공간 복잡도를 가집니다.
이번 포스팅에서는 Greedy 알고리즘의 개념과 특징들을 살펴보았고, 거스름돈 문제와 활동 선택 문제를 통해 그리디 알고리즘의 작동 원리와 적용 방법을 살펴보았습니다.
다음 포스팅에서는 동적 프로그래밍(DP)을 다루겠습니다.