分支定界(英語:Branch and bound,BB)是用於離散最佳化、組合最佳化以及數學最佳化問題的演算法設計範式。分支定界演算法可以視為一種對可行解進行窮舉的演算法,但是和窮舉法所不同的是,分支定界演算法在對某一分支進行檢索之前會先算出該分支的上界或下界,如果界限不比目前最佳解更好,那麼該分支就會被捨棄,從而節約了大量的時間。分支定界演算法非常依賴合適的上界或下界,如果無法找到合適的界限,該演算法將會退化為窮舉法。
該方法最初是由阿爾薩·蘭德和艾莉森·哈考特在1960年由英國石油公司贊助的倫敦經濟學院進行離散規劃研究時提出的,[1]目前已成為解決NP困難最佳化問題最常用的工具。[2]「分支定界」一詞最早出現在解決旅行推銷員問題的時候。[3][4]
概述
分支定界法的目的是從可行解集合中選出一個解,使得目標函數最大化或最小化。其中集合被稱為搜尋空間或可行區域。本節所有的最佳化問題均可視為對進行最小化,因為對進行最大化問題本質上還是對進行最小化。分支定界法需要遵循以下兩個原則:
- 先是將搜尋空間通過遞歸的手段分成多個子空間,在每個子空間對進行最小化,這種分割方式就是分支。
- 如果只有分支那麼這種方法就成為了暴力搜尋法,運算量將會非常龐大。為了提升演算法的效能,需要對每個分支進行下界計算,對於那些下界已經超過目前最佳解的分支,需要進行剪枝操作。
為了將這些原則轉化為問題的具體演算法,我們需要將這些候選解轉化為合適的數據結構,這樣的表示方式被稱為問題的實例。我們用表示實例的候選解集,實例表示必須有如下三個操作:
- :產生兩個或多個實例,每個實例為的一個子集。(通常來講,每個子集都是互不相交的,這是為了避免每個候選解被多次訪問從而浪費時間,但有時也會有例外。的最佳解必然會出現在它的一個或多個子集中。[5])
- :計算實例中所有候選解所對應的目標函數值的下界,滿足對於任意,。
- :確定是否表示單個候選解。(如果不是,接下來可以從中回傳一些可行的解再進一步進行分支定界的操作。[5])如果回傳了一個解,那麼提供了整個可行解空間中最佳目標函數的上界。
通過使用這些操作,分支定界演算法在分支操作形成的實例樹中執行自頂向下遞歸搜尋。在訪問實例時,我們不妨先檢查,如果已經大於目前所找到的上界,我們就直接丟棄該實例。為了實現這一步驟,我們需要設定一個全域變數,用於記錄目前為止所檢查的所有實例中看到的最小上界。
下面是最小化任意目標函數的通用分支定界演算法框架。[2]要從中得到一個演算法,我們需要一個邊界函數,用來計算函數在搜尋樹節點上的下界,以及一個基於特定問題的分支規則。本文提出的通用演算法是一個高階函數。
- 首先使用啟發法找出一個最佳化問題的解,並儲存數值。(如果無法通過啟發法找到解,則設B的值為無窮大)B表示到目前為止找到的最佳解,並把它作為候選解的上界。
- 初始化一個佇列,用於儲存在不進行變數分配時所得到的局部解。
- 執行如下迴圈直至佇列被清空:
- 從佇列中取出節點
- 如果節點代表單一的候選解且滿足,那麼我們就可以說為目前所找到的最佳解,令←。
- 反之,節點產生新的分支節點,對於這些新產生的節點:
- 如果,則捨棄該節點,因為最佳解不可能出現在這個節點裏。
- 反之,將節點存入佇列。
在這一演算法中,佇列也可以替換為其他的數據結構。在使用遵循先進先出原則的佇列時,該演算法屬於廣度優先搜尋。反之如果使用遵循先進後出的堆疊儲存節點,該演算法就成為了深度優先搜尋。當然,也可以使用優先佇列對所有的節點的下界進行排序,進一步提升效率。[2]採用這種演算法的例子是戴克斯特拉演算法及其衍生的A*搜尋演算法。當無法通過啟發法生成初始解時,建議使用深度優先演算法的變體,因為它可以快速生成完整解,從而得到整個問題的上界。[6]
和C++語言相似的偽代碼如下:
// 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;
}
在上述偽代碼中,函數heuristic_solve
和populate_candidates
為子程式,必須基於具體問題進行設計。
當為空間處於的向量時,分支定界演算法可以與區間分析[7]和間隔承包商技術相結合,以提供全域最小包絡值的保證。[8][9]
參考文獻
Wikiwand in your browser!
Seamless Wikipedia browsing. On steroids.
Every time you click a link to Wikipedia, Wiktionary or Wikiquote in your browser's search results, it will show the modern Wikiwand interface.
Wikiwand extension is a five stars, simple, with minimum permission required to keep your browsing private, safe and transparent.