본문 바로가기
🖥️ Computer Science/Algorithm

[알고리즘] 그리디(Greedy)

by hyebin (Helia) 2022. 3. 8.

그리디(Greedy)

  • 탐욕법 알고리즘
  • 현재 상황에서 가장 좋은 것을 고르는 알고리즘

 

그리디  알고리즘 문제를 해결하는 방법

  1. 선택 절차(Selection Procedure): 현재 상태에서 최적의 해답 선택
  2. 적절성 검사(Feasibility Check): 선택된 해가 문제의 조건을 만족하는지 검사
  3. 해답 검사(Solution Check): 원래의 문제가 해결되었는지 검사, 해결되지 않았다면 선택 절차로 돌아와 위의 과정 반복

 

그리디 알고리즘 조건

  • 탐욕스러운 선택 조건(Greedy Choice Property): 앞의 선택이 이후의 선택에 영향을 주지 않는다
  • 최적 부분 구조(Optimal Substructure): 문제에 대한 최종 해결 방법은 부분 문제에 대한 최적 문제 해결 방법이다.

 


그리디 알고리즘 문제

거스름돈 문제

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

"가장 큰 화폐 단위부터" 돈을 거슬러 주는 방법으로 문제 해결 가능

 

var n = Int(readLine()!)!
var coin: [Int] = [500, 100, 50, 10]
var count = 0

for c in coin{
    count += n/c
    n %= c
}
print(count)

 

그리디 알고리즘 문제를 해결한 후 해법이 정당한지 검토해야 한다. 이 문제를 그리디 알고리즘으로 해결할 수 있는 이유는 문제의 동전 중에서 큰 단위가 항상 작은 단위의 배수이기 때문에 작은 단위의 동전들을 종합해 다른 해가 나올 수 없기 때문이다. 

 


큰 수의 법칙

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

배열의 크기 N, 더해지는 횟수 M, 그리고 K가 주어질 때 동빈이의 큰 수의 법칙에 따른 결과를 출력하시오.

가장 큰 수를 k번 더하고 두 번째로 큰 수를 한번 더하는 연산을 반복

var input = readLine()!.split(separator: " ").map{Int(String($0))!}
var data = readLine()!.split(separator: " ").map{Int(String($0))!}
var n = input[0], m = input[1], k = input[2]
var re = 0
data.sort()

while m != 0 {
    for _ in 0..<k{
        if m == 0{ break}
        re += data[n-1]
        m -= 1
    }
    if m == 0{ break }
    re += data[n-2]
    m -= 1
}

print(re)

n: 5 m: 8 k:3 arr: [2,4,5,4,6] 이 입력으로 들어올 때,

(6+6+6+5) + (6+6+6+5)로 정답은 46이다.

 

위의 예시에서 {6,6,6,5} 수열이 반복된다. 반복되는 수열의 길이는 (k+1)로, 반복되는 횟수는 m/(k+1)이다.

반복되는 횟수 m/(k+1)에 k를 곱하면 가장 큰 수의 등장 횟수가 된다.

이때, m이 (k+1)로 나누어 떨어지지 않는 경우를 고려해 m%(k+1) 연산을 하여 나머지 값을 구해야 한다.

따라서 가장 큰 수가 더해지는 횟수는 다음과 같다.

m/(k+1) * k  + m%(k+1)

var input = readLine()!.split(separator: " ").map{Int(String($0))!}
var data = readLine()!.split(separator: " ").map{Int(String($0))!}
var n = input[0], m = input[1], k = input[2]

data.sort()

var cnt = (m/(k+1)*k) + (m%(k+1))
var re = cnt*data[n-1] + (m-cnt)*data[n-2]
print(re)

 


숫자 카드 게임

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

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

각 행마다 가장 작은 수를 찾은 뒤에 그 수 중에서 가장 큰 수를 찾는 문제

var input = readLine()!.split(separator: " ").map{Int(String($0))!}
var n = input[0], m = input[1]

var re = 0

for _ in 0..<n{
    var data = readLine()!.split(separator: " ").map{Int(String($0))!}
    var min = data.min()!
    
    re = max(re, min)
}

print(re)

1이 될 때까지

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

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

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

최대한 많이 나누기 위해 N이 K로 나누어 떨어질 때까지 N을 1씩 감소시키고, N이 K로 나누어 떨어지면 N을 K로 나누는 연산을 반복

var input = readLine()!.split(separator: " ").map{Int(String($0))!}
var n = input[0], k = input[1]
var re = 0

while n>=k{
    while n%k != 0{
        n -= 1
        re += 1
    }
    n /= k
    re += 1
}
while n > 1{
    n -= 1
    re += 1
}

print(re)

N이 K의 배수가 되도록 효율적으로 한 번에 빼는 방식

var input = readLine()!.split(separator: " ").map{Int(String($0))!}
var n = input[0], k = input[1]
var re = 0

while true{
    var target = n/k * k
    re += n-target
    n = target
    
    if n<k{ break }
    re += 1
    n /= k
}

re += (n-1)
print(re)
반응형

댓글