Search

Branch and Bound

1. 백트래킹과 한계점

백트래킹(backtracking)은 상태공간트리를 DFS로 탐색, 유망(promising)하지 않은 노드는 pruning하여 효율화.
그러나 bound(한계치) 계산은 오직 탐색 진행 여부만 판단하며, bound의 크고 작음 자체는 탐색 순서에 활용하지 않음.
깊이우선탐색(DFS)이므로, 해답까지의 경로가 길거나 pruning이 늦게 이루어지는 경우 불필요한 탐색이 많아질 수 있음.

2. 분기한정법(Branch-and-Bound) 개요

분기(branching): 문제의 해 공간을 분할하여 상태공간트리 구축
한정(bounding): 각 노드에서 해답 가능성의 상한/하한(bound)을 계산
전략: bound가 기존 최적해보다 나쁘면 해당 서브트리 전체를 prune
핵심 차이: 유망한 노드 중에서 bound가 가장 좋은 것(최고 bound)을 가진 노드를 먼저 확장(탐색 순서에 bound를 적극적으로 활용)

3. 탐색 전략

깊이우선검색(DFS): 백트래킹에서 사용
너비우선검색(BFS): 모든 레벨을 차례로 확장
최고우선검색(Best-First Search, BestFS):
확장되지 않은 노드 중에서 bound가 가장 좋은 노드부터 확장
일반적으로 우선순위 큐(priority queue, heap)를 사용하여 구현

4. 최적화 문제에서의 분기한정법

4.1 최소화 문제

각 노드에서 bound는 최소값에 대한 하한을 의미
이미 찾은 해답보다 bound가 크면(더 나쁜 경로), 해당 노드는 prune

4.2 최대화 문제

bound는 최대값에 대한 상한을 의미
이미 찾은 해답보다 bound가 작으면 해당 노드 prune

5. 0–1 배낭문제에서의 분기한정법

5.1 알고리즘 구조 (Best-First Search 예시)

우선순위 큐(PQ)에 root node 삽입
PQ에서 bound가 가장 큰 노드를 remove
그 노드의 자식(아이템 포함/비포함) 노드들을 생성
각 자식의 bound 계산, bound가 현재 maxprofit보다 크면 PQ에 삽입
profit(이익) 값이 maxprofit보다 크면 maxprofit 갱신
void knapsack3(int n, const int p[], const int w[], int W, int& maxprofit){ priority_queue_of_node PQ; node u, v; initialize(PQ); v.level = 0; v.profit = 0; v.weight = 0; maxprofit = 0; v.bound = bound(v); insert(PQ, v); while(!empty(PQ)){ remove(PQ, v); // bound가 가장 큰 노드 if(v.bound > maxprofit){ // 포함하는 경우 u.level = v.level + 1; 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) insert(PQ, u); // 포함하지 않는 경우 u.weight = v.weight; u.profit = v.profit; u.bound = bound(u); if(u.bound > maxprofit) insert(PQ, u); } } }
C++
복사
bound 계산은 남은 용량에 대해 다음 아이템을 쪼개 넣을 수 있다고 가정해(무게당 가치로) 최대로 얻을 수 있는 이익의 상한선을 산출.

6. 분기한정법의 효율

백트래킹(DFS) 대비, 분기한정 BestFS가 더 적은 노드로 최적해에 도달 가능(탐색 노드 수 감소)
분기한정 BFS는 pruning을 하지만, 항상 최적 bound부터 확장하지는 않으므로 탐색 노드 수가 더 많을 수 있음
BestFS는 PQ에 bound가 높은 노드가 많아질수록(해공간이 크고 가지치기가 늦게 일어날수록) 실제 성능 차이가 커짐

7. 외판원 문제(Traveling Salesman Problem, TSP)와 분기한정법

n개 도시를 순회하여 모든 도시를 정확히 한 번씩 방문하고 출발점으로 돌아오는 최단 경로를 찾는 문제.
모든 경로의 개수는 (n–1)!로, 완전탐색은 매우 비효율적임.
분기한정법을 통해 유망하지 않은 부분공간을 가지치기(pruning).
상태공간트리에서 노드는 현재까지 방문한 도시 경로와, 남은 도시들에서 갈 수 있는 최저비용의 합(bound)을 저장.

Bound 계산 예시

이미 방문한 경로의 비용 + 남은 각 도시에서 방문 가능한 가장 짧은 간선의 비용(minimum) 합산
bound < 현재까지의 최단경로이면 PQ에 삽입(확장), 아니면 prune

8. Best-First Search로 푸는 TSP 알고리즘 구조

1.
PQ에 root node(경로 [1], bound=초기 하한) 삽입
2.
PQ에서 bound가 가장 작은 노드를 remove
3.
그 노드의 경로를 한 단계 더 확장(아직 방문하지 않은 도시를 추가)
4.
bound 계산, bound < minlength이면 PQ에 삽입
5.
경로가 완성되면 실제 경로 길이와 minlength를 비교, 더 짧으면 갱신
void travel(int n, const number W[][], ordered-set& opttour, number& minlength){ priority_queue_of_node PQ; node u, v; initialize(PQ); v.level = 0; v.path=[1]; v.bound = bound(v, n, W); minlength =; insert(PQ, v); while(!empty(PQ)){ remove(PQ, v); if(v.bound < minlength){ u.level = v.level + 1; // u의 경로를 v.path에 아직 방문하지 않은 도시로 확장 for(all i such that 2≤i≤n && i not in v.path){ u.path = v.path; u.path.append(i); if(u.level == n-2){ u.path.append(남은 유일한 도시); u.path.append(1); if(length(u.path, W) < minlength){ minlength = length(u.path, W); opttour = u.path; } } else{ u.bound = bound(u, n, W); if(u.bound < minlength) insert(PQ, u); } } } } }
C++
복사
bound 함수: 아직 경로에 없는 도시에서 나갈 수 있는 최소비용의 합을 더해 하한을 계산.

9. 결론

분기한정법은 해공간의 pruning과 탐색순서 최적화를 결합, 최적화 조합문제(0–1 배낭, TSP 등)에서 핵심적
Best-First Search는 우선순위 큐를 이용해 가장 유망한 노드(최고 bound)를 우선적으로 탐색
bound의 품질(타이트함)에 따라 pruning 효과와 성능이 달라진다