성장, 그 아름다운 향연
article thumbnail

[fastcampus 알고리즘 강의 내용 참고]

 


 

 

탐욕 알고리즘이란 최적의 해에 가까운 값을 구하기 위해 사용됩니다.

 

즉, 여러 가지 경우 중에서 하나를 결정할 때, 매 순간마다 최적의 경우만 선택하는 방식을

채택해서 최종적인 값을 구합니다.

 

 

 

예를 들어보겠습니다.


문제1) 동전 문제

 

1원, 50원, 100원, 500원 동전이 주어질 때, 가장 적게 지불하는 동전의 수를 구하기

 

//val coinList = listOf(1,100,50,500)

private fun Int.coinProblem(list: List<Int>) : Int {
	//외부로부터 숫자를 전달받을 때 항상 고정값이므로 Int.함수명을 이용

	//항상 최적을 따라야 하기 때문에 가장 큰 숫자부터 내림차순으로 정렬
	val coinList = list.sortedDescending()


    var coinCount = 0	// 전체 코인 수
    var money = this	// this는 Int.coinProblem을 뜻함
    var coinNum: Int	// 각각의 코인마다 소모된 수
    
    
    for(coin in coinList) {
        coinNum = money / coin
        coinCount += coinNum	

        money -= coinNum * coin	// 코인의 양 * 코인 
    }

    return coinCount
}

 

fun main() {
    val coinList = listOf(1,100,50,500)
    
    println(4720.coinProblem(coinList))
}

 

출력값

 

500원은 9개, 100원은 2개, 50원은 0개, 1원은 20개로 총 31개가 사용됨을 확인할 수 있습니다.

 

 

 

그리디 알고리즘 == 매 순간마다 최적의 경우를 탐색한다.

 

머릿 속에 떠올리셨나요?

다음 문제로 넘어가겠습니다.


문제2) 부분 배낭 문제

 

무게 제한이 K인 배낭에 최대 가치를 가지도록 물건을 넣는 문제 (무게 : w, 가치 : v)

 

위의 문제의 핵심은 물건을 "쪼갤 수 있다" 입니다. 그래서 Fractional Knapsack Problem이라고도 불리우며,

이와 반대되어 물건을 쪼개서 넣을 수 없는 배낭 문제는 0/1 Knapsack Problem 라고 합니다. 이는 그리디 알고리즘으로 해결할 수 없고, 동적 프로그래밍(DP)로 해결할 수 있습니다. 

 

 

다시 문제로 돌아와서, 각각의 물건의 무게, 가치를 설정하겠습니다.

물건 (i) 물건1 물건2 물건3 물건4 물건5
무게 (w) 20 15 10 25 30
가치 (v) 10 12 10 8 5

 

먼저 그리디 알고리즘은 매 순간 최적의 경우를 선택한다고 했습니다.

그렇다면 무게가 가장 작은 값으로부터 가방을 채워나가야 합니다.

 

코드로 설명하겠습니다.

 

//val backpack = hashMapOf(
//      20 to 1, 15 to 12, 10 to 10, 25 to 8, 30 to 5)


//반환형이 Double인 이유는 물건을 쪼갤 때 소수점이 포함될 수 있기 때문
private fun fractionalKnapsack(backpack : HashMap<Int,Int>, value : Int): Double {

	// .toSortedMap()으로 key에 대해 오름차순으로 정렬
    val sortedBackpack = backpack.toSortedMap()
    
    var totalValue = 0.0
    var capacity = value
    val list = mutableListOf<Any>() // (test)각각의 물건마다 얼만큼 저장됐는지 확인하는 리스트

    for(i in sortedBackpack) {
        if(capacity - i.key >= 0) {
            capacity -= i.key
            totalValue += i.value

            list.add(1)		//모든 물건이 들어갔음을 표시하기 위해 1 저장
        } else {
        
        	// double으로 소수점 이하를 출력하기 위해서는
            // 변수에 double형, 우측에서는 최소 한 개 이상 toDouble()으로 형 변환시켜야 함
            val fraction = capacity / i.key.toDouble()
            
            // 전체 값에다가 해당 가치를 넣을 수 있는만큼 쪼개는 작업
            totalValue += i.value * fraction
            
            list.add(fraction)
            break
        }
    }
    
    //test
    list.forEach {
        print("$it ")
    }
    println()
    
    return totalValue
}

 

fun main() {
    val backpack = hashMapOf(
        15 to 12, 10 to 10, 20 to 10, 25 to 8, 30 to 5)
    
    println(fractionalKnapsack(backpack, 30))
}

 

출력값

 

값이 정상적으로 출력됨을 확인할 수 있습니다.

이상 마무리하며 다시 머릿 속에서 떠올려 봅니다...

 

 

그리디 알고리즘 == 매 순간마다 최적의 경우를 탐색한다.


번외) 그리디 알고리즘의 한계

 

 

출처 : fun-coding

 

만약 가장 큰 값을 찾도록 시작 노드로부터 탐색한다고 가정할 때

그리디 알고리즘을 이용하면

 

710이 있을 때 -> 10 선택

57이 있을 때 -> 선택

 

하지만 최댓값은 15이기 때문에 모순임을 알 수 있다. 

profile

성장, 그 아름다운 향연

@dev_minoflower

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

profile on loading

Loading...