[그리디] 개념 & 예제

공부·2023년 1월 11일
0

출처 : 이것이 코딩 테스트다 with 파이썬

그리디(Greedy) 알고리즘은 어떤 문제가 있을 때 단순 무식하게, 탐욕적으로 문제를 푸는 알고리즘이다. 탐욕적이라는 말은 '현재 상황에서 지금 당장 좋은 것만 고르는 방법'을 의미한다. 그리디 알고리즘은 매 순간 가장 좋아보이는 것을 선택하며, 현재의 선택이 나중에 미칠 영향에 대해서는 고려하지 않는다.

그리디 알고리즘은 기준에 따라 좋은 것을 선택하는 알고리즘이므로 문제에서 '가장 큰 순서대로', '가장 작은 순서대로'와 같은 기준을 알게 모르게 제시해준다. 대체로 이 기준은 정렬 알고리즘을 사용했을 때 만족시킬 수 있으므로 그리디 알고리즘 문제는 자주 정렬 알고리즘과 짝을 이뤄 출제된다.

(1) 거스름돈 문제

당신은 음식점의 계산을 도와주는 점원이다. 카운터에는 거스름돈으로 사용할 500원, 100원, 50원, 10원짜리 동전이 무한히 존재한다고 가정한다. 손님에게 거슬러 줘야 할 돈이 N원일 때 거슬러 줘야 할 동전의 최소 개수를 구하라. 단, 거슬러 줘야 할 돈 N은 항상 10의 배수이다.

해설

  • 가장 큰 화폐 단위부터 돈을 거슬러주는 것.
n = 1260
count = 0

coin_types = [500, 100, 50, 10]

for coin in coin_types :
	count += (n // coin)
    n %= coin
   
print(count)
  • 거스름돈 문제가 그리디 알고리즘으로 해결할 수 있는 이유는 가지고 있는 동전 중에서 큰 단위가 항상 작은 단위의 배수이므로 작은 단위의 동전들을 종합해 다른 해가 나올 수 없기 때문이다.

(2) 큰 수의 법칙

동빈이의 큰 수의 법칙은 다양한 수로 이루어진 배열이 있을 때 주어진 수를 M번 더하여 가장 큰 수를 만드는 법칙이다. 단, 배열의 특정한 인덱스(번호)에 해당하는 수가 연속해서 K번을 초과하여 더해질 수 없는 것이 이 법칙의 특징이다.

해설

  • 가장 큰 수를 만드려면 가장 큰 수를 많이 더해야 한다. 그리고 연속해서 K번을 초과하면 안 되므로 K + 1번일 때 두번째로 큰 수를 더해야 한다. 코드는 다음과 같다.
n, m, k = map(int, input().split())
data = list(map(int, input().split()))

data.sort()
first = data[n - 1]
second = data[n - 2]

result = 0
  • 반복문을 통해 진행
while True :
	for i in range(k) :
    	if m == 0 :
        	break
            
    	result += first
        m -= 1
    
    if m == 0 :
    	break
    result += second
    m -= 1
       
print(result)
  • 하지만 수학적인 규칙(수열)을 알면 다음처럼 쉽게 처리할 수 있다.
# first가 나오는 개수 만큼 곱하여 result에 더한다.
count = m // (k + 1)
result += (m - count) * first
# second가 나오는 개수 만큼 곱하여 result에 더한다.
result += count * second

print(result)

(3) 숫자 카드 게임

숫자 카드 게임은 여러 개의 숫자 카드 중에서 가장 높은 숫자가 쓰인 카드 한 장을 뽑는 게임이다. 단, 게임의 룰을 지키며 카드를 뽑아야 하고 룰은 다음과 같다.

  1. 숫자가 쓰인 카드들이 N X M 형태로 놓여 있다. 이때 N은 행의 개수를 의미하며, M은 열의 개수를 의미한다.
  2. 먼저 뽑고자 하는 카드가 포함되어 있는 행을 선택한다.
  3. 그 다음 선택된 행에 포함된 카드들 중에 가장 숫자가 낮은 카드를 뽑아야 한다.
  4. 따라서 처음에 카드를 골라낼 행을 선택할 때, 이후에 해당 행에서 가장 숫자가 낮은 카드를 뽑을 것을 고려하여 최종적으로 가장 높은 숫자의 카드를 뽑을 수 있도록 전략을 세워야 한다.

해설

  • 각 행마다 가장 작은 수를 찾아 큰 수와 비교한다.
n, m = map(int, input().split())

for _ in range(n) :
	arr = list(map(int, input().split())
    
    min_value = min(arr)
    
    result = max(result, min_value)

print(result)

(4) 1이 될 때까지

어떠한 수 N이 1이 될 때까지 다음의 두 과정 중 하나를 반복적으로 선택하여 수행하려고 한다. 단 두번째 연산은 N이 k로 나누어떨어질 때만 선택할 수 있다.

  1. N에서 1을 뺀다.
  2. N을 K로 나눈다.

N과 K가 주어질 때 N이 1이 될 때까지 1번 혹은 2번의 과정을 수행해야 하는 최소 횟수를 구하는 프로그램을 작성하시오.

  • 최대한 많이 나누기
    1. N이 K의 배수가 될 때까지 1을 빼기
    1. N을 K로 나누기
n, k = map(int, input().split())
result = 0

while n >= k :
	while n % k != 0 :
    	n -= 1
        result += 1
    
    n //= k
    result += 1

while n > 1 :
	n -= 1
    result += 1
    
print(result)

while문 없이 몫과 나머지로 경우를 나누는 방법

while True :
	target = (n // k) * k # 순수 k의 배수
    result += (n - target) # while을 돌리는 대신 나머지만큼 결과에 삽입
    n = result
    
    if n < k :
    # 배수가 될 수 없다.
    	break
    
    result += 1
    n //= k

result += (n - 1)
print(result)
profile
리액트

0개의 댓글