Branch And Bound 알고리즘 | [알고리즘실습] 제11강 분기한정 1. 배낭(Knapsack) 문제 분기한정으로 풀기 168 개의 베스트 답변

당신은 주제를 찾고 있습니까 “branch and bound 알고리즘 – [알고리즘실습] 제11강 분기한정 1. 배낭(Knapsack) 문제 분기한정으로 풀기“? 다음 카테고리의 웹사이트 th.taphoamini.com 에서 귀하의 모든 질문에 답변해 드립니다: th.taphoamini.com/wiki. 바로 아래에서 답을 찾을 수 있습니다. 작성자 주니온TV 아무거나연구소 이(가) 작성한 기사에는 조회수 1,510회 및 좋아요 8개 개의 좋아요가 있습니다.

Table of Contents

branch and bound 알고리즘 주제에 대한 동영상 보기

여기에서 이 주제에 대한 비디오를 시청하십시오. 주의 깊게 살펴보고 읽고 있는 내용에 대한 피드백을 제공하세요!

d여기에서 [알고리즘실습] 제11강 분기한정 1. 배낭(Knapsack) 문제 분기한정으로 풀기 – branch and bound 알고리즘 주제에 대한 세부정보를 참조하세요

#알고리즘실습 #분기한정 #배낭문제
경북대학교 컴퓨터학부 글로벌소프트웨어융합전공
알고리즘실습 2020년 1학기 11주차 강의
Foundations of Algorithms 5th Ed.,
by Richard E. Neapolitan.
Chapter 6. Branch-and-Bound
6.1. The 0-1 Knapsack Problem
주니온TV@Youtube – 자세히 보면 유익한 코딩 채널
https://bit.ly/2JXXGqz

branch and bound 알고리즘 주제에 대한 자세한 내용은 여기를 참조하세요.

알고리즘 기법[부분 탐색] – 분기 한정(Branch & Bound)

분기 한정(Branch & Bound) … 백트래킹이 허용되는 탐색에서 더 이상 탐색할 필요가 없는 지점을 판단하는 것. … -탐색과정에서 더 이상 의미가 없다고 …

+ 여기를 클릭

Source: hcr3066.tistory.com

Date Published: 3/25/2021

View: 1090

Branch and Bound Algorithm – GeeksforGeeks

Branch and bound is an algorithm design paradigm which is generally used for solving combinatorial optimization problems.

+ 자세한 내용은 여기를 클릭하십시오

Source: www.geeksforgeeks.org

Date Published: 5/26/2021

View: 2205

알고리즘(Algorithm) (Algorithm) – Branch-and-Bound (분기한정)

되추적 기법과 유사하게 상태공간트리를 구축하여 문제를. 해결한다. 최적의 해를 구하는 문제(optimization problem)에 적용할. Branch-and-Bound. 최적의 …

+ 여기에 자세히 보기

Source: cs.kangwon.ac.kr

Date Published: 10/6/2022

View: 6780

[알고리즘 – 이론] Branch-and-Bound Strategy (분기한정법) – 안녕

1. Branch and Bound란? … Branch and Bound(분기한정법)이란 BackTracking(되추적 기법)을 개선한 알고리즘이다. 분기한정법 또한 state space tree를 …

+ 여기에 더 보기

Source: hi-guten-tag.tistory.com

Date Published: 11/25/2022

View: 8629

분기한정법(Branch-and-bound)을 이용한 0-1 배낭채우기문제 …

하지만 되추적 알고리즘은 분기한정을 사용하여 얻을 수 있는 장점을 제대로 살리지 못한다. 그래서 이제부터는 마디가 유망한지 결정하기 위해 한계값을 …

+ 여기에 보기

Source: seungjuitmemo.tistory.com

Date Published: 11/26/2022

View: 4824

Branch and Bound Method (분기 한정법) – sepang’s dev – Tistory

이번에 알아볼 알고리즘 기법은 분기 한정법(Branch and Bound Method)이다. 이 글에서는 최적화 문제를 해결하기 위한 분기 한정 방법, …

+ 여기에 보기

Source: sepang2.tistory.com

Date Published: 7/26/2022

View: 1115

Branch and bound – Wikipedia

A branch-and-bound algorithm consists of a systematic enumeration of candate solutions by means of state space search: the set of candate solutions is …

+ 여기에 더 보기

Source: en.wikipedia.org

Date Published: 12/26/2022

View: 6662

[알고리즘, C++] 분기한정법 (Branch and Bound)

[알고리즘, C++] 분기한정법 (Branch and Bound). 제달이 2020. 5. 30. 19:43. 이번 포스트에서는 Branch and Bound(분기한정법) 기법에 대해서 다루도록 하겠습니다.

+ 여기에 자세히 보기

Source: buganddog.tistory.com

Date Published: 11/9/2022

View: 8696

[Algorithm Concept] 03: Branch and Bounds – 완숙의 에그머니

분기 한정법. Branch : 이전 상태 공간 트리에서 다음 가지로 넘어가는 경우를 말함; Bound : 한계; 이전의 백 트레킹과 크게 다르지 않다.

+ 자세한 내용은 여기를 클릭하십시오

Source: wansook0316.github.io

Date Published: 8/18/2022

View: 3441

분기한정법 : Branch and bound – AI Study

즉 lower bound 가 m 보다 큰 어떠한 노드도 폐기처분 될수있다. … 분기한정법의 효율성은 사용되는 branching and bounding algorithm 의 효과에 전적으로 의존한다 …

+ 여기에 표시

Source: www.aistudy.com

Date Published: 6/24/2021

View: 8188

주제와 관련된 이미지 branch and bound 알고리즘

주제와 관련된 더 많은 사진을 참조하십시오 [알고리즘실습] 제11강 분기한정 1. 배낭(Knapsack) 문제 분기한정으로 풀기. 댓글에서 더 많은 관련 이미지를 보거나 필요한 경우 더 많은 관련 기사를 볼 수 있습니다.

[알고리즘실습] 제11강 분기한정 1. 배낭(Knapsack) 문제 분기한정으로 풀기
[알고리즘실습] 제11강 분기한정 1. 배낭(Knapsack) 문제 분기한정으로 풀기

주제에 대한 기사 평가 branch and bound 알고리즘

  • Author: 주니온TV 아무거나연구소
  • Views: 조회수 1,510회
  • Likes: 좋아요 8개
  • Date Published: 2020. 5. 24.
  • Video Url link: https://www.youtube.com/watch?v=5xR5pf3-0nY

분기 한정(Branch & Bound)

분기 한정(Branch & Bound)

분기: branch, 한정: bound

→분기를 한정시켜 쓸데없는 시간 낭비를 줄이는 방법

백트래킹에서 부가적으로 발생한 알고리즘 설계 기법

백트래킹이 허용되는 탐색에서 더 이상 탐색할 필요가 없는 지점을 판단하는 것.

-비선형 구조의 탐색 과정에서 더 이상 나아갈 수 없는 상황에서 그 이전 단계로 복귀

-탐색과정에서 더 이상 의미가 없다고 판단되는 데이터에 대해서는 더 이상 탐색하지 않고 백트래킹함.

-나무에서 필요없는 가지를 잘라내는 가지치기와 유사하여 가지치기 기법이라고도 함.

탐색할 트리

만약 2번에서 3번으로 진행하려고 할 때, 3번 이하의 모든 노드를 더 이상 탐색할 필요가 없다고 판단할 경우,

이하의 탐색을 중단하고 9번으로 진행할 수 있다.

탐색공간의 축소

위 그림은 더 이상 탐색이 필요 없다는 것을 판단하고 분기를 제한하여 탐색을 배제한 결과를 나타낸다.

결과적으로 11회 탐색해야 할 문제를 6회의 탐색으로 동일한 결과를 얻을 수 있기 때문에 알고리즘의 효율을

향상시킬 수 있다.

Branch & Bound 원리

각 노드를 방문할 때 마다, 그 노드가 유망한지의 여부를 결정하기 위해서 한계치(bound)를 계산한다.

-한계치는 그 노드로부터 가지를 뻗어나가서(branch) 얻을 수 있는 해답치의 한계를 나타낸다.

-한계치가 이전까지 찾은 최고 해답값보다 더 좋으면 그 마디는 유망하다(Promising) .

→만약 한계치가 지금까지 찾은 최적의 해답치 보다 좋지 않은 경우는 더 이상 가지를 뻗어서 검색을 계속 할

필요가 없으므로, 그 노드는 유망하지 않다고 할 수 있다.(이 경우, 해당 서브트리를 가지치기(pruning)한다.)

Backtracking과의 공통점 및 차이점

공통점

-경우들을 차례로 나열하는 방법 필요

차이점

-Backtracking: 가보고 더 이상 진행이 되지 않으면 돌아온다.

-Branch&Bound: 최적해를 찾을 가능성이 없으면 분기를 하지 않는다.

Branch & Bound 예시 및 알고리즘

최대-최소 게임 트리 (max-min game tree)

-게임은 두 명의 경기자 중 하나만 이기면 상대는 진다. 따라서 제로합(zero-sum)게임이라 부르며 상대방의 반응을

고려하여 선택한다.

-게임은 나의 기대값을 최대화, 상대의 기대값을 최소화한다. 따라서 최대-최소게임 트리로 표현할 수 있다.

틱택톡 게임 방법은 오목과 유사하다. 가로나 세로, 대각선 한 줄 이상을 O 또는 X가 모두 차지하면 이기는 게임이다.

최대-최소 게임 트리(tic – tac – toe)

Branch and Bound Algorithm

Data Structure and Algorithms Course

Recent Articles on Branch and Bound

What is Branch and Bound Algorithm?

Branch and bound is an algorithm design paradigm which is generally used for solving combinatorial optimization problems. These problems are typically exponential in terms of time complexity and may require exploring all possible permutations in worst case. The Branch and Bound Algorithm technique solves these problems relatively quickly.

Example to show working of Branch and Bound Algorithm

Let us consider the 0/1 Knapsack problem to understand Branch and Bound.

There are many algorithms by which the knapsack problem can be solved:

Let’s see the Branch and Bound Approach to solve the 0/1 Knapsack problem: The Backtracking Solution can be optimized if we know a bound on best possible solution subtree rooted with every node. If the best in subtree is worse than current best, we can simply ignore this node and its subtrees. So we compute bound (best solution) for every node and compare the bound with current best solution before exploring the node.

Example bounds used in below diagram are, A down can give $315, B down can $275, C down can $225, D down can $125 and E down can $30.

If you like GeeksforGeeks and would like to contribute, you can also write an article and mail your article to [email protected]. See your article appearing on the GeeksforGeeks main page and help other Geeks.

Please write comments if you find anything incorrect, or if you want to share more information about the topic discussed above

이론] Branch-and-Bound Strategy (분기한정법)

1. Branch and Bound란?

Branch and Bound(분기한정법)이란 BackTracking(되추적 기법)을 개선한 알고리즘이다.

분기한정법 또한 state space tree를 사용한다.

하지만 분기한정법과 되추적의 차이점은

트리를 여행하는 방법에 제한을 두지 않는다.

오로지 최적화 문제에만 사용한다.

Backtracking에서는 dfs를 사용하여 상태공간트리를 탐색했지만,

branch and bound에서는 bfs를 사용하고, 또 Best-First-Search를 사용해서 더 효율적이게 트리를 탐색한다.

분기 한정법은 특정 마디가 유망한지를 결정하기 위해서 그 마디에서의 수, 한계값(Bound)을 계산한다.

그리고 최적의 한계값을 가진 노드의 자식을 방문한다.

이 방법은 DFS보다 빠르고, 자주 DFS보다 더 빨리 optimal solution에 도달한다.

만약 Bound 값이 지금까지 찾은 최고의 solution보다 좋지 않으면 해당 마디는 유망하지 않다(nonpromising)고 판단한다.

만약 Bound 값이 최고의 solution보다 좋다면 유망하다(promising)이라고 판단한다.

이러한 방법은 BFS를 조금 변환하면 구현할 수 있다.

BFS에서는 Queue를 사용하지만 Best-First-Search에서는 Priority Queue를 사용한다.

2. Branch and Bound for the 0-1 KnapSack Problem

0-1 KnapSack Problem을 따로 다룬 게시글이 있으므로 그것을 참고하면 될 것 같다.

https://hi-guten-tag.tistory.com/160

알고리즘: 분기한정법(Branch-and-bound)을 이용한 0-1 배낭채우기문제 (0-1 knapsack problem) 공부하기! :: Memo Memo

반응형

이번 포스팅은 분기한정법(branch-and-bound)을 이용한 0-1 배낭채우기 문제에 대해서 알아본다.

이전의 포스팅에서는 동적계획법(Dynamic programming)과 되추적(Backtracking)을 이용한

0-1 배낭채우기에 대해서 알아봤었다.

그렇다면 분기한정법이란 무엇일까?

분기한정법은 상태공간트리를 사용하여 문제를 푼다는 사실이 되추적과 매우 비슷한 설계방식이다.

다른 점이라고 한다면

1) 트리 횡단 방법에 구애받지 않고,

2) 최적화 문제를 푸는데만 쓰인다.

분기한정법에서는 되추적에서 더 나아가

어떤 마디가 유망한지 결정하기 위해서 한계값(bound)를 계산하고 그 한계값이 여태까지

찾은 최고 해답 값보다 더 좋으면 유망하다(promising)고 한다.

분기한정 알고리즘은 되추적 알고리즘의 경우와 마찬가지로 최악의 경우 보통 지수시간이지만

실제 사례에서 매우 효율적인 경우가 많이 검증되었다.

이전의 다루었던 되추적알고리즘을 이용한 0-1 배낭채우기문제는 사실 분기한정 알고리즘이다.

하지만 되추적 알고리즘은 분기한정을 사용하여 얻을 수 있는 장점을 제대로 살리지 못한다.

그래서 이제부터는 마디가 유망한지 결정하기 위해 한계값을 사용하는 것 이외에도

유망한 마디값들의 한계값을 비교하여 그 중에서 가장 좋은 한계값을 가진 마디의 자식마디를 방문할 것이다.

이 방법을 분기한정 가지치기 최고우선검색(best-fist serach with branch-and-bound prunning)이라고 한다.

이 방법은 너비우선탐색(breadth-fist-search)를 이용해서 구현할 수 있다.

우선 간단하게 너비우선탐색에 대해서 알아보자면 다음과 같다.

위와 같은 순서로 검색하는 방법이 너비우선탐색(BFS)

void breadth_first_search(tree T) { queue_of_node Q; node u, v; initialize(Q); // Initialize Q to be empty v = root of T; visit v; enqueue(Q,v); while(!empty(Q)) { dequeue(Q,v); for(each child u of v) { visit u; enqueue(Q,u); } } }

대기열(queue)를 사용해서 enqueue라는 프로시저로 대기열의 뒤에 아이템을 붙여 넣고

dequeue라는 프로시저로 앞에서 아이템을 제거한다.

이제부터 0-1 배낭채우기 문제에 분기한정법을 어떻게 적용하는지 살펴보자

다음과 같은 아이템 내역들이 있다고 가정하자.

분기한정 가지치기 너비우선검색은 깊이우선검색 대신 너비우선검색을 하는 것을 제외하고

되추적 방법과 똑같이 진행된다.

weight와 profit은 그 마디에 오기까지 포함되었던 아이템의 총 무게와 총 이익으로 한다.

마디가 유망한지 결정하기 위해서 totweight와 bound를 weight와 profit으로 각각 초기화하고,

그 다음 toweight가 W를 초과하게 하는 아이템에 도달할 때까지 탐욕적으로 아이템을 취하여 그 무게와 totweight와 bound에 각각 더한다.

배낭에 넣을 수 있는 만큼만 그 아이템의 일부분을 취하여 그 부분의 weight를 toweight에 더한다.

이런 식으로 bound는 그 마디에서 확장하여 얻을 수 있는 이익의 상한이 된다.

만약 그 수준i에 위치하고 있고, 수준 k에 위치한 마디는 무게의 합이 W가 넘어가는 마디라고 했을 때

다음과 같은 식을 얻는다.

아울러 weight >= W 이거나

지금까지 찾은 최고 해답에서의 값인 maxprofit보다 이 한계값이 작거나 같으면

그 마디는 유망하지 않다.

위의 예제에 대해 이 방식을 적용하여 가지치기하면 다음과 같은 상태공간트리를 얻을 수 있다.

여기서 (3, 1)과 (4, 3)은 한계값이 0인 경우가 되추적 알고리즘과 다르다.

분기한정 알고리즘은 어떤 마디에서 확장 여부를 결정할 때 지금까지 구한 답 중에서 가장 좋은 값보다 한

계값이 더 좋은 지 검사한다.

따라서 이경우에는 가중치가 W보다 크거나 같기 때문에 마디가 유망하지 않게 되면서 한계값을 0으로 둔다.

하지만 이렇게 너비우선검색으로 검색했을 때 검색 마디갯수는 17이다. 이는 되추적 알고리즘보다 좋지 않다.

이는 깊이우선검색과는 달리 너비우선검색에서는 maxprofit의 값이 자식마디를 실제로 방문할 때

변해버릴 수 있기 때문에 검색시간을 낭비하게 되는 것이다.

void breadth_first_branch_and_bound(state_space_tree T, number& best){ queue_of_node Q; node u, v; initialize(Q); // Initialize Q to be empty v = root of T; // Visit root enqueue(Q,v); best = value(v); while(!empty(Q)) { dequeue(Q,v); for(each child u of v) { // Visit each child if(value(u) is better than best) best = value(u); if(bound(u) is better than best) enqueue(Q,u); } } }

이 알고리즘은 위에서 살펴봤던 너비우선탐색을 수정한 것이다.

이 알고리즘에서는 마디의 한계값이 당시 최고 해답의 값보다 좋은 경우에만 그 마디에서 계속 확장한다.

당시 최고 해답의 값(best)는 뿌리마디에서의 해답의 값으로 초기화하기 때문에 best를

어떤 해답보다도 나쁜 값으로 초기화하는 낭비를 한다.

이제부터는 위 방식을 이용하여 0-1배낭채우기 문제를 푸는 알고리즘을 살펴본다.

재귀를 사용하지 않기 때문에 다음과 같은 노드를 정의한다.

Struct node { int level; int profit; int weight; }

<아직은 부족한 knapsack problem algorithm1>

void knapsack2(int n, const int p[], cont int w[], int W, int &maxprofit) { queue_of_node Q; node u, v; initialize(Q); v.level =0; v.profit = 0; v.weight = 0; maxprofit = 0; enqueue(Q, v); while (!empty(Q)) { dequeue(Q, v); u.level = v.level+1; u.profit = v.profit + p[u.level]; u.weight = v.weight + w[u.level]; if ((u.weight <= W) && (u.profit > maxprofit)) maxprofit = u.profit; if (bound(u)>maxprofit) enqueue(Q, u); u.weight = v.weight; u.profit = v.profit; if (bound(u)>maxprofit) enqueue(Q, u); } } // 문제점 : 경로 출력? include, bestset 출력

float bound(node u) { index j, k; int totweight; float result; if (u.weight >= W) return 0; else { result = u.profit; j = u.level +1; totweight = u.weight; while ((j<=n) && (totweight + w[j] <= W)) { totweight = totweight + w[j]; result = result + p[j]; j++; } k = j; if (k<= n) result = result + (W - totweight)*p[k]/w[k]; return result; } } 일반적으로 너비우선검색 전략은 깊이우선검색보다 좋은 점이 없다. 그러나 마디의 유망성 여부를 결정하는 것 이외에 추가적인 용도로 한계값을 사용하면 검색을 향상시킬 수 있다. 주어진 어떤 마디의 모든 자식마디를 방문한 후 유망하면서 확장하지 않은 모든 마디의 모두 살펴보고 그 중에서 한계값이 가장 좋은 마디를 우선적으로 확장한다. 지금까지 찾은 최고의 해답보다 그 한계값이 좋다면 그 마디는 유망하다. 미리 정해 놓은 순서대로 무작정 검색을 진행하는 것보다 이런식으로 하면 최적해를 더 빨리 찾게 된다. 이렇게 최고우선검색(Best-First-Search)를 하면 일반적으로 너비 우선검색에 비해서 좋아진다. 그렇다면 이제부터 최고우선검색을 이용한 우리한 필요한 knapsack problem알고리즘을 알아보겠다. 검색 절차는 다음과 같다. (볼드체로 표시된 부분을 주의깊게 보자 ) 1. 마디(0, 0)을 방문한다. a) 이익과 무게를 0으로 둔다. b) 한계값을 계산하면 115가 된다. c) maxprofit은 0이 된다. 2. 마디 (1, 1)을 방문한다. a) 이익과 무게를 계산하면, 각각 40과 2가 된다. b) maxprofit은 40이 된다. c) 한계값을 계산하면 115가 된다. 3. 마디(1, 2)을 방문한다. a) 이익과 무게를 0으로 둔다. b) 한계값을 계산하면 82가 된다. c) maxprofit은 0이 된다. 4. 아직 확장되지 않은 마디 중에서 가장 큰 한계값을 가진 유망한 마디를 구한다. a) 마디(1, 1)은 한계값이 115이고, 마디(1, 2)는 82이므로 마디(1,1)이 한계값이 가장 크면서 유망하고, 확장하지 않은 마디이다. 그 마디의 자식마디를 다음에 방문한다. 5. 마디 (2, 1)을 방문한다. a) 이익과 무게를 계산하면, 각각 70과 7이 된다. b) maxprofit은 70이 된다. c) 한계값을 계산하면 115가 된다. 6. 마디(2, 2)을 방문한다. a) 이익과 무게가 40, 2가 된다. b) 한계값을 계산하면 98이 된다. 7. 아직 확장하지 않은 마디 중에서 가장 큰 한계값을 가진 유망한 마디를 구한다. a) (2, 1)이 가장 유망므로 그 마디의 자식마디를 다음에 방문한다. 8. 마디 (3,1)을 방문한다. a) 이익과 무게를 계산하면 120, 17이 된다. b) weight가 w보다 크므로 이 마디는 유망하지 않다. 한계값을 0으로 둔다. 9. 마디 (3, 2)을 방문한다. a) 이익과 무게를 계산하면 70, 70 이 된다. b) 한계값을 계산하면 80이 된다. 10. 아직 확장하지 않은 마디 중에서 가장 큰 한계값을 가진 유망한 마디를 구한다. a) (2, 2)가 가장 한계 값이 크므로 유망하다. 다음에 그 마디의 자식마디를 방문한다. 11. 마디 (3,3)을 방문한다. a) 이익과 무게를 계산하면 90과 12가 된다. b) 12 < W이므로 maxprofit이 90으로 갱신하고 한계값 82, 80 보다 크므로 (3, 1)과 (3,2)는 유망하지 않게 된다. c) 한계값을 계산하면 98이 된다. 위와 같은 방식으로 검색하다가 유망하고 확장하는 마디가 없으면 작업이 완료된다. 최고우선검색으로 11개의 마디만 검색했는데, 일반 너비우선검색보다 검색한 마디의 수가 6개 적고, 깊이우선검색보다 2개 적다. 최고우선검색은 너비우선검색을 조금 변형시켜서 구현하면 된다. queue 대신 우선순위 대기열(priority queue)를 사용한다. 우선순위 대기열은 우선순위가 높은 구성요소를 항상 먼저 제거한다. 우선순위 대기열은 heap을 이용해서 구현할 수 있다. void best_first_branch_and_bound (state_space_tree T,number best) { priority_queue_of_node PQ; node u,v; initialize(PQ); // initialize PQ to empty v = root of T; best = value(v); insert(PQ,v); while(!empty(PQ)) { // Remove node with best bound remove(PQ,v); if(bound(v) is better than best) // Check if node is still promising for(each child u of v) { if(value(u) is better than best) best = value(u); if(bound(u) is better than best) insert(PQ,u); } } } < 최고우선검색 알고리즘을 이용한 0-1 knapsack problem>

void knapsack3(int n, const int p[], cont int w[], int W, int &maxprofit){ queue_of_node PQ; node u, v; initialize(PQ); v.level =0; v.profit = 0; v.weight = 0; v.bound = bound(v); maxprofit = 0; insert (PQ, v); while (!empty(Q)) { remove(PQ, v); if(v.bound > maxprofit) { u.level = v.level+1; u.profit = v.profit + p[u.level]; u.weight = v.weight + w[u.level]; if ((u.weight <= W) && (u.profit > maxprofit)) maxprofit = u.profit; u.bound = bound(u); if (bound(u) > maxprofit) insert (PQ, u); u.weight = v.weight; // Set u to the child u.profit = v.profit; // that does not include u.bound = bound(u); // the next item. if (u.bound > maxprofit) insert (PQ, u); } } }

(bound는 이전에 사용했던 것을 이용)

출처 : Foundations of Algorithms, Using C++ Pseudocode

반응형

Branch and Bound Method (분기 한정법)

이번에 알아볼 알고리즘 기법은 분기 한정법(Branch and Bound Method)이다. 이 글에서는 최적화 문제를 해결하기 위한 분기 한정 방법, 비슷한 기법인 역추적 기법과의 차이점을 알아볼 것이다. 그리고 어떤 문제가 분기 한정법을 사용하기에 적절한 문제인지 식별해보고, 이전 부터 계속 해왔던 0/1 배낭 문제를 해당 기법을 이용하여 해결해 볼 것이다.

Introduction to Branch and Bound

역추적 알고리즘과 마찬가지로 분기 한정 알고리즘도 상태 공간을 사용하여 조합 문제를 해결한다. 그러나 분기 한정법은 방문할 다음 노드가 런타임 중에 결정되고 입력 인스턴스마다 다르기 때문에 트리 탐색 순서를 미리 결정하지 않는다. 또한 역추적 알고리즘은 최적화/비최적화 문제를 모두 해결할 수 있으나, 분기 한정 알고리즘은 최적화 문제만 해결하도록 설계되었다.

역추적에서의 promising function에서는 부울 값을 사용했지만, 분기 한정법의 promising function에는 숫자 값을 사용한다. 해당 수치는 노드를 넘어 확장하여 얻을 수 있는 해의 값에 경계(bound)를 얻기 위해 사용된다. 경계 값이 지금까지 찾은 최적 값보다 좋지 않으면 해당 노드는 유망하지 않고, 그렇지 않으면 해당 노드는 유망하다. 역추적 알고리즘과 분기 한정 알고리즘의 유사점과 차이점을 정리하면 다음과 같다.

역추적 분기 한정 유사점1 상태 공간 트리를 사용하여 문제 해결 유사점2 최적화 문제의 경우, 이론적 상한이 각 노드에서 계산되어 유망성을 결정 유사점3 worst-case일 경우네는 기하급수적(exponential) 시간 복잡도를 가짐 차이점1 깊이 우선 탐색(DFS) 트리 순회라고 하는 미리 결정된 순서로 트리 탐색 트리에서 방문하는 다음 노드는 많은 유망 노드 중 가장 좋은 유망 노드를 선택하여 런타임 동안 결정된다. 이는 너비 우선 탐색이 수정된 최상 우선 탐색이 사용됨 차이점2 최적화 및 비 최적화 문제에 사용 최적화 문제에만 사용

Breadth First Search

분기 제한 알고리즘은 너비 우선 탐색(BFS, Breadth First Search)을 수정한 최상 우선 탐색(Best First Search)을 구현한다. 때문에 BFS에 대해 먼저 간단히 알아보자.

BFS에서는 루트 노드를 먼저 방문하고 fig 1.1에 표시된 대로 level-1의 모든 노드를 방문한 다음 level-2의 모든 노드를 방문하고 마지막으로 level-n의 모든 노드를 방문한다. 때문에 깊이 우선 탐색과 달리 단순 재귀 알고리즘이 없고 스택(FILO) 대신 큐(FIFO) 자료구조를 사용하여 구현 할 수 있다.

fig 1.1

Pseudocode of BFS

Without Pruning

void breadth-first-tree-search(tree T){ queue_of_node Q; node child, parent; initialize(Q); //initialize queue to be empty parent = root of T; visit parent; enqueue(Q, parent); //insert parent at the end of Queue while(!empty(Q)){ dequeue(Q, parent); //remove from front queue for(each child child of parent){ visit child; enqueue(Q, child) } } }

다음은 분기 한정에서의 가지 치기가 사용되지 않은 일반적인 BFS이다. 큐를 사용하는데 enqueue를 이용해 큐의 가장 뒤에 아이템을 위치시키고 dequeue를 이용해 큐의 가장 앞의 아이템을 제거한다. 코드를 살펴보면 fig 1.1같은 순서로 트리를 탐색한다.

With Branch and Bound Pruning

void breadth_first_branch_and_bound(state_space_tree T, number& best){ queue_of_node Q; node c, p; //child node and parent node initialize(Q); //Initialize Q to be empty p = root of T; //Visit root enqueue(Q, p); //add p at the end of queue Q best = value(p); //value() function to extract actual value at node P While(!empty(Q)){ dequeue(Q, p); for(each child c of p){ if(value(c) is better than best), then best = value(C); if(bound(c) is better than best), then enqueue(Q, c); //add to Q }// bound() function to extract theoretical value at node P } }//end

다음은 분기 한정법의 가지 치기를 이용해 위의 BFS를 수정한 것이다. 여기서는 노드의 한계값이 현재 최적 해의 값보다 좋은 경우에만 그 노드에서 계속 트리를 확장한다.

0/1 Knapsack problem with Branch and Bound

이제 0/1 배낭 문제에 분기 한정법을 적용해보자. 위에서 본 분기 한정법을 사용하는 가지치기 알고리즘은 재귀 함수를 사용하지 않으므로, 각 재귀 호출에 생성되는 새 변수가 없다. 따라서 0/1 배낭 문제에 대해 다음과 같은 노드 구조를 사용하여 각 노드에 필요한 정보를 저장해야 한다.

struct node{ int level; //the level of the node in the tree int profit; //the profit of the node int weight; // the weight of the node };

다음과 같은 노드 구조를 이용하면 다음과 같은 알고리즘을 사용할 수 있다. 우선 BFS를 이용한 경우이다.

//inputs: n, W, decreasing order sorted array w[] and p[] by p[i]/w[i] //output: Maimum profit void knapsack2(int n, const int p[], const int w[], int w, int& maxprofit){ queue_of_node Q; node c, p; initailize(Q); //Initialize Q to be empty p.level = 0; p.profit = 0, p.weight = 0; maxprofit = 0; // Initialize p to root enqueue(Q, p); while(!empty(Q)){ dequeue(Q, p); //remove p from Q c.level = p.level + 1; // Set c to a child of p //Set c to the child that includes the next item c.weight = p.weight + w[c.level] c.profit = p.profit + p[c.level] if(c.weight <= W && c.profit > maxprofit) maxprofit = c.profit; if(bound(c) > maxprofit) enqueue(Q, c) //Set c to the child that does not include the next item c.weight = p.weight; c.profit = p.profit; //no need to compare c.profit with maxprofit if(bound(c) > maxprofit) enqueue(Q, c); }//end of while loop }//end of knapsack2

다음과 같은 알고리즘에 저번 글(지금 말할 내용들과 비교해보자)에서의 0/1 배낭문제에서 사용된 것과 동일한 인스턴스를 넣어주면 fig 2.1과 같은 상태 공간 트리가 얻어진다.

fig 2.1

Compare with DFS Backtracking Pruning

역추적 방법의 가지치기와 비교해보자. 우선, 노드(3,1)과 노드(4,3)의 경계값은 0이다. 왜냐하면 노드의 weight가 W보다 크거나 같아 해당 노드가 non-promising일 때 경계값은 0으로 재설정되기 때문이다. 즉, \(u_i \geq W\)인 경우 노드 i는 유망하지 않으며 경계 값을 0으로 재설정한다. 이렇게 하면 경계 값이 지금까지 찾은 최적 해의 값보다 나을 수 없다.

둘째, 역추적의 경우 node(1,2)는 유망하지 않다. 그러나 이때의 최대 이익(maxprofit)의 값이 $40이고 해당 노드의 경계값은 $82이기 때문에 BFS의 세번째 반복에서는 해당 노드를 방문해서 유망하다고 처리한다.

셋째, 노드의 자식 방문 여부는 해당 노드를 방문하는 시점에 결정된다. 예를 들어 노드(2,3)을 방문할 때 현재 최대 이익이 $70인데 경계값이 $82이기 때문에 해당 노드의 자식을 방문하기로 결정한다.

그리고 DFS때와 달리 BFS에서는 노드의 자식을 방문할 때 최대 이익이 변경될 수 있다. 예를 들어 노드(2,3)의 자식을 방문할 때 최대 이익이 $70에서 $90으로 변경되었다.

이처럼 BFS를 이용한 접근 방식은 각 노드의 자식을 확인하기 때문에 시간을 낭비한다. 때문에 여기서 더 개선된 최상 우선 탐색을 사용하여 이러한 과정을 피할 수 있다.

Using Best-First search with Branch and Bound Pruning

위에서 봤듯이 일반적으로 역추적 알고리즘에 사용되는 DFS에 비해 지금 사용했던 BFS가 더 나은 점이 없다. 두 가지 모두 미리 결정된 순서로 맹목적으로 진행되기 때문이다. BFS는 트리의 두 개 이상의 확장되지 않은 유망 노드의 경계 값을 비교하여 향상될 수 있다.

따라서 주어진 노드의 모든 자식을 방문한 후, 우리는 확장되지 않은 노드 중에서 가장 좋은 경계 값을 가진 하나의 노드를 선택하여 확장할 수 있다. 결과적으로 위에서 언급한 최상 우선 검색을 사용하여 트리를 확장하는 것이다. 그 과정을 코드로 나타내면 다음과 같다.

void best_first_branch_and_bound(tree T, number& best){ priority_queue_of_node PQ; node c, p; initailize(PQ); //Initialize PQ to be empty p = root of T; best = value(p); insert(PQ, p); //function to add while(!empty(Q)){ remove(PQ, p); //function to remove node with best bound if(bound(p) is better than best){//Check if node is still promising for(each child c of p){ if(value(c) is better than best), then best = value(c); if(bound(c) is better than best), then insert(Q, c); } } } //end of while } //end of best-first

각 삽입/제거 시간에 노드에 대한 경계 값이 필요하고 우선 순위 큐에 있는 노드의 순서를 지정하기 위해 노드에 경계 값을 저장해야한다. 때문에 노드는 다음과 같은 구조를 가진다.

struct node{ int level; //the level of the node in the tree int profit; //the profit of the node int weight; // the weight of the node float bound; };

이제 이 노드와 최상 우선 검색을 이용하여 개선된 0/1 배낭 문제 해결 알고리즘을 살펴보자.

//inputs: n, W, decreasing order sorted array w[d] and p[] by p[i]/w[i] //output: Maximum profit void knapsack3(int n, int p[], int w[], int W, int& maxprofit){ priority_queue_of_node PQ; node c, p; initailize(PQ); //Initialize PQ to be empty p.level = 0; p.profit = 0; p.weight = 0; maxprofit = 0; //Initialize p to root p.bound = bound(p); insert(PQ, p); // this function adds node to PQ while(!empty(PQ)){ remove(PQ, p) //this function remove node with best bound if(p.bound > maxprofit){ //check if node is still promising //Set c to a child that includes next item c.level = p.level+1; c.weight = p.weight + w[c.level]; c.profit = p.profit + p[c.level]; if(c.weight <= W && c.profit > maxprofit) then maxprofit = c.profit; if(c.bound > maxprofit) then insert(PQ, c); //Set u to the child that does not include the next item c.weight = p.weight; c.profit = p.profit; c.bound = bound(u); if(bound(u) > maxprofit), then insert(PQ,c); } //end of outer if in previous slide } //end of while loop in previous slide } //end of knapsack3 in previous slide

fig 2.2

같은 인스턴스를 넣어줬을 때 최상 우선 탐색을 사용한 분기 한정 알고리즘(knapsack3)이 가장 적은 노드를 탐색했음을 확인할 수 있다. 한번 이전 일반적인 BFS를 사용햇던 knapsack2 알고리즘과 비교해보자.

최상 우선 탐색에서는 PQ에서 노드를 제거한 후 경계 값을 전역 변수 maxprofit보다 높은지 확인한다. 이 값이 false라면 방문했던 노드가 유망하지 않게 된다. 예를 들어 노드(1,2)는 처음 방문할 때는 유망하나 나중의 maxprofit이 90이 되면 더 이상 유망하지 않게 된다. 이렇듯 방문 후 유망에서 비 유망으로 변경되는 노드의 자식 방문을 피하는 것이다.

주어진 노드의 모든 자식을 방문한 후 우리는 확장되지 않은 모든 유망 노드를 살펴본 다음 가장 좋은 경계 값을 가진 노드를 확장할 수 있다. 이렇게 하면 미리 정해진 순서대로 맹목적으로 확장하는 것보다 더 빨리 최적 해에 도달할 수 있다. 따라서 최상 우선 탐색은 방문 후 유망하지 않게 될 노드의 자식을 방문하는 걸 방지한다.

자료 출처

Introduction to The Design and Analysis of Algorithms / Ananu Levitin

FOUNDATIONS OF ALGORITHMS / RICHARD E. NEAPORITAN

Panda의 IT 노트 / https://seungjuitmemo.tistory.com/109

Branch and bound

Algorithmic design paradigm for optimization

Branch and bound (BB, B&B, or BnB) is an algorithm design paradigm for discrete and combinatorial optimization problems, as well as mathematical optimization. A branch-and-bound algorithm consists of a systematic enumeration of candidate solutions by means of state space search: the set of candidate solutions is thought of as forming a rooted tree with the full set at the root. The algorithm explores branches of this tree, which represent subsets of the solution set. Before enumerating the candidate solutions of a branch, the branch is checked against upper and lower estimated bounds on the optimal solution, and is discarded if it cannot produce a better solution than the best one found so far by the algorithm.

The algorithm depends on efficient estimation of the lower and upper bounds of regions/branches of the search space. If no bounds are available, the algorithm degenerates to an exhaustive search.

The method was first proposed by Ailsa Land and Alison Doig whilst carrying out research at the London School of Economics sponsored by British Petroleum in 1960 for discrete programming,[1][2] and has become the most commonly used tool for solving NP-hard optimization problems.[3] The name “branch and bound” first occurred in the work of Little et al. on the traveling salesman problem.[4][5] Branch and bound methods do not go deep like Depth-first search; the first direction is lateral movement in the tree similar to Breadth-first search (BFS).

Overview [ edit ]

The goal of a branch-and-bound algorithm is to find a value x that maximizes or minimizes the value of a real-valued function f(x), called an objective function, among some set S of admissible, or candidate solutions. The set S is called the search space, or feasible region. The rest of this section assumes that minimization of f(x) is desired; this assumption comes without loss of generality, since one can find the maximum value of f(x) by finding the minimum of g(x) = −f(x). A B&B algorithm operates according to two principles:

It recursively splits the search space into smaller spaces, then minimizing f ( x ) on these smaller spaces; the splitting is called branching .

on these smaller spaces; the splitting is called . Branching alone would amount to brute-force enumeration of candidate solutions and testing them all. To improve on the performance of brute-force search, a B&B algorithm keeps track of bounds on the minimum that it is trying to find, and uses these bounds to “prune” the search space, eliminating candidate solutions that it can prove will not contain an optimal solution.

Turning these principles into a concrete algorithm for a specific optimization problem requires some kind of data structure that represents sets of candidate solutions. Such a representation is called an instance of the problem. Denote the set of candidate solutions of an instance I by S I . The instance representation has to come with three operations:

branch( I ) produces two or more instances that each represent a subset of S I . (Typically, the subsets are disjoint to prevent the algorithm from visiting the same candidate solution twice, but this is not required. However, an optimal solution among S I must be contained in at least one of the subsets. [6] )

produces two or more instances that each represent a subset of . (Typically, the subsets are disjoint to prevent the algorithm from visiting the same candidate solution twice, but this is not required. However, an optimal solution among must be contained in at least one of the subsets. ) bound( I ) computes a lower bound on the value of any candidate solution in the space represented by I , that is, bound( I ) ≤ f ( x ) for all x in S I .

computes a lower bound on the value of any candidate solution in the space represented by , that is, for all in . solution(I) determines whether I represents a single candidate solution. (Optionally, if it does not, the operation may choose to return some feasible solution from among S I .[6]) If solution(I) returns a solution then f(solution(I)) provides an upper bound for the optimal objective value over the whole space of feasible solutions.

Using these operations, a B&B algorithm performs a top-down recursive search through the tree of instances formed by the branch operation. Upon visiting an instance I, it checks whether bound(I) is equal or greater than the current upper bound; if so, I may be safely discarded from the search and the recursion stops. This pruning step is usually implemented by maintaining a global variable that records the minimum upper bound seen among all instances examined so far.

Generic version [ edit ]

The following is the skeleton of a generic branch and bound algorithm for minimizing an arbitrary objective function f.[3] To obtain an actual algorithm from this, one requires a bounding function bound, that computes lower bounds of f on nodes of the search tree, as well as a problem-specific branching rule. As such, the generic algorithm presented here is a higher-order function.

Using a heuristic, find a solution x h to the optimization problem. Store its value, B = f(x h ) . (If no heuristic is available, set B to infinity.) B will denote the best solution found so far, and will be used as an upper bound on candidate solutions. Initialize a queue to hold a partial solution with none of the variables of the problem assigned. Loop until the queue is empty: Take a node N off the queue. If N represents a single candidate solution x and f(x) < B , then x is the best solution so far. Record it and set B ← f(x) . Else, branch on N to produce new nodes N i . For each of these: If bound(N i ) > B , do nothing; since the lower bound on this node is greater than the upper bound of the problem, it will never lead to the optimal solution, and can be discarded. Else, store N i on the queue.

Several different queue data structures can be used. This FIFO queue-based implementation yields a breadth-first search. A stack (LIFO queue) will yield a depth-first algorithm. A best-first branch and bound algorithm can be obtained by using a priority queue that sorts nodes on their lower bound.[3] Examples of best-first search algorithms with this premise are Dijkstra’s algorithm and its descendant A* search. The depth-first variant is recommended when no good heuristic is available for producing an initial solution, because it quickly produces full solutions, and therefore upper bounds.[7]

Pseudocode [ edit ]

A C++-like pseudocode implementation of the above is:

// C++-like implementation of branch and bound, // assuming the objective function f is to be minimized CombinatorialSolution branch_and_bound_solve ( CombinatorialProblem problem , ObjectiveFunction objective_function /*f*/ , BoundingFunction lower_bound_function /*bound*/ ) { // Step 1 above double problem_upper_bound = std :: numeric_limits < double >:: infinity ; // = B CombinatorialSolution heuristic_solution = heuristic_solve ( problem ); // x_h problem_upper_bound = objective_function ( heuristic_solution ); // B = f(x_h) CombinatorialSolution current_optimum = heuristic_solution ; // Step 2 above queue < CandidateSolutionTree > candidate_queue ; // problem-specific queue initialization candidate_queue = populate_candidates ( problem ); while ( ! candidate_queue . empty ()) { // Step 3 above // Step 3.1 CandidateSolutionTree node = candidate_queue . pop (); // “node” represents N above if ( node . represents_single_candidate ()) { // Step 3.2 if ( objective_function ( node . candidate ()) < problem_upper_bound ) { current_optimum = node . candidate (); problem_upper_bound = objective_function ( current_optimum ); } // else, node is a single candidate which is not optimum } else { // Step 3.3: node represents a branch of candidate solutions // "child_branch" represents N_i above for ( auto && child_branch : node . candidate_nodes ) { if ( lower_bound_function ( child_branch ) <= problem_upper_bound ) { candidate_queue . enqueue ( child_branch ); // Step 3.3.2 } // otherwise, bound(N_i) > B so we prune the branch; step 3.3.1 } } } return current_optimum ; }

In the above pseudocode, the functions heuristic_solve and populate_candidates called as subroutines must be provided as applicable to the problem. The functions f ( objective_function ) and bound ( lower_bound_function ) are treated as function objects as written, and could correspond to lambda expressions, function pointers and other types of callable objects in the C++ programming language.

Improvements [ edit ]

When x {\displaystyle \mathbf {x} } is a vector of R n {\displaystyle \mathbb {R} ^{n}} , branch and bound algorithms can be combined with interval analysis[8] and contractor techniques in order to provide guaranteed enclosures of the global minimum.[9][10]

Applications [ edit ]

This approach is used for a number of NP-hard problems:

Branch-and-bound may also be a base of various heuristics. For example, one may wish to stop branching when the gap between the upper and lower bounds becomes smaller than a certain threshold. This is used when the solution is “good enough for practical purposes” and can greatly reduce the computations required. This type of solution is particularly applicable when the cost function used is noisy or is the result of statistical estimates and so is not known precisely but rather only known to lie within a range of values with a specific probability.[citation needed]

Relation to other algorithms [ edit ]

Nau et al. present a generalization of branch and bound that also subsumes the A*, B* and alpha-beta search algorithms.[16]

Optimization Example [ edit ]

Branch and bound can be used to solve this problem

Maximize Z = 5 x 1 + 6 x 2 {\displaystyle Z=5x_{1}+6x_{2}} with these constraints

x 1 + x 2 ≤ 50 {\displaystyle x_{1}+x_{2}\leq 50}

4 x 1 + 7 x 2 ≤ 280 {\displaystyle 4x_{1}+7x_{2}\leq 280}

x 1 x 2 ≥ 0 {\displaystyle x_{1}x_{2}\geq 0}

x 1 {\displaystyle x_{1}} and x 2 {\displaystyle x_{2}} are integers.

The first step is to relax the integer constraint. We have two extreme points for the first equation that form a line: [ x 1 x 2 ] = [ 50 0 ] {\displaystyle {\begin{bmatrix}x_{1}\\x_{2}\end{bmatrix}}={\begin{bmatrix}50\\0\end{bmatrix}}} and [ 0 50 ] {\displaystyle {\begin{bmatrix}0\\50\end{bmatrix}}} . We can form the second line with the vector points [ 0 40 ] {\displaystyle {\begin{bmatrix}0\\40\end{bmatrix}}} and [ 70 0 ] {\displaystyle {\begin{bmatrix}70\\0\end{bmatrix}}} .

the two lines.

The third point is [ 0 0 ] {\displaystyle {\begin{bmatrix}0\\0\end{bmatrix}}} . This is a convex hull region so the solution lies on one of the vertices of the region. We can find the intersection using row reduction, which is [ 70 / 3 80 / 3 ] {\displaystyle {\begin{bmatrix}70/3\\80/3\end{bmatrix}}} , or [ 23.333 26.667 ] {\displaystyle {\begin{bmatrix}23.333\\26.667\end{bmatrix}}} with a value of 276.667. We test the other endpoints by sweeping the line over the region and find this is the maximum over the reals.

We choose the variable with the maximum fractional part, in this case x 2 {\displaystyle x_{2}} becomes the parameter for the branch and bound method. We branch to x 2 ≤ 26 {\displaystyle x_{2}\leq 26} and obtain 276 @ ⟨ 24 , 26 ⟩ {\displaystyle \langle 24,26\rangle } . We have reached an integer solution so we move to the other branch x 2 ≥ 27 {\displaystyle x_{2}\geq 27} . We obtain 275.75 @ ⟨ 22.75 , 27 ⟩ {\displaystyle \langle 22.75,27\rangle } . We have a decimal so we branch x 1 {\displaystyle x_{1}} to x 1 ≤ 22 {\displaystyle x_{1}\leq 22} and we find 274.571 @ ⟨ 22 , 27.4286 ⟩ {\displaystyle \langle 22,27.4286\rangle } . We try the other branch x 1 ≥ 23 {\displaystyle x_{1}\geq 23} and there are no feasible solutions. Therefore, the maximum is 276 with x 1 ⟼ 24 {\displaystyle x_{1}\longmapsto 24} and x 2 ⟼ 26 {\displaystyle x_{2}\longmapsto 26} .

See also [ edit ]

References [ edit ]

[알고리즘, C++] 분기한정법 (Branch and Bound)

제달이

이번 포스트에서는 Branch and Bound(분기한정법) 기법에 대해서 다루도록 하겠습니다.

분기한정법은 이전 포스트에서 다루었던 backtrackingr과 매우 유사하지만, backtracking이 존재할 수 있는 모든 알고리즘의 해를 찾는 방법이라면, branch and Bound기법은 마찬가지로 상태공간 트리를 구축하여 문제를 해결하지만, 궁극적으로 모든 가능한 해를 다 고려하여 최적의 해를 찾는다는 점이 큰 차이점이라 할 수 있겠습니다.

Branch and Bound 기법은 크게 두가지 단계로 나뉩니다.

1. 어떤 노드에 도달하였을 때, 해당 노드가 promising 한지 아닌지를 판단하기 위해 노드의 바운드를 계산하는 일

2. 헌재 노드의 바운드가 이제까지 찾은 best solution보다 작다면, 그 노드는 non-Promising 하다 판단, pruning.

이러한 방법은 이전 벡트래킹 포스트에서 다루었던 0-1 Knapsack Problem의 문제와 매우 유사한 형태 입니다.

다만 이전 포스트에서 다루었던것은 이러한 분기한정법을 깊이우선 탐색을 통해 Backtracking으로 해결하였다면, 이번 포스트에서는 Breadth-First-Search , Best-First Search 탐색 방법으로 분기한정법을 다루도록 하겠습니다.

너비우선검색(Breadth-First Search)

이 방식은 분기한정법을 너비우선 탐색으로 진행한 방법입니다.

말 그대로 루트노드를 먼저 검색하고, 다음level에 있는 모든 노드를 왼쪽에서 오른쪽순으로 검색한다음, 마찬가지로 다음 레벨에 대해서도 이와 같은 순서로 탐색을 진행하며 최적의 해를 찾아나가는 방식입니다. 그림을 통해 진행과정을 하나하나 알아보도록 하겠습니다.

0번째 level 탐색

bound를 계산하는 방식은 이전 backtracking 포스트의 0-1 knapsack문제에서 다루었기 때문에 자세히 다루지 않겠습니다. 첫번째 루트노드 탐색에선, 현재가지 찾은 best solution이 0, bound값을 계산하였을 때 115의 값을 가집니다. 따라서 bests olution보다 bound값이 크므로 promising하다 판단하여 해당 노드를 queue에 삽입하고, 그 자식노드들에 대한 탐색을 진행합니다. 이와같은 연산을 Queue가 empty가 될 때까지 탐색을 진행합니다.

1번째 level 탐색

이전 queue에 저장된 Node0을 Pop하고, 그 자식노드들에 대해서 탐색을 진행합니다. Node1, Node2는 각각 해당 레벨에 해당하는 item(1번 item)을 넣기로 한 경우와 넣지 않기로 한 경우를 표현한 것입니다. 먼저 물건을 넣기로 한 경우인 Node1의 profit은 40으로 현재까지 중에 가장 best solution입니다. 또한 각 노드의 bound값을 계산하면 각각 115, 82이고 이는 현재 best_solution인 40보다 큰 bound값이므로 모두 queue에 삽입합니다.

2번째 레벨 탐색

마찬가지로 이전 queue에 저장된 {Node0, Node1}을 모두 pop하여 그 자식노드들에 대해서 profit 값과 bound 값을 계산합니다. 계산결과 현재까지의 최적의 해는 Node3의 값인 70이며, Node6의 bound값은 60으로 현재까지의 최적의 값보다 bound값이 작으니 non-promising하다는 것을 알 수 있습니다. 따라서 Node6의 자식노드에 대해서는 더이상 탐색할 필요가 없으니 이를 제외한 나머지 노드들 만을 Queue에 삽입합니다.

3번째 레벨 탐색 4번쨰 레벨 탐색

마지막 레벨탐색에서 Node15는 item4를 넣을경우 무게가 초과 되기때문에 non-promising하고 그 외

Queue가 모두 빌 때까지 마찬가지로 연산을 진행하면 위와같이 best_solution이 90임을 알 수 있습니다.

이제 상태공간트리와 알고리즘의 흐름을 파악하였으니 이를 토대로 Breadth-First-Search 방식으로 수도코드를 작성해 보도록 하겠습니다. 재귀(recursive)로 구현하기에는 상당히 복잡하므로 queue를 이용하도록 하겠습니다

#include //Breath-First Search with Branch and Bound #include #include class node{ int level; int profit; int weight; }; int knapsack2(int n, int[] p, int[] w, int W){ queue Q; node u,v ; int maxProfit; clear(Q); //큐를 빈 대기열로 초기화 v.level = 0 ; v.profit = 0; v. weigth=0; Q.push(v); while(!empty(Q)){ v = Q.pop(); u.level = v.level + 1; //take care of the left child: 현재아이템을 넣기로 한경우 u.weight = v.weight + w[u.level]; u.profit = v.profit + p[u.level]; if (u.weight <= W && u.profit > maxProfit) maxProfit = u.profit; if (bound(u) > maxProfit) Q.push(u); //take care of the right child ;현재 아이템을 넣지 않는경우 u.weight = v.weight; u.profit = v.profit; if(bound(u) > maxProfit) Q.push(u); } return maxProfit; } float bound(node u){ int j, k; int totWeight; float result ; if (u.weight>=W) return 0; else{ result = u.profit; j = u.level + 1; while(j<=n && totWeight + w[j] <= W){ totWeight = u.weight + w[j]; result += p[j]; j++; } k = j ; if(k <= n){ result += (W-totWeight)*(p[k]/w[k]); } return result; } } Best-First-Search(최고우선탐색) 이 방법은 최정의 해에 더 빨리 도달하기 위한 전략입니다. bound 값이 큰것이 더 좋은 결과값을 낼 확률이 높다는 것에 기반하여 너비우선 탐색과같이 순차적으로 검색하는 것이 아닌, 우선순위 큐를 사용하여 바운드 값이 큰 순서대로 탬색을 진행합니다. 그림을 통해서 동작방식을 살펴보도록 하겠습니다. 루트노드를 삽입하는 부분은 위에 그림과 겹치므로, level2 탐색부터 보겠습니다. level2 탐색 우선 순위 큐에 의해서 큐에는 bound값이 큰것이 먼저 나올 수 있도록 정렬되어 있습니다. 이제까지의 bestsolution은 70이며, 다음에 탐색할 노드는 바운드 값이 가장 큰 Node3의 자식노드입니다. level3 탐색 탐색결과 Node3의 자식노드인 Node5는 무게초과로 Non-promising, Node6는 bound값이 80으로 아직까진 현재 best_solution인 70보다는 값이 크므로 promising으로 판단하고 우선순위 큐에 넣어줍니다. 다음에 탐색할 노드는 bound값이 가장 큰 Node4의 자식노드입니다. Node4의 자식노드인 Node7을 검색한 결과, 현재까지의 bestSolution은 90으로 갱신됩니다. Node7의 bound는 90보다 크므로 큐에 삽입하고, Node8의 바운드값은 90보다 작으므로 non-Promising 하다 판단하여 큐에 넣지 않습니다. 탐색 종료 이런식으로 순차적으로 탐색을 진행하면, 최종적으로 큐에 남은 Node2, Node6는 best_solution보다 작은 bound값을 가지고 있으므로 non-Promising이라 판단, 따라서 더이상 그 자식노드들을 살펴보지 않고 그대로 탐색을 종료하게 됩니다. 딱 봐도 탐색 과정이 이전 너비우선 탐색기반 bound & branch법 보다 상당히 줄어든 것을 확인할 수 있습니다. 이와 같이 최고우선탐색 기법을 사용하면, 너비우선 탐색에 비해서 일반적으로 검색 성능이 좋아집니다. 이제 동작방식을 확인하였으니 마찬가지로 수도코드를 작성해 보겠습니다. (sudo 코드가 아닌 실전 코드를 확인하고 싶으시다면 (여길 참조하시면 좋을 것 같습니다 -http://yimoyimo.tk/Dynamic,Branch-and-bound-Knapsack/#branch-and-bound--01-%EB%B0%B0%EB%82%AD%EC%B1%84%EC%9A%B0%EA%B8%B0-best--first-search) #include //Best-First Search with Branch and Bound #include #include #include #include using namespace std; struct node{ int weight; int profit; int level; float bound; }; struct cmp{ bool operator() (node x, node y) { return y.bound > x.bound; } }; public int knapsack3(int n, int[] p, int[] w, int W) { priority_queue , cmp> PQ; node u, v; int maxProfit=0 ; clear(Q); v.level = 0; v.profit = 0; v.weight = 0 ; v.bound = bound(v) PQ.push(v); while(!PQ.empty()){ u.level = v.level+1; //take care of left child : item넣었을때 u.weight = v.weight + w[u.level]; u.profit = v.profit + p[u.level]; if(u.weight<=W && u.profit> maxProfit) maxProfit = u.profit; u.bound = bound(u); if(u.bound > maxProfit) PQ.push(u); //take careof right child: item넣지 않았을 떄 u.weight = v.weight; u.profit = v.profit; u.bound = bound(u); if(u.bound > maxProfit) PQ.push(u); } } #bound 함수부분, public float bound(node u){ int j,k ; int totWeight; float result ; if(u.weight >= W) return 0 else{ result = u.profit; j = u.level +1 ; totWeight = u.weight; while(j<=n && totWeight + w[j]<=W){ totWeight += w[j]; result += p[j]; j++; } k =j ; if(k<=n){ result += (W-totWeight)*(p[k]/w[k]); return result; } } } The Traveling SalesPerson Problem (with BFS_B&B) 이제 마지막으로 TSP문제를 Best-First-Search 기반 Branch Bound방식으로 해결해 보겠습니다. T.S.P문제는 다음과 같습니다. 외판원이 어느 한도시에서 출발하여, 다른 도시들을 각각 한번씩만 방문하고 자기 도시로 돌아오는 가장 짧은 일주경로를 걸정하는 문제 자기 도시로 돌아오는 가장 짧은 일주경로를 걸정하는 문제 일반적으로 이 문제는 음이 아닌 가중치가 있는 방향성 그래프를 입력값으로 받는다 여러 개의 일주여행경로 중에서 길이가 최소가 되는 경로가 최적의 경로가 된다. 이 문제를 분기한정법으로 해결하기 위해서는 먼저 1. 상태공간트리의 노드가 어떤 식으로 표현이 되어야 하는가 정의 2. 어떤 노드의 서브트리를 탐색했을 때 얻을 수 있는 최적의 bound값을 계산하는 방법 이 두가지에 대해서 생각해야 합니다. 먼저 상태공간트리가 어떤 식으로 정의 될 지 생각해보면 다음과 같습니다. TSP space-state-tree 아마도 뻗어나가는 경로를 표현할 수 있어야 하므로, level 1은 출발지, level2는 출발지에서 다음 노드 까지의 경로.. 이런식으로 노드를 표현할 수 있을 겁니다. 이런 식으로 뻗어나가서 단말노드(leaf node)에 도달하게 되면 비로소 완전한 일주경로를 가지게 되는 것이죠 따라서 우리는 단말노드에 있는 일주 경로를 모두 검사하여 그중에서 가장 길이가 짧은 일주경로를 찾으면 됩니다. 위 그림에서 만약 경로에 저장되어 있는 노드가 n-1개가 되면 더 이상 뻗어 나갈 필요가 없는데, 그 이유는 어차피 n번째 노드를 방문한다면 자연스럽게 처음의 노드로 회귀해야 때문입니다. 이제 상태공간 트리를 생각해 보았으니 bound값을 계산할 수 있는 방법에 대해서 생각해 봅시다. 상태 공간트리에서 k번째 레벨에 있는 노드들은 k+1번쨰 정점을 방문한 상태를 의미합니다. 먼저 위 그림을 하나하나 해석해보겠습니다. [1, ..., k]의 여행경로를 가진 노드의 한계치는 다음과 같이 계산할 수 있습니다. - A = V - ([1, .., K] 경로에 속한 모든 노드의 집합) -> 그림상으로 주황색 점선 안

– bound = [1,…,k] 경로의 총 가중치 합 _

+ v_k에서 A에 속한 정점으로 가는 것중 최소의 cost 값

+ (시그마)vi를 제외한 정점들 중 (빨간색 박스안 정점들을 제외한 정점들 중) cost가 최소인 것들의 합

(말이 상당히 애매하지만, 밑에서 그림과 설명하도록하겠습니다)

글로 정의하였지만 아직 잘 와닿지 않을 수 있습니다.

이 과정을 아래 예제를 통해서 확인해 보도록 하겠습니다.

루트노드의 bound값 계산

루트노드의 하한 구하는 방법:

어떤 일주경로라도, 각 정점을 최소한 한번은 떠나야 하므로, 각 정점을 떠나는 이음선의 cost 최소값의 합이 bound값이 됩니다. 즉 각각의 경우중 최소의 값만 다 더하면, bound 값은 4 + 7 + 4 + 2+ 4 = 21 입니다.

단, 여기서 주의할 점은 bound값은 “이러한 가중치를 가지는 경로가 존재 한다는 것”이 아니라 이보다 더 cost가 작은 경로가 존재할 수 없다는 의미를 가지는 것입니다. 그래서 하한(lower bound)라는 말을 사용하는 겁니다.

이제 각 레벨별로 어떻게 알고리즘이 동작하는지 살펴보도록 하겠습니다.

먼저 bound계산 알고리즘에 대한 좀더 자세한 이해를 위해 [1,2]를 선택했을 때의 bound값 계산과정을 살펴보도록 하겠습니다.

노드 [1,2]를 선택한 경우 bound값 계산

이미 v2를 선택하였음으로, v1->v2의 비용은 이음선의 가중치인 14가 됩니다. 나머지는 앞서 설명한대로 구해보면

– v2에서 A에 속한 값 중 가중치 중에 (7, 8, 7) 최소인값 : 7

– v3에서 전체에서 v2를 제외한(4, 7, 16) 가중치 중 최소인 값 : 4

– v4에서 전체에서 v2를 제외한(11, 9, 2) 가중치 중 최소인 값 : 2

– v5에서 전체에서 v2를 제외한(18, 17, 4) 가중치 중 최소인 값 : 4

– [1,2]노드의 bound값 : 31

따라서 이런 방식으로 나머지 노드들에 대해서 계산을 해보면 다음과 같은 상태가 됩니다.

level1 에서 각각의 bound 값 계산

마지막으로 한번더 노드 [1,2,3]을 선택한 경우의 bound값을 구해보도록 하겠습니다.

노드 [1,2,3]의 bound값 계산

이미 v2와 v3를 선택하였으므로 v1->v2->v3의 cost는 14 + 7 = 21이 됩니다.

나머지는 앞서 설명한 방법과 동일하게 계산해 보면

– v1 -> 14

– v2 -> 7

– v3 에서 A에 속한 값중 (7, 16)가중치가 최소인 값 : 7

– v4 에서 전체에서 v2, v3를 제외한 값중 (11,2) 가중치가 최소인 값 : 2

– v5 에서 전체에서 v2, v3를 제외한 값중 (18, 4) 가중치가 최소인 값: 4 ‘

– [1,2,3]노드의 bound값 : 14 + 7 + 7 + 2 + 4 = 34

따라서 이런 방식으로 나머지 노드들에 대해서 계산을 해보면 레벨3의 상태공간트리는 다음과 같은 상태가 됩니다. 위에 예시를 [1,2,3]을 선택한 경우를 들었지만 우선순위 큐에 의해 [1,3]을 선택한 경로의 자식노드부터 탐색을 시작하는군요;

level3 state-space-tree

이와 같이 계속 계산해 나가다 보면 결국 최종적으로 단말노드(level n-1)까지 계산할 수 있습니다.

드디어 첫번째 best_solution값이 나왔습니다. 이제 여태 해왔던 것처럼 Queue를 하나씩 꺼내어 bound값을 bestSolution과 비교하여 bound값이 더 작다면(최적이라면) 그대로 탐색하지않고 종료를, 아니라면 해당노드의 밑으로 탐색을 진행하고, 더 좋은 best solution값이 나오면 값을 갱신하는 방법으로 진행해 나가면 최종해를 구할 수 있습니다

결국 큐에 있는 모든 노드들의 bound값이 최적의 해인 30보다 모두 큰 값이므로 최적의 경로는 30의 가중치를 가진 v1 -> v4 -> v5 -> v2 -> v1 경로인것을 구할 수 있습니다. 이제 동작방식을 이해하였으니 sudo코드를 작성해 보도록 하겠습니다.

#include #include #include #include #include using namespace std; struct node{ int level; int path[] ; int bound; }; struct cmp{ bool operator() (node x, node y) { return y.bound < x.bound; } }; public int TSP(int n, int[] W, node optTour) { priority_queue , cmp> PQ; node u, v; int best_solution = 10000; //무한대와 같은 큰값 clear(PQ); v.level = 0; v.path = [1]; while(!empty(PQ)){ v = PQ.pop(); //if v is promising if(v.bound < best_solution) { //take_care_of_children u.level = v.level + 1 ; for (all i such that 2<=i<= n && i not in v.path){ u.ordered_path = v.ordered_path; put i at the end of u.path; if(u.level == n-2){ put index of only vertex not in u.path at the end of u.path; put 1 at the end of u.path ; if(bound(u) < best_solution){ best_Solution = bound(u); optTour = u.path; } } else{ u.bound = bound(u); if(u.bound < best_solution) PQ.push(u) } } } } } 자세한 알고리즘은 생략하였지만 결론은 branch and bound 기법으로 문제를 접근하여도 여전히 시간복잡도가 지수적이거나 그보다 못하다는 점입니다. 즉 n=40정도가 된다면 문제를 풀 수 없는 것과 다름이 없다고 할 수 있습니다. 이 문제는 아직까지 최적의 알고리즘이 밝혀지지 않은 문제중 하나인데요, 오늘은 이렇게 해서 B&B방식을 3개의 예제를 통해서 알아보았습니다. 이상으로 포스트 마치겠습니다.

분기한정법 : Branch and bound

Branch and bound

분기한정법은 여러가지의 최적화 문제, 특히 이산 (discrete) 과 조합최적화 (combinatorial optimization) 에서 최적 해를 찾기위한 일반적인 방법이다. 그것은 암묵적인 열거 방법 (implicit enumeration method) 부류에 속한다. 분기한정법은 1960 년에 linear programming 을 위해 A. H. Land 와 A. G. Doig 가 An Automatic Method for Solving Discrete Programming Problems 에 처음 소개하였다.

분기한정법은 인수 x (가능영역 (feasible region) 이라고 불리움) 에 대해 함수 f(x) 의 최소값을 찾는것 이라고 표현할수 있다. f 와 x 는 모두 임의의 값이다. 분기한정 과정은 두가지 tool 을 필요로 한다.

첫째는 여러개의 작은 feasible subregion (이상적으로 subregion 으로 나뉘는) 들이 feasible region 을 구성하는 방법이다. 이것은 분기 (branching) 라고 불리는데, 그 과정이 subregion 각각에 대해 재귀 반복 과정을 거치고, 만들어진 subregion 들이 자연스럽게 search tree 또는 branch-and-bound-tree 라고 불리는 tree 구조를 형성하기 때문이다. 그 노드들 각자가 subregion 이 되는 것이다.

두번째는 bounding 으로서, feasible subregion 내에서 최적해를 찾기위한 upper and low bound 를 빠르게 찾는 방법이다.

분기한정법의 핵심은, (작업을 최소화 하기 위해) 탐색트리에서 subregion A 의 lower bound 가 다른 이미 검사된 subregion B 의 upper bound 보다 크다면 A 를 탐색에서 폐기처분하는 간단한 방법이다. 이러한 단계를 절단 (pruning) 이라고 부른다. 절단은 검사된 모든 subregion 중에서 볼수있는 minimum upper bound 를 기록하는 전역변수 m 을 유지함으로써 구현된다. 즉 lower bound 가 m 보다 큰 어떠한 노드도 폐기처분 될수있다.

노드의 upper bound 가 lower bound 와 match 되고 그 값이 동등한 subregion 내에서 함수의 최소값 일수가 있다. 또 어떤 경우에는 그러한 최소값을 직접 찾는 방법도 있다. 이러한 두가지 경우를 그 노드가 해결 (solved) 된것이라고 말한다. 이러한 노드는 알고리즘이 진행되어 가면서 언제든 pruned 될수 있다는 것을 주목하라.

이상적으로는 분기한정법은 탐색트리의 모든 노드가 절단 (pruned) 되거나 해결 (solved) 되면 끝난다. 그러한 관점에서 모든 non-pruned subregions 은 함수의 전역 최소값과 같은 upper and lower bounds 를 가질것이다. 실제로 사용될때는 분기한정법은 주어진 시간이 지나면 종료된다. 그러한 관점에서 모든 non-pruned sections 중에서 minimum lower bound and the minimum upper bound 는 전역최소값 (global minimum) 을 포함하는 값의 범위로서 정의한다.

분기한정법의 효율성은 사용되는 branching and bounding algorithm 의 효과에 전적으로 의존한다. 즉 잘못 선택하면 subregions 이 매우 작아질 때 까지 어떤 pruning 도 없이 반복된 branching 만 할수도 있다. 그러한 경우에 분기한정법은 비현실적으로 큰 소모적인 열거 (exhaustive enumeration of the domain) 로 환원될 것이다. 모든 문제 해결을 위한 범용 bounding algorithm 은 존재하지 않으며 누군가 발견할 것이라고 희망할수도 없다. 그러므로 일반적인 패러다임은 각각의 응용을 위해 따로 구현될 필요가 있으며 branching and bounding algorithm 은 그 경우에 특별히 설계된다. 분기한정법은 bounding method 와 탐색트리 노드를 생성하고 검사하는 방법에 따라 분류될수 있다.

분기한정법은 자연히 병렬분산 (parallel and distributed) 의 구현, 예를들면 순회판매원 문제 (Travelling Salesman Problem) 같은 문제에 사용된다. 즉 많은 비결정 난해 (NP-hard) 문제를 위해 사용된다.

분기한정법은 다양한 휴리스틱의 기초가 될수있다 예를들면 upper and lower bounds 사이의 차이가 어떤 threshold 보다 작게되면 branching 을 멈추기를 원할수 있다. 이것은 그 해법이 “실제적인 목적을 위해서는 충분히 좋은 (good enough for practical purposes)” 때 사용되며 필요한 계산량을 크게 줄일수 있다. 이러한 종류의 해법은 특히 사용되는 cost function 이 noisy 하거나 통계적 추정 (statistical estimates) 의 결과 일때, 즉 정확하게 알수는 없지만 어떤 확률값의 범위내에 있다는 것을 알때에 사용된다. 알파베타 가지치기 (Alpha-Beta Pruning) 은 최소최대 (Mini-max) 문제를 위해 사용하는 것으로 분기한정의 특별한 버전이라고 할수있다. ………… (Wikipedia : Branch and bound)

term :

분기한정법 (Branch and bound) 최적화 (Optimization) 수학 (Mathematics) 경영과학 (Operation Research) 비결정 완전 (NP-complete) 비결정 난해 (NP-hard) 순회판매원 문제 (Travelling Salesman Problem) 배낭문제 (Knapsack problem) 휴리스틱 (Heuristic) 유전알고리즘 (Genetic Algorithm) 알파베타 가지치기 (Alpha-Beta Pruning) 최소최대 (Mini-max) 조합최적화 (Combinatorial Optimization)

site :

branch and bound : 전북대 박순철 교수님 동영상 ( ★★★ )

알고리즘 강의 : 분기한정법 : 도경구

paper :

Unification of Kohonen Neural Network with the Branch-and-Bound Algorithm on Pattern Clustering : 박창목, 왕지남, 대한산업공학회, 1997

키워드에 대한 정보 branch and bound 알고리즘

다음은 Bing에서 branch and bound 알고리즘 주제에 대한 검색 결과입니다. 필요한 경우 더 읽을 수 있습니다.

이 기사는 인터넷의 다양한 출처에서 편집되었습니다. 이 기사가 유용했기를 바랍니다. 이 기사가 유용하다고 생각되면 공유하십시오. 매우 감사합니다!

사람들이 주제에 대해 자주 검색하는 키워드 [알고리즘실습] 제11강 분기한정 1. 배낭(Knapsack) 문제 분기한정으로 풀기

  • 알고리즘
  • 분기한정
  • 배낭문제
  • Knapsack Problem
  • Branch and Bound
[알고리즘실습] #제11강 #분기한정 #1. #배낭(Knapsack) #문제 #분기한정으로 #풀기


YouTube에서 branch and bound 알고리즘 주제의 다른 동영상 보기

주제에 대한 기사를 시청해 주셔서 감사합니다 [알고리즘실습] 제11강 분기한정 1. 배낭(Knapsack) 문제 분기한정으로 풀기 | branch and bound 알고리즘, 이 기사가 유용하다고 생각되면 공유하십시오, 매우 감사합니다.

See also  Taules De Centre De Disseny | Bacyion Table De Salle À Manger Avec 4 Chaises Ensembles De Meubles, Belle, Minimaliste Et Robuste 180 개의 정답

Leave a Comment