기본 콘텐츠로 건너뛰기

[알고리즘] 동적 계획법

분할정복

동적 계획법

동적 계획법(Dynamic Programming)은 복잡한 문제를 부분 문제들로 나누어 해결할 때, 메모이제이션을 통해 효율적으로 문제를 해결하는 기법이다. 동적 계획법은 대개 다음 두 가지 속성을 가진 문제에 대해 사용할 수 있다.

  1. 중복되는 부분 문제: 문제를 나누어 풀 때 같은 문제가 반복적으로 해결된다.
  2. 최적 부분 구조: 문제의 최적해가 그 부분 문제의 최적해로부터 구해진다.

글로만 설명하면 이해하기 어려우니 예제를 통해 살펴보자.

중복되는 부분 문제

피보나치 수열은 F(0) = 0, F(1) = 1, F(n) = F(n - 1) + F(n - 2) (n >= 2)로 정의되는 수열이다. 이를 재귀적으로 구현하면 다음과 같다.

이를 그대로 재귀함수로 구현하면 다음과 같다.

int fibonacci(int n) {
    if (n == 0) return 0;
    if (n == 1) return 1;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

위 코드에서 fibonacci(5)를 호출하면 결과적으로 fibonacci(2)를 3번씩이나 호출하게 된다.

overlapping-subproblems

이렇게 같은 부분 문제를 반복적으로 호출하는 것을 중복되는 부분 문제라고 한다. n이 커지면 커질수록 중복되는 부분 문제의 수가 기하급수적으로 증가하고, 똑같은 부분 문제를 풀기 위해 똑같은 재귀 호출 과정을 반복하게 된다. 이런 현상을 방지하기 위해 한번 계산한 부분 문제를 저장해두었다가 다시 계산하지 않도록 하는 것이 메모이제이션이다.

다음 코드는 메모이제이션을 적용한 피보나치 수열의 구현이다.

int cache[100]; // -1로 초기화

int fibonacci2(int n) {
    if (n == 0) return 0;
    if (n == 1) return 1;
    if (cache[n] != -1) return cache[n];
    return cache[n] = fibonacci2(n - 1) + fibonacci2(n - 2);
}

이는 실제로 속도의 측면에서 엄청난 차이를 보인다. fibonacci 함수는 n이 약 50이 넘어가면 계산이 불가능한 수준이 되지만, fibonacci2 함수는 n이 10,000,000이 넘어가도 1초 이내에 계산이 가능하다.

최적 부분 구조

optimal-substructure

각 칸에 숫자가 써있는 행렬의 왼쪽 위(0, 0)에서부터 오른쪽 아래(n - 1, n - 1)까지 이동할 때, 경로에 있는 숫자의 합을 최대화하는 문제를 생각해보자. (단, 한번에 오른쪽 또는 아래로만 이동할 수 있다.)

이 문제를 풀기 위해 다음과 같은 부분 문제를 정의할 수 있다.

  • maxpath(i, j): (i, j)에서 (n - 1, n - 1)까지 이동할 때, 경로에 있는 숫자의 합의 최댓값

그러면 다음과 같은 점화식을 세울 수 있다.

  • maxpath(i, j) = A[i][j] + max(maxpath(i + 1, j), maxpath(i, j + 1))

위 점화식을 메모이제이션을 이용해 구현하면 다음과 같다.

int n;
int A[100][100];
int cache[100][100]; // -1로 초기화

int maxpath(int i, int j) {
    if (i == n - 1 && j == n - 1) return A[i][j];
    if (cache[i][j] != -1) return cache[i][j];
    return cache[i][j] = A[i][j] + max(maxpath(i + 1, j), maxpath(i, j + 1));
}

위 문제를 메모이제이션을 적용하여 효율적으로 풀 수 있었던 이유는 부분 문제를 풀 때 (i, j)까지 어떤 경로로 왔는지에 대한 정보가 (i, j)에서 (n - 1, n - 1)까지의 최적해를 구하는 데에 전혀 영향을 끼치지 않기 때문이다.

이처럼 지금까지 어떤 경로로 부분 문제에 도달했건 간에, 전체 문제의 최적해가 부분 문제의 최적해로부터 구해진다면 이를 최적 부분 구조라고 한다.

동적 계획법을 통해 문제를 풀고자 한다면 최적 부분 구조를 만족하도록 부분 문제를 정의하는 것이 중요하다.

생각해보기

만약 위 문제에서 "숫자 10은 두번 이상 지나갈 수 없다."라는 조건이 추가된다면 어떻게 부분 문제를 정의해야 할까?

배낭 문제

knapsack

배낭 문제는 동적 계획법을 이용해 풀 수 있는 대표적인 문제이다. 문제의 정의는 다음과 같다.

각 물건은 무게와 가치를 가지고 있으며, 배낭에는 넣을 수 있는 최대 무게가 정해져 있다. 이때 배낭에 넣을 수 있는 물건들의 가치의 합의 최댓값은 얼마인가?

이 문제를 풀기 위해 다음과 같은 부분 문제를 정의할 수 있다.

  • knapsack(i, w): 0번째 물건부터 i번째 물건까지만 고려했을 때, 무게의 합이 w를 넘지 않도록 물건을 골랐을 때의 가치의 합의 최댓값

그러면 다음과 같은 점화식을 세울 수 있다.

  • knapsack(i, w): 다음 두 값 중 최댓값
    • knapsack(i - 1, w) i번째 물건을 넣지 않는 경우
    • knapsack(i - 1, w - weight[i]) + value[i] i번째 물건을 넣는 경우 (단, w >= weight[i]일 때만)

이를 동적 계획법을 이용해 구현하면 다음과 같다.

int n;
int weight[100];
int value[100];
int cache[100][100]; // -1로 초기화

int knapsack(int i, int w) {
    if (i == -1) return 0;
    if (cache[i][w] != -1) return cache[i][w];
    int ret = knapsack(i - 1, w);
    if (w >= weight[i]) ret = max(ret, knapsack(i - 1, w - weight[i]) + value[i]);
    return cache[i][w] = ret;
}

LIS: 최대 증가 부분 수열

https://algospot.com/judge/problem/read/LIS

부분 문제

  • lis(start): S[start]에서 시작하는 증가 부분 수열 중 최대 길이

코드

int n;
int S[500];
int cache[500]; // -1로 초기화

int lis(int start) {
    if (cache[start] != -1) return cache[start];
    int ret = 1;
    for (int next = start + 1; next < n; ++next) {
        if (S[start] < S[next]) ret = max(ret, lis(next) + 1);
    }
    return cache[start] = ret;
}

PI: 원주율 외우기

https://algospot.com/judge/problem/read/PI

부분 문제

  • pi(start): start부터 시작하는 숫자열을 외우는 데 드는 최소 난이도

코드

int n;
int digits[10000];
int cache[10000]; // -1로 초기화

int difficulty(int start, int end) // start부터 end까지의 난이도, 구현은 생략

int pi(int start) {
    if (start == n) return 0;
    if (cache[start] != -1) return cache[start];
    int ret = INF;
    for (int len = 3; len <= 5; ++len) {
        if (start + len <= n) ret = min(ret, pi(start + len) + difficulty(start, start + len - 1));
    }
    return cache[start] = ret;
}

이 블로그의 인기 게시물

[글로벌 IT전문가와 킹고인의 만남 시즌2] 행사 신청/참석 안내

  글로벌 IT전문가와 킹고인의 만남 시즌2에 대해 많은 관심 감사드립니다! 본 웹페이지를 통해서 학우님들의 원활한 행사 신청 및 참석을 위해 GDSC Community Platform 사용법을 안내드리고자 합니다 [카카오톡으로 링크 접속하신 경우 안내] 카카오톡 내장 브라우저에서 Google 로그인 시 "액세스 차단됨: Bevy의 요청이 Google 정책을 준수하지 않습니다"로 표시되는 사례가 확인되었습니다. 구글 계정 보안 정책상 카카오톡 내장 브라우저 내 로그인을 허용하지 않은 관계로, 디바이스에 설치된 기본 브라우저(Google Chrome 등)를 통해서 신청하시길 바랍니다. 👉 글로벌 IT전문가와 킹고인의 만남 시즌2 신청하기 플랫폼 인프라스트럭처 운영사/제공자: Google LLC/Bevy Labs, Inc. 행사 신청하기 1. GDSC 이벤트 플랫폼 웹사이트에서 구글 계정을 이용해서 로그인을 합니다. 2. (처음 로그인하는 경우) Sign up 페이지에서 필요한 정보를 입력합니다. 3. 로그인인 된 상태일 경우 "RSVP for this event now!" 아래에 온라인/오프라인 참석을 선택할 수 있습니다. 희망하시는 참석 방법 오른쪽에 있는 RSVP 버튼을 클릭하시면 됩니다. 4. RSVP 클릭 후 참석자 (Attendee Information) 입력하세요. (한글 설명, 학번, 전공 등) 5. RSVP Confirmed가 표시될 경우 신청이 완료되었음을 확인하실 수 있습니다. 행사 참석하기 (온라인) 행사가 시작될 경우 행사 웹페이지에서 [Join Event] 버튼이 표시됩니다. [Join Event] 버튼을 클릭하시면 바로 참석하실 수 있습니다. 참고: 행사 신청하신 경우 시스템 상 자동으로 이메일을 통해서 안내드립니다.

[모집] 2024학년도 1학기 신규인원 모집: 3/10(일) 마감

안녕하세요, 구글 기술 앰버서더, 성균관대학교 Google Developer Student Clubs 입니다! GDSC (Google Developer Student Clubs)는 Google에서 학생들이 개발/리더십 능력을 향상할 수 있도록 지원하는 대학생 커뮤니티 프로그램입니다. 성균관대학교 GDSC는 구글 코리아, Google for Developers, SW중심대학사업단 등 다양한 단체와 협업하여 구글 기술을 대중에 알리고 관련 행사를 주최하며, 이러한 프로그램을 통해 협업성, 인적 네트워킹 및 리더십을 향상할 수 있습니다.

[12월 행사] GDSC 연합해커톤 "나무톤" 행사 사진

GDSC 연합해커톤 "나무톤" 행사 사진 공유합니다.

[11월 행사] 머신러닝/인공지능 (ML/AI) 스터디 워크샵: 사전 신청 오픈! — Google Developers 전문가와 함께하는 머신러닝/인공지능 학습, 텐서플로우 실습 및 네트워킹 기회! (11/9 사전신청 마감)

  👉 사전 신청 종료 추가적인 사전 신청을 원하시는 경우 연락 페이지 를 통해서 문의하시길 바랍니다. 업데이트 (11/8): 본 행사는 정책상 참여자 분들께서 요청하실 경우 행사 참여 확인서를 발급해드릴 예정입니다. 행사 참석 당일날 스태프 분께 말씀하시면 됩니다. 업데이트 (11/9):  상세한 행사 정보가 부분적으로 오류가 있어서 정정했습니다. (행사 시작 시간은 변경되지 않았습니다.) 기타 문의하실 사항이 있으실 경우 연락 페이지를 통해서 문의주시면 감사하겠습니다.   안녕하세요, 성균관대학교 Google Developer Student Club (GDSC) 입니다. Google Developers 전문가 분들과 함께 저희 GDSC SKKU TensorFlow 팀에서 11월 10일 💻November ML/AI Study Workshop💻을 주최합니다! 👏🏼 프로그램에서는 TensorFlow 기초 이해부터 주요 신경망 모델링 및 학습까지 TensorFlow 기술 전반에 대한 실습이 진행되며 관련 전문가 분들과의 네트워킹 기회가 제공될 예정입니다. 🍔 또한 본 행사에서는 참가자분들을 위한 간식, 음료와 간단한 저녁식사도 준비되어 있습니다! 👇🏼 이벤트 상세 내용은 아래와 같습니다. 📍 일시: 11월 10일 (금) 16:00 ~ 20:30 📍 장소: 자연과학캠퍼스 화학관 1층 330102 첨단강의실 📍 참가대상: 성균관대학교 학부생 누구나 📍 프로그램 내용 I. TensorFlow 기초 이해 II. 주요 신경망 모델링 및 학습 (CNN, Cloud Run, RNN) III. 종료 및 네트워킹 📍 진행자 이영빈 님 (GDG Songdo Organizer) 한상준 님 (GDG Songdo Organizer) 권정민 님 (Google Developer Experts) 장현수 님 ((전)성균관대학교 박사) 📍 사전 신청링크 https://gdscskku.blogspot.com/mlai-study   머신러...

[62회] 매년 바뀌는 프론트엔드 분야에서 개발자가 살아남는 방법 - 글로벌 IT전문가와 킹고인의 만남 시즌2 예순두번째 만남

글로벌 IT전문가와 킹고인의 만남 시즌2 62회차 강연자를 소개합니다! 안녕하세요, 미래의 IT 리더 여러분! 세계적으로 인정받는 글로벌 IT 전문가 Naman Gupta가 성균관대학교를 방문해 대학생들을 대상으로 특별한 강연을 개최합니다. Naman Gupta 님는 Neurotone AI의 시니어 소프트웨어 엔지니어로 활동하며, Google Developer Groups 뉴델리의 공동 조직자이자 Google Summer of Code 2017 수상자입니다. Naman Gupta 님의 강연에서는 인공지능, 머신러닝, 웹 개발 등 최신 IT 트렌드와 혁신적인 제품 개발 방법을 배울 수 있습니다. 또한, 글로벌 기업에서의 커리어 쌓기와 스타트업 창업 노하우도 공유할 예정입니다. 특히 IT 분야에 관심 있는 대학생들에게 큰 도움이 되는 만큼 많은 참여 부탁드립니다! 매년 바뀌는 프론트엔드 분야에서 개발자가 살아남는 방법 Surviving as a Developer in the Ever-Changing Frontend Field 📆 일시: 2024년 9월 12일(목) 12:00 ~ 13:00 💡 강연방식: 온/오프 하이브리드 강연 🔎 강연자: Naman Gupta (GDG New Delhi/University of Wisconsin-Madison) 🎬 강연참여 - 오프라인 강연 참여 (20명 선착순) : 자연과학캠퍼스 삼성학술정보관 2층 솦ː공방 인(仁) 480209호 - 온라인 강연 참여 : 신청자에 한하여 신청 이메일로 강연링크 발송 * 오프라인 참여 학생 : 약속을 지키는 성균인! NoShow 불가! 🏆 참석혜택 - 오프라인 참석 학생들에게 간단 도시락 증정 (종료 후)  - 온/오프 참석 전원 AI품 비교과 1시간 인정 - 온/오프 참석 전원 킹고코인 마일리지 10코인 부여 👉 신청방법 가이드: https://gdscskku.blogspot.com/2024/03/itglobalseminar-help.html 👉 행사 참석 URL (신청 필요...