분할 전략, 동태 계획, 탐욕 선택 과 재 귀 간 의 관계 와 차이 (2)

지난 글 에서 우 리 는 분 치 전략, 동태 계획, 탐욕 선택 과 재 귀 간 의 관 계 를 분석 했다. 다음은 우 리 는 하나의 사례, 동전 찾기 문 제 를 통 해 분 치 알고리즘, 동적 계획 알고리즘, 욕심 산법 을 각각 설계 했다.
동전 거스름돈 문제: 현존 하 는 액면가 v1, v2, v3... 단위 의 동전 은 최소 몇 개의 동전 이 있어 야 총액 이 x 단위 의 잔돈 을 찾 을 수 있 습 니까?여기 서 우 리 는 v [] = {0, 1, 2, 5, 10, 20, 50} 을 가정 합 니 다.0 은 자릿수 를 채 우 는 데 쓰 인 다. 이렇게 v1, v2 와 아래 표 시 된 1, 2 가 서로 맞는다.여기 v1 은 1 이 어야 합 니 다. 1 이 아니라면 x 를 지정 하면 풀 수 없 을 수도 있 습 니 다. 즉, 찾 을 수 없습니다.예 를 들 어 v [] = {2, 5, 10, 20, 50}, x = 18, 풀 리 지 않 았 습 니 다.여기 서 우 리 는 편 의 를 위해 배열 v 를 정렬 한 것 으로 설정 합 니 다. 정렬 이 없 으 면 정렬 알고리즘 으로 해결 할 수 있 습 니 다.
1. 분 치 전략
분할 전략 알고리즘 을 설계 하기 전에 우 리 는 이 문 제 를 해결 하 는 전달 식 을 얻어 야 한다. 즉, 큰 문 제 를 어떻게 몇 개의 작은 문제 로 나 누 는 지 하 는 것 이다.
(1) 첫 번 째 방법: (이것 은 내 가 스스로 찾 아 낸 전달 식 이 니 조심 하 세 요. 하하)
f (x, i) 는 v [1], v [2],..., v [i] 로 0 x 에 필요 한 최소 동전 수 를 찾 으 려 면 다음 과 같이 전달 해 야 한다.
f(x, i)=min( f(x, i-1),  x/v[i] + f(x%v[i], vmax(x%v[i], v)))
그 중에서 vmax (x, v) 는 i 로 돌아 가 고 i 는 v [i] < = x 를 만족 시 킵 니 다.
여기 서 f (x, i) 라 는 큰 문 제 를 구 할 때 우 리 는 그것 을 분해 합 니 다. (Divide)
case 1, 우 리 는 v [i] 를 건 너 뛰 고 v [1], v [2],..., v [i - 1] 로 0 x, 즉 f (x, i - 1) 를 찾 습 니 다.(Conquer)
case 2, 우 리 는 v [i] 로 0 을 찾 아야 합 니 다. 0 을 x / v [i] + f (x% v [i], vmax (x% v [i], v) (Conquer) 로 찾 아야 합 니 다.
그럼 케이스 1, 케이스 2 의 가장 작은 것 은 f (x, i) 입 니 다.
코드 는 다음 과 같 습 니 다:
/*change.h*/
int min(int a, int b)
{
    if(a < b) 
        return a;
    return b;
}
/*
x:     
v:      
length:     
return i, v[i]<=x= v[i]) && (i < length))
    {
        i++;
    }
    return i-1;
}
/*
v:      
x:     
i:f(x,i)  i,   v[1],v[2],...,v[i]   
length:       
*/
int change_dc(int v[], int x, int i, int length)
{
    if(x == 1)
        return 1;
    if(x == 0)
        return 0;
    if(i == 1) // i 1 ,       ;        f(x,i-1) ,i  0
        return x;
    return min(change_dc(v, x, i-1, length), x/v[i]+change_dc(v, x%v[i], vmax(x,v, length), length));
}

(2) 두 번 째 방법:
따 오다http://www.ccs.neu.edu/home/jaa/CSG713.04F/Information/Handouts/dyn_prog.pdf)
전달 식 은 다음 과 같다.
c (x) = min (1 + c (x - v [i]), i 의 수치 범 위 는 i: x > = v [i] 입 니 다. 이 제한 조건 은 반드시 있어 야 합 니 다. 그렇지 않 으 면 마이너스 가 발생 합 니 다.
위 에서 제시 한 문 서 는 매우 상세 하 게 말 해 야 한다. 더 이상 말 하지 않 고 코드 를 직접 올 려 야 한다.
/*change.h*/
int change_dc2(int v[], int x, int length)
{
    int i;
    int min;
    int temp;
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 
    if(x == 0)
        return 0;
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 
    min = x;
    for(i=2; i= v[i])
        {
            temp = change_dc2(v, x-v[i], length) + 1;
            if(temp < min)
                min = temp;
        }
    }
    return min;
}

2. 동적 기획
f (1, 1), f (1, 2), c (1), c (2) 등 많은 하위 문제 가 여러 번 계산 되 는 것 을 발 견 했 을 것 이다. 그러면 우 리 는 동적 계획 으로 이런 문 제 를 해결 할 수 있다.여기 에는 두 가지 다른 방식 이 있 는데 하 나 는 위 에서 아래로 비망록 방식 (memoization) 이 고 하 나 는 아래 에서 위로 하 는 방식 이다.
(1) 위 에서 아래로 내 려 오 는 비망록 방식
우 리 는 쉽게 분할 전략 알고리즘 을 위 에서 아래로 비망록 방식 으로 바 꿀 수 있 습 니 다. 분할 전략 알고리즘 은 일반적으로 재 귀 하 는 사상 을 사용 하기 때문에 재 귀 는 위 에서 아래로 하 는 것 입 니 다. 여기 서 우 리 는 비망록 의 체 제 를 가입 하면 ok 입 니 다.바로 한 개의 문 제 를 풀 때마다 우 리 는 이 문 제 를 판단 할 때 이미 풀 었 고 이미 풀 었 다 면 직접 풀 었 다.만약 에 없 으 면 우 리 는 이 문 제 를 풀 고 이 문제 의 해 를 보존 하면 다음 에 이 문 제 를 풀 때 직접 사용 할 수 있다.
분할 정책 (1) 의 위 에서 아래로 비망록 방식:
/*change.h*/
#define MAX 20
/*
v:      
x:      
i:f(x, i)  i,   v[1],v[2],...,v[i]   x
length:         
c:  f(x, i)  ,c[x][i]  f(x, i),               -1,       
*/
int change_dp_memoization(int v[], int x, int i, int length, int c[][MAX])
{
    if(c[x][i] >= 0)
        return c[x][i];
                                                                                                                                                                                                           
    if(x == 1)
    {
        c[x][i] = 1;
        return c[x][i];
    }
    if(x == 0)
    {
        c[x][i] = 0;
        return c[x][i];
    }
    if(i == 1)
    {
        c[x][i] = x;
        return c[x][i];
    }
    c[x][i] = (change_dp_memoization(v, x, i-1, length, c), x/v[i]+change_dp_memoization(v, x%v[i], vmax(x,v, length), length, c));
    return c[x][i];
}

분할 정책 (2) 의 위 에서 아래로 비망록 방식:
/*change.h*/
/*
c:  c(x)  ,c[i] c(i)   ,           -1       
*/
int change_dp_memoization2(int v[], int x, int length, int c[])
{
    int i;
    int temp;
                                                                                                                                                                                                   
    if(c[x] >= 0)            //if c[x] has caculated
        return c[x];
                                                                                                                                                                                                   
    if(x == 0)
        return 0;
                                                                                                                                                                                               
    c[x] = x;
    for(i=1; i= v[i])
        {
            temp = change_dp_memoization2(v, x-v[i], length, c) + 1;
            if(temp < c[x])
                c[x] = temp;
        }
    }
    return c[x];
}

(2) 밑 에서 위로 올 라 가 는 방식
아래 에서 위로 향 하 는 방식 은 위 에서 아래로 향 하 는 것 과 정반 대 이다. 우 리 는 먼저 규모 가 작은 문 제 를 해결 한 다음 에 한 층 한 층 씩 나 아가 규모 가 약간 큰 문 제 를 구 할 때 사용 해 야 할 규모 가 작은 문 제 는 이미 해결 되 었 다.여기 서 우 리 는 위 에서 아래로, 아래 에서 위로 두 가지 다른 방식 을 구분 해 야 한다.위 에서 아래로 내 려 오 는 방식 으로 큰 문 제 를 풀 때 필요 한 작은 문 제 는 아직 해결 되 지 않 았 을 수도 있 고, 다른 약간 큰 문제 에 의 해 해결 되 었 을 수도 있 으 므 로, 우 리 는 이미 풀 린 문 제 를 저장 할 수 있 는 배열 이 필요 하 다.한편, 아래 에서 위로 향 하 는 방식 은 작은 문 제 를 해결 하기 전에 그 규모 보다 작은 모든 문제 가 해결 되 었 음 을 보증한다. 이렇게 큰 문 제 를 해결 할 때 그 에 포 함 된 작은 문 제 는 모두 해결 되 었 고 직접 가 져 와 서 사용한다.또 하 나 는 위 에서 아래로 일반적으로 재 귀적 으로 실현 하 는 것 이다. 큰 문 제 를 해결 할 때 어떤 작은 문 제 는 아직 해결 하지 못 하고 재 귀적 으로 계산 할 수 밖 에 없 기 때문이다. 위 에서 우 리 는 이미 보 았 다. 분 치 전략 을 가 진 알고리즘 에 비망록 체 제 를 더 하면 되 고 아래 에서 위로 일반적으로 반복 적 으로 실현 되 며 한 층 씩 위로 올 라 가 네가 원 하 는 문제 까지 할 수 있다.
분할 정책 (1) 의 아래 에서 위로 향 하 는 방식:
/*change.h*/
//int         
#define INFINITY (unsigned int)(1< n)
                c[m][n] = min(c[m][n-1], m/v[n] + c[m%v[n]][n]);
            else
                c[m][n] = min(c[m][n-1], m/v[imax] + c[m%v[imax]][n]);
        }
    }
    return c[x][i];
}

정책 (2) 의 아래 에서 위로 나 누 는 방식:
/*change.h*/
int change_dp_bottomup2(int v[], int x, int length, int c[])
{
    int i, j;
    int temp;
                                                                               
    for(i=1; i<=x; i++)
    {
        c[i] = i;
        for(j=2; j= v[j])
            {
                temp = c[i-v[j]] + 1;
                if(c[i] > temp)
                    c[i] = temp;
            }
        }
    }
    return c[x];
}

3. 욕심 선택
여기 서 우리 가 제시 한 거스름돈 배열 v [] 는 욕심 을 만족 시 키 는 선택 성격 이 므 로 여기 서 증명 하지 않 는 다.사실 2v [i] < = v [i + 1] 만 있 으 면 욕심 선택의 성격 을 만족시킨다.2v [i] < = v [i + 1] 을 만족 시 키 지 않 으 면 제로 문 제 를 욕심 산법 으로 해결 할 수 없다.예 를 들 어 v [] = {0, 1, 2, 5, 8, 10, 20, 50}, x = 16 은 2, 즉 8 액면가 의 동전 두 개 로 0 을 찾 아야 하고 욕심 산법 의 해 는 10, 5, 1 이다.다음은 욕심 산법 의 코드 를 드 립 니 다.
/*change.h*/
int change_greedy(int v[], int x, int length)
{
    int count = 0;
    int i;
           
    while(x > 0)
    {
        i = vmax(x, v, length);
        count += x/v[i];
        x = x%v[i];
    }
           
    return count;
}

좋은 웹페이지 즐겨찾기