Algorithm Small Classroom (9) Branch and Bound Method

I. Overview

1.1 Concept

The branch and bound method is an algorithm for solving optimization problems. It usually searches the solution space tree of the problem in a breadth-first or minimum-cost (maximum benefit)-first manner. The basic idea is to expand the feasible solutions of the problem, and then find the best solution from each branch.

In the branch and bound method, the branch is to use the breadth-first strategy to generate all the branches of the extended node in turn. The limit is to calculate the upper bound of the node during the node expansion process, and cut off some branches while searching.

1.2 Differences from backtracking method

The solution goals are different

  • The backtracking method is to find all solutions that satisfy the constraints
  • The branch and bound method is to find a solution that satisfies the conditions, or the optimal solution in a certain sense

search differently

  • Backtracking: Depth First
  • Branch and bound method: breadth first or least cost first

1.3 Classification

queued branch and bound    

Organize the active node table into a queue, and select the next node as the current extended node according to the first-in-first-out principle of the queue.

priority queue branch and bound

Organize the active node list into a priority queue, and select the next node with the highest priority as the current extended node according to the node priority specified in the priority queue.

  • Maximum priority queue: use the maximum heap to reflect the maximum benefit priority
  • Minimum priority queue: use the minimum heap to reflect the minimum cost priority

2. Related issues

2.1 0-1 knapsack problem

Problem Description

  • Given n items and a backpack. The weight of item i is wi, its value is vi, and the capacity of the knapsack is c.
  • How to choose the items in the knapsack so that the total value of the items in the knapsack is maximized?
  • When choosing items to pack into the backpack, there are only 2 choices for each item i, that is, to pack in the backpack or not to pack in the backpack. Item i cannot be loaded into the knapsack multiple times, nor can only part of item i be loaded.

queued branch and bound

The capacity c of the backpack is 30, the diagram is as follows: 

#include<queue>
#include<iostream>
#include<vector>
#include<cstdio>
#include<string.h>
#include<algorithm>
#define N 100
using namespace std;

//记录物品信息
struct goods{
	int wight;//物品重量
	int value;//物品价值
};

//记录各节点信息
struct Node{
	int lv;//记录当前节点层数
	int wei;//当前总重量
	int val;//当前总价值
	int status[N];//当前节点的物品状态数组 0/1
};

int n,bestValue,cv,cw,C;//物品数量,价值最大,当前价值,当前重量,背包容量
int final[N];//最终存储状态
struct goods goods[N];


int BranchBound(){
	queue<Node> que;

	Node n1={0,0,0,{0}};
	que.push(n1);

	while(!que.empty()){
		Node nd;
		nd=que.front();
		int lv=nd.lv;	//当前第几层,可以作为goods[]数组的索引

		//如果是最后一层节点,
		//1. 记录该节点的信息
		//2. 弹出队列
		//3. 并跳过循环
		if(lv>=n){
			if(nd.val>bestValue)
			{
				bestValue=nd.val;
				for(int i=0;i<n;i++)
				{
					final[i]=nd.status[i];
				}
			}
			que.pop();
			continue;
		}

		//判断左孩子节点
		//该节点重量加上 下一个物品
		if(nd.wei+goods[lv].wight<=C)
		{
			//构造左孩子节点
			Node mid = que.front();
			mid.lv+=1;
			mid.val+=goods[lv].value;
			mid.wei+=goods[lv].wight;
			//置左孩子结点的 下一状态位为1
			mid.status[lv]=1;

			//将左孩子入队
			que.push(mid);
		}

		//构造并加入右孩子节点
		Node mid2 =  que.front();
		mid2.status[lv]=0;
		mid2.lv+=1;
		que.push(mid2);

		//将当前访问节点弹出
		que.pop();
	}
	return bestValue;
}

int main()
{
    printf("物品种类n:");
    scanf("%d",&n);
    printf("背包容量C:");
    scanf("%d",&C);
    for(int i = 0; i < n; i++){
        printf("物品%d的重量w[%d]及其价值v[%d]:",i+1,i+1,i+1);
        scanf("%d%d",&goods[i].wight,&goods[i].value);
    }

    int sum3 = BranchBound();

    printf("回溯法求解0/1背包问题:\nX=[");
    for(int i = 0; i < n; i++)
        cout << final[i] <<" ";//输出所求X[n]矩阵
    printf("]   装入总价值%d\n",sum3);
    return 0;

}

branch-and-bound method

#include <iostream> // 引入输入输出流头文件
#include <queue> // 引入队列头文件
#include <vector> // 引入向量头文件
#include <algorithm> // 引入算法头文件
using namespace std; // 使用标准命名空间

// 物品结构体
struct Item {
    int weight; // 物品的重量
    int value; // 物品的价值
};

// 节点结构体
struct Node {
    int level; // 节点的层次
    int profit; // 节点的收益
    int weight; // 节点的重量
    int bound; // 节点的上界
    vector<bool> taken; // 节点所选取的物品序列
};

// 优先队列的比较函数
struct CompareNode {
    bool operator()(const Node& a, const Node& b) {
        return a.bound < b.bound; // 按照上界从大到小排序
    }
};

// 计算节点的上界
int calculateBound(Node u, int n, int c, vector<Item>& items) {
    if (u.weight >= c) // 如果节点重量超过背包容量,返回0
        return 0;

    int profitBound = u.profit; // 初始化上界为节点收益
    int j = u.level + 1; // 从下一层开始考虑物品
    int totalWeight = u.weight; // 初始化总重量为节点重量

    while ((j < n) && (totalWeight + items[j].weight <= c)) { // 当物品未考虑完且总重量不超过背包容量时,循环执行
        totalWeight += items[j].weight; // 将物品加入总重量
        profitBound += items[j].value; // 将物品价值加入上界
        j++; // 考虑下一个物品
 }

    if (j < n) // 如果还有物品未考虑完,按照单位价值比例加入上界
        profitBound += (c - totalWeight) * items[j].value / items[j].weight;

    return profitBound; // 返回上界值
}

// 优先队列式分支限界法解决0-1背包问题
int knapsack(int n, int c, vector<int>& w, vector<int>& vv) {
    vector<Item> items(n); // 创建一个物品向量

    for (int i = 0; i < n; i++) { // 遍历输入的重量和价值向量,将其转化为物品结构体存入物品向量中
        items[i].weight = w[i];
        items[i].value = vv[i];
 }

    sort(items.begin(), items.end(), [](const Item& a, const Item& b) {
    return (double)a.value / a.weight > (double)b.value / b.weight;
 }); // 按照单位价值从大到小对物品向量进行排序

    priority_queue<Node, vector<Node>, CompareNode> PQ; // 创建一个优先队列,用于存储节点

    Node u, v; // 定义两个节点变量,u为当前节点,v为扩展节点
    u.level = -1; // 初始化u的层次为-1,表示根节点之前的虚拟节点
    u.profit = 0; // 初始化u的收益为0
    u.weight = 0; // 初始化u的重量为0

    int maxProfit = 0; // 初始化最大收益为0

    u.bound = calculateBound(u, n, c, items); // 计算u的上界值
    PQ.push(u); // 将u压入优先队列中

    while (!PQ.empty()) { // 当优先队列不为空时,循环执行
    u = PQ.top(); // 取出优先队列中的第一个元素,即上界最大的节点,赋值给u
    PQ.pop(); // 弹出优先队列中的第一个元素

    if (u.bound > maxProfit) { // 如果u的上界大于当前最大收益,说明有可能找到更优解,继续扩展子节点,否则剪枝处理
    v.level = u.level + 1; // 将v的层次设为u的下一层,即考虑下一个物品是否放入背包中
    v.weight = u.weight + items[v.level].weight; // 将v的重量设为u的重量加上当前考虑物品的重量,即假设放入背包中的情况
    v.profit = u.profit + items[v.level].value; // 将v的收益设为u的收益加上当前考虑物品的价值,即假设放入背包中的情况
    v.taken = u.taken; // 将v所选取的物品序列设为u所选取的物品序列,即继承父节点的选择情况
    v.taken.push_back(true); // 在v所选取的物品序列末尾添加true,表示当前考虑物品被放入背包中

    if (v.weight <= c && v.profit > maxProfit)
        maxProfit = v.profit;
        /* 如果v的重量不超过背包容量且v的收益大于当前最大收益,
           则将最大收益更新为v的收益,即找到了一个更优解 */

    v.bound = calculateBound(v, n, c, items);
        /* 计算v的上界值 */

    if (v.bound > maxProfit)
        PQ.push(v);
        /* 如果v的上界大于当前最大收益,
           则将v压入优先队列中,等待后续扩展 */

    v.weight = u.weight;
     /* 将v的重量设为u的重量,即假设不放入背包中的情况 */

     v.profit = u.profit;
     /* 将v的收益设为u的收益,即假设不放入背包中的情况 */

     v.taken = u.taken;
     /* 将v所选取的物品序列设为u所选取的物品序列,
        即继承父节点的选择情况 */

     v.taken.push_back(false);
     /* 在v所选取的物品序列末尾添加false,
        表示当前考虑物品没有被放入背包中 */

     v.bound = calculateBound(v, n, c, items);
     /* 计算v的上界值 */

     if (v.bound > maxProfit)
         PQ.push(v);
         /* 如果v的上界大于当前最大收益,
            则将v压入优先队列中,等待后续扩展 */

     }
 }

 return maxProfit;
 /* 返回最大收益值 */
}

int main() {
    int n = 3;
    /* 定义物品数量为3 */

    int c = 30;
    /* 定义背包容量为30 */

    vector<int> w = {16, 15, 15};
    /* 定义一个向量存储每个物品的重量 */

    vector<int> v = {45, 21, 25};
    /* 定义一个向量存储每个物品的价值 */

    int maxProfit = knapsack(n, c, w, v);
    /* 调用背包函数,并将返回值赋给maxProfit变量 */

    cout << "最大价值为:" << maxProfit << endl;

 return 0;
}

 2.2 The traveling salesman problem  

Problem Description:

A salesman wants to go to several cities to sell goods, and the distance between the cities is known. He wants to choose a route starting from the station, passing through each city, and finally returning to the place of residence, so that the total distance is the shortest.

 The result is: 1 3 2 4

queued branch and bound 

#include <iostream>
#include <queue>
#include <vector>
using namespace std;

const int INF = 1e9;

struct Node {
    vector<int> path;  // 当前路径
    vector<bool> visited;  // 记录节点是否已访问
    int cost;  // 当前路径的总代价
    int level;  // 当前节点的层级

    Node(int n) {
        visited.resize(n, false);
        cost = 0;
        level = 0;
    }

    Node(const Node& other) {
        path = other.path;
        visited = other.visited;
        cost = other.cost;
        level = other.level;
    }
};

void printSolution(const vector<int>& path) {
    cout << "最优解是: [";
    for (int i = 0; i < path.size(); i++) {
        cout << path[i] + 1;
        if (i != path.size() - 1) {
            cout << " ";
        }
    }
    cout << "]" << endl;
}

int tsp(vector<vector<int>>& graph, vector<int>& optimalPath) {
    int n = graph.size();

    // 初始化最小代价
    int minCost = INF;

    // 创建初始节点
    Node rootNode(n);
    rootNode.path.push_back(0);  // 起始节点为0
    rootNode.visited[0] = true;
    rootNode.level = 1;

    // 创建队列并将初始节点加入
    queue<Node> q;
    q.push(rootNode);

    // 遍历队列中的节点
    while (!q.empty()) {
        Node currNode = q.front();
        q.pop();

        // 如果当前节点是叶子节点
        if (currNode.level == n) {
            // 加上回到起始节点的代价
            currNode.cost += graph[currNode.path.back()][0];

            // 更新最小代价和最优路径
            if (currNode.cost < minCost) {
                minCost = currNode.cost;
                optimalPath = currNode.path;
            }
        }

        // 遍历当前节点的邻居节点
        for (int i = 0; i < n; i++) {
            if (!currNode.visited[i] && graph[currNode.path.back()][i] != -1) {
                // 创建新节点
                Node newNode = currNode;
                newNode.visited[i] = true;
                newNode.path.push_back(i);
                newNode.cost += graph[currNode.path.back()][i];
                newNode.level = currNode.level + 1;

                // 如果当前路径的代价小于最小代价,则加入队列继续搜索
                if (newNode.cost < minCost) {
                    q.push(newNode);
                }
            }
        }
    }

    return minCost;
}

int main() {
    int n;
    cin >> n;

    // 读取输入的图
    vector<vector<int>> graph(n, vector<int>(n));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            cin >> graph[i][j];
        }
    }

    // 求解旅行售货员问题
    vector<int> optimalPath;
    int minCost = tsp(graph, optimalPath);
    // 输出结果
    cout << "最优值为: " << minCost << endl;
    printSolution(optimalPath);

    return 0;
}

priority queue branch and bound 

#include <iostream>
#include <queue>
#include <vector>
using namespace std;

const int INF = 1e9;

struct Node {
    vector<int> path;  // 当前路径
    vector<bool> visited;  // 记录节点是否已访问
    int cost;  // 当前路径的总代价
    int level;  // 当前节点的层级

    Node(int n) {
        visited.resize(n, false);
        cost = 0;
        level = 0;
    }

    Node(const Node& other) {
        path = other.path;
        visited = other.visited;
        cost = other.cost;
        level = other.level;
    }
};

struct CompareNode {
    bool operator()(const Node& node1, const Node& node2) {
        return node1.cost > node2.cost;  // 按照代价从小到大排序
    }
};

void printSolution(const vector<int>& path) {
    cout << "最优解是: [";
    for (int i = 0; i < path.size(); i++) {
        cout << path[i] + 1;
        if (i != path.size() - 1) {
            cout << " ";
        }
    }
    cout << "]" << endl;
}

int tsp(vector<vector<int>>& graph, vector<int>& optimalPath) {
    int n = graph.size();

    // 初始化最小代价
    int minCost = INF;

    // 创建初始节点
    Node rootNode(n);
    rootNode.path.push_back(0);  // 起始节点为0
    rootNode.visited[0] = true;
    rootNode.level = 1;

    // 创建优先队列并将初始节点加入
    priority_queue<Node, vector<Node>, CompareNode> pq;
    pq.push(rootNode);

    // 遍历优先队列中的节点
    while (!pq.empty()) {
        Node currNode = pq.top();
        pq.pop();

        // 如果当前节点是叶子节点
        if (currNode.level == n) {
            // 加上回到起始节点的代价
            currNode.cost += graph[currNode.path.back()][0];

            // 更新最小代价和最优路径
            if (currNode.cost < minCost) {
                minCost = currNode.cost;
                optimalPath = currNode.path;
            }
        }

        // 遍历当前节点的邻居节点
        for (int i = 0; i < n; i++) {
            if (!currNode.visited[i] && graph[currNode.path.back()][i] != -1) {
                // 创建新节点
                Node newNode = currNode;
                newNode.visited[i] = true;
                newNode.path.push_back(i);
                newNode.cost += graph[currNode.path.back()][i];
                newNode.level = currNode.level + 1;

                // 如果当前路径的代价小于最小代价,则加入优先队列继续搜索
                if (newNode.cost < minCost) {
                    pq.push(newNode);
                }
            }
        }
    }

    return minCost;
}

int main() {
    int n;
    cin >> n;

    // 读取输入的图
    vector<vector<int>> graph(n, vector<int>(n));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j <n;  j++) {
            cin >> graph[i][j];
        }
    }
    // 求解旅行售货员问题
    vector<int> optimalPath;
    int minCost = tsp(graph, optimalPath);

    // 输出结果
    cout << "最优值为: " << minCost << endl;
    printSolution(optimalPath);

    return 0;
}

2.3 Loading problem

Problem Description:

Optimal loading problem: There is a batch of n containers to be loaded on a ship with load capacity C, and the weight of container i is wi. Without considering the container volume, how to choose the container to be loaded into the ship so that loading The total weight of the container in the ship is the largest

An example of the known optimal loading problem, n=3, C=30, W={16,15,15}, try to answer the following questions:

 queued branch and bound 

#include <iostream>
#include <queue>
#include <vector>
using namespace std;

struct Node {
    vector<int> load;  // 当前装载情况
    int level;  // 当前节点的层级
    int weight;  // 当前装载的总重量

    Node(int n) {
        load.resize(n, 0);
        level = 0;
        weight = 0;
    }

    Node(const Node& other) {
        load = other.load;
        level = other.level;
        weight = other.weight;
    }
};

void knapsack(int n, int C, const vector<int>& weights) {
    // 初始化最优值和最优解
    int bestValue = 0;
    vector<int> bestLoad(n, 0);

    // 创建初始节点
    Node rootNode(n);
    rootNode.level = 0;

    // 创建队列并将初始节点加入
    queue<Node> q;
    q.push(rootNode);

    // 遍历队列中的节点
    while (!q.empty()) {
        Node currNode = q.front();
        q.pop();

        // 如果当前节点是叶子节点
        if (currNode.level == n) {
            // 更新最优值和最优解
            if (currNode.weight <= C && currNode.weight > bestValue) {
                bestValue = currNode.weight;
                bestLoad = currNode.load;
            }
            continue;
        }

        // 不装载第level物品的子节点
        Node noNode = currNode;
        noNode.level = currNode.level + 1;
        q.push(noNode);

        // 装载第level物品的子节点
        if (currNode.weight + weights[currNode.level] <= C) {
            Node yesNode = currNode;
            yesNode.level = currNode.level + 1;
            yesNode.load[currNode.level] = 1;
            yesNode.weight += weights[currNode.level];
            q.push(yesNode);
        }
    }

    // 输出结果
    cout << "最优值为: " << bestValue << endl;
    cout << "最优解为: [";
    for (int i = 0; i < n; i++) {
        cout << bestLoad[i] << " ";
    }
    cout << "]" << endl;
}

int main() {
    int n, C;
    cin >> n >> C;

    // 读取物品重量
    vector<int> weights(n);
    for (int i = 0; i < n; i++) {
        cin >> weights[i];
    }

    // 求解最优装载问题
    knapsack(n, C, weights);

    return 0;
}

priority queue branch and bound 

#include <iostream>
#include <queue>
#include <vector>
using namespace std;

struct Node {
    vector<int> load;  // 当前装载情况
    int level;  // 当前节点的层级
    int weight;  // 当前装载的总重量

    Node(int n) {
        load.resize(n, 0);
        level = 0;
        weight = 0;
    }

    Node(const Node& other) {
        load = other.load;
        level = other.level;
        weight = other.weight;
    }
};

struct NodeComparator {
    bool operator()(const Node& a, const Node& b) {
        return a.weight < b.weight;  // 按照节点的权重(装载的总重量)升序排序
    }
};

void knapsack(int n, int C, const vector<int>& weights) {
    // 初始化最优值和最优解
    int bestValue = 0;
    vector<int> bestLoad(n, 0);

    // 创建初始节点
    Node rootNode(n);
    rootNode.level = 0;

    // 创建优先队列并将初始节点加入
    priority_queue<Node, vector<Node>, NodeComparator> pq;
    pq.push(rootNode);

    // 遍历优先队列中的节点
    while (!pq.empty()) {
        Node currNode = pq.top();
        pq.pop();

        // 如果当前节点是叶子节点
        if (currNode.level == n) {
            // 更新最优值和最优解
            if (currNode.weight <= C && currNode.weight > bestValue) {
                bestValue = currNode.weight;
                bestLoad = currNode.load;
            }
            continue;
        }

        // 不装载第level物品的子节点
        Node noNode = currNode;
        noNode.level = currNode.level + 1;
        pq.push(noNode);

        // 装载第level物品的子节点
        if (currNode.weight + weights[currNode.level] <= C) {
            Node yesNode = currNode;
            yesNode.level = currNode.level + 1;
            yesNode.load[currNode.level] = 1;
            yesNode.weight += weights[currNode.level];
            pq.push(yesNode);
        }
    }

    // 输出结果
    cout << "最优值为: " << bestValue << endl;
    cout << "最优解为: [";
    for (int i = 0; i < n; i++) {
        cout << bestLoad[i] << " ";
    }
    cout << "]" << endl;
}

int main() {
    int n, C;
    cin >> n >> C;

    // 读取物品重量
    vector<int> weights(n);
    for (int i = 0; i < n; i++) {
        cin >> weights[i];
    }

    // 求解最优装载问题
    knapsack(n, C, weights);

    return 0;
}

6.4 Wiring Problems 

Problem Description

The printed circuit board divides the wiring area into n×m square grid arrays, as shown in the figure. An exact circuit board routing problem requires determining the shortest routing scheme connecting the midpoint of square a to the midpoint of square b. When wiring, the circuit can only be routed along a straight line or at a right angle. In order to avoid line intersections, the routed squares are marked closed, and other lines are not allowed to pass through the closed area. For the sake of discussion, we assume that the area outside the circuit board is a box that has been marked closed.

Guess you like

Origin blog.csdn.net/qq_62377885/article/details/130747118