모든 기록
Data Structure & Algorithm · 2026.09.09

3주차 - Greedy는 매 순간의 최선이 전체 최선이 되는 상황, 거스름돈과 회의실 배정으로 확인

매번 가장 큰 동전을 고르는 거스름돈 문제와, 종료 시간이 가장 빠른 회의부터 고르는 회의실 배정 문제로 그리디 알고리즘이 '순간의 최선'을 반복하는 원리를 확인.

3주차 - Greedy는 매 순간의 최선이 전체 최선이 되는 상황, 거스름돈과 회의실 배정으로 확인 대표 이미지

Greedy(탐욕) 알고리즘이다.

사실 이거를 공부할 때마다 드는 생각은 매 순간 가장 큰 이득을 가져다주는 것을 고르는 상황이 어떻게 알고리즘이지? 라는 생각이 든다.

def make_change_greedy(change, coins):
    """
    문제 설명:
- 그리디(Greedy) 알고리즘으로 거스름돈을 계산합니다.
- 가장 큰 단위의 동전부터 사용하여 최소 개수로 거슬러줍니다.
 
    그리디 알고리즘으로 거스름돈 계산
 
    Args:
        change: 거슬러줄 금액
        coins: 동전 종류 리스트 (큰 순서)
 
    Returns:
        (총 개수, {동전: 개수} 딕셔너리)
    """
    result = {}
    total_coins = 0
 
    for coin in coins:
        if change == 0: break
        result[coin] = 0
 
        while coin <= change:
            change -= coin
            result[coin] += 1
            total_coins += 1
 
            if change == 0: break
 
    del_list = []
 
    for coin in result:
        if result[coin] == 0:
            del_list.append(coin)
 
    for item in del_list:
        del result[item]
 
    return total_coins, result
 
# 테스트 케이스
if __name__ == "__main__":
    # 테스트 케이스 1
    change1 = 1260
    coins1 = [500, 100, 50, 10]
    total, details = make_change_greedy(change1, coins1)
 
    print("=== 거스름돈 계산 ===")
    print(f"거슬러줄 금액: {change1}원")
    for coin, count in details.items():
        print(f"{coin}원: {count}개")
    print(f"총 {total}개")
    print()
 
    # 테스트 케이스 2
    change2 = 4570
    coins2 = [500, 100, 50, 10]
    total, details = make_change_greedy(change2, coins2)
 
    print("=== 거스름돈 계산 ===")
    print(f"거슬러줄 금액: {change2}원")
    for coin, count in details.items():
        print(f"{coin}원: {count}개")
    print(f"총 {total}개")
    print()
 
    # 테스트 케이스 3
    change3 = 1000
    coins3 = [500, 100, 50, 10]
    total, details = make_change_greedy(change3, coins3)
 
    print("=== 거스름돈 계산 ===")
    print(f"거슬러줄 금액: {change3}원")
    for coin, count in details.items():
        print(f"{coin}원: {count}개")
    print(f"총 {total}개")

달성 목표

최소 개수의 동전을 사용하여 특정 목표 금액을 거슬러 줘야 한다.

동전의 개수와 금액은 반비례

사실 쉬운 문제이기 때문에 직관적으로 개수와 동전의 금액이 반비례 한다는 것이 직관적으로 드러난다.

그렇기에 500원 1개와 100원 5개가 있을 때 우리가 선택해야 하는 것이 무조건적으로 500원 1개이다.

지불 가능한 가장 큰 동전 고르기

이러한 식으로 개수를 기준으로 순간 최선의 선택은 지불 가능한 가장 큰 동전을 고르는 것이고 이러한 점을 그대로 구현하였다.

"""
[그리디 - 회의실 배정]
 
문제 설명:
- 하나의 회의실에 여러 회의를 배정합니다.
- 각 회의는 시작 시간과 종료 시간이 있습니다.
- 최대한 많은 회의를 배정하려고 합니다.
 
입력:
- meetings: [(시작, 종료), ...] 회의 리스트
 
출력:
- 배정된 회의 개수
 
예제:
입력: [(1, 4), (3, 5), (0, 6), (5, 7), (3, 8), (5, 9), (6, 10), (8, 11), (8, 12), (2, 13), (12, 14)]
출력: 4개
선택: [(1, 4), (5, 7), (8, 11), (12, 14)]
"""
 
def select_meetings(meetings):
    """
    회의실 배정 (그리디)
 
    Args:
        meetings: [(시작, 종료)] 리스트
 
    Returns:
        (배정된 회의 개수, 선택된 회의 리스트)
    """
    result = []
 
    def check_meet(start, end):
        if len(result) == 0:
            result.append((start, end))
        else:
            if result[-1][1] <= start:
                result.append((start, end))
 
    asc = sorted(meetings, key=lambda x: x[1], reverse=False)
    for meet in asc:
        check_meet(meet[0], meet[1])
 
    return len(result), result
 
if __name__ == "__main__":
    meetings1 = [(1, 4), (3, 5), (0, 6), (5, 7), (3, 8), (5, 9)]
    count1, selected1 = select_meetings(meetings1)
    print("=== 테스트 케이스 1 ===")
    print(f"전체 회의: {meetings1}")
    print(f"배정된 회의 개수: {count1}개")
    print(f"선택된 회의: {selected1}")
    print()
 
    meetings2 = [(1, 4), (3, 5), (0, 6), (5, 7), (3, 8), (5, 9), (6, 10), (8, 11), (8, 12), (2, 13), (12, 14)]
    count2, selected2 = select_meetings(meetings2)
    print("=== 테스트 케이스 2 ===")
    print(f"전체 회의: {len(meetings2)}개")
    print(f"배정된 회의 개수: {count2}개")
    print(f"선택된 회의: {selected2}")

회의실을 가장 빨리 비워주는 선택

매번 회의 종료 시간이 이른 것이 좋은 선택이다. 이렇게 될 경우 뒤에 남은 회의 시간이 최대가 되며 많은 회의를 받을 수 있는 기회가 늘기 때문이다.

그렇기 때문에 회의 종료 시간을 기준으로 정렬을 하고 진행을 하였다

다른 말로 회의 이후 남은 시간이 우리에게 이득이 되는 것이고 이를 최대화 시키는 것이 그리디이다.

이어 읽으면 좋은 기록

3주차 - 재귀로 짜는 이진 트리 순회, 그리고 BST가 성능을 보장하는 이유

전위/중위/후위 순회를 재귀로 구현하며 결과 배열을 함수 밖에 두는 대신 중첩 함수로 감싸는 방법부터, 이진 트리 구조 자체는 탐색 성능을 보장하지 않는다는 것, 균형이 무너지면 O(log n)이 O(n)이 되는 이유, 캐시 지역성 때문에 DB가 이진 트리 대신 B+트리를 쓰는 이유까지.

3주차 - 그래프 탐색, BFS와 DFS를 직접 구현하며 비교

인접 리스트로 무방향/방향 그래프를 만드는 것부터, Queue로 너비 우선 탐색하는 BFS와 재귀로 깊이 우선 탐색하는 DFS를 각각 구현하고 장단점을 비교. 시간 복잡도는 둘 다 O(V+E).