目录
一、分支限界算法思想
1. 分支限界法类似于回溯算法,是在问题的解空间树上搜索问题的算法,主要体现在两点不同:
- 求解目标不同。回溯算法的求解目标是找出解空间树中满足约束条件的所有解,而分支限界法的求解目标是找出满足约束条件的一个解,或者是在满足约束条件的解中找出某种意义下的最优解。
- 搜索解空间树的方式不同。回溯算法以深度优先的方式搜索解空间树,而分支限界法则以广度优先或者以最小耗费优先的方式搜索解空间树。
2. 分治限界算法基本思想:
分支限界法常以广度优先或者以最小耗费优先的方式搜索解空间树。在分支限界法中,每一个活节点只有一次机会称为扩展节点,活结点一旦称为扩展节点,每一个产生所有儿子节点(分支),在这些儿子节点中,导致不可行解或是导致非最优解的儿子节点会被舍弃掉,其余儿子节点会被加入活结点表中。
为了有效的选择下一个扩展节点加速搜索,在每一个活结点处计算一个函数值(限界),并根据计算的函数值结果从当前活结点表中去下一个最有利的节点称为扩展节点,使搜索朝着解空间树上最优解的分支推进。重复上述节点扩展过程,直到找到所需的最优解或者活结点表为空。
扩展节点:一个正在产生儿子的节点
活结点:一个自身已经生成,但其儿子还没有全部生成的节点
死结点:一个所有儿子已经产生的节点
深度优先搜索是对一个扩展节点R,一旦产生了它的一个儿子C,就把C当作新的扩展节点。在完成对子树C的深度搜索之后回溯到R时,将R重新变成扩展节点,继续生成R的下一个儿子。
广度优先搜索是在一个扩展节点R变成死节点之前,他一直在扩展节点。
从活节点表中选择下一个扩展节点时,不同的方式导致不同的分支限界法,常见有:
二、分支限界法的应用
1. 集装箱装载问题
有一批共n个集装箱要装上两艘载重量分别为c1,c2的轮船,其中集装箱i的重量为wi,且要求确定是否有一个合理的装载方案可将这n个集装箱装上这两艘轮船。
分析:
仅求出装载的最大重量
#include<iostream>
#include<queue>
using namespace std;
//描述节点类型
struct Node
{
Node(int l, int w)
{
level = l;
weight = w;
}
int level;//当前节点的层数
int weight;//从根节点到当前节点所选物品的总重量
};
int main()
{
int w[] = {
12,8,15 };//集装箱的重量
int c = 27;//轮船的容量
int n = sizeof(w) / sizeof(w[0]);//集装箱的数量
int cw = 0;//已选物品的重量
int bestw = 0;//记录最优装载量
int i = 0;
//广度优先遍历子集树的FIFO队列
queue<Node> que;
while (i < n)//当前节点是第i层
{
//处理左孩子,表示选择i节点
if (cw + w[i] <= c)//选择i节点后,其总重量不能超过轮船的总重量
{
if (cw + w[i] > bestw)
{
bestw = cw + w[i];
}
que.push(Node(i + 1, cw + w[i]));//活结点孩子入队列
}
//处理右孩子,表示不选择i节点
que.push(Node(i + 1, cw));
//处理完i节点后,它成为死节点,然后出队列
Node node = que.front();
que.pop();
//恢复cw和i的值,表示从i节点跳到广度遍历的下一个节点
cw = node.weight;
i = node.level;
}
cout << bestw << endl;
}
打印出具体装入的集装箱
#include<iostream>
#include<queue>
using namespace std;
int w[] = {
12,8,15 };//集装箱的重量
int c = 27;//轮船的容量
const int n = sizeof(w) / sizeof(w[0]);//集装箱的数量
int cw = 0;//已选物品的重量
int bestw = 0;//记录最优装载量
//描述节点类型
struct Node
{
Node(int w, int l, Node* p, bool left)
{
weight = w;
level = l;
parent = p;
isleft = left;
}
int level;//当前节点的层数
int weight;//从根节点到当前节点所选物品的总重量
Node* parent;//记录当前节点的父节点
bool isleft;//记录当前节点是否被选择
};
int i = 0;
Node* node = nullptr;
//广度优先遍历子集树的FIFO队列
queue<Node*> que;
Node* bestnode = nullptr;
//添加活结点到队列中
void addLiveNode(int w, int level, Node* parent, bool isleft)
{
Node* node = new Node(w, level, parent, isleft);
que.push(node);
//在最后一层,记录最优节点
if (level == n && w == bestw)
{
bestnode = node;
}
}
int main()
{
while (i < n)//当前节点是第i层
{
//处理左孩子,表示选择i节点
if (cw + w[i] <= c)//选择i节点后,其总重量不能超过轮船的总重量
{
if (cw + w[i] > bestw)
{
bestw = cw + w[i];
}
addLiveNode(cw + w[i], i + 1, node, true);
}
//处理右孩子,表示不选择i节点
addLiveNode(cw, i + 1, node, false);
//处理完i节点后,它成为死节点,然后出队列
node = que.front();
que.pop();
//恢复cw和i的值,表示从i节点跳到广度遍历的下一个节点
cw = node->weight;
i = node->level;
}
cout << bestw << endl;
int bestx[n] = {
0 };
for (int j = n - 1; j >= 0; j--)
{
bestx[j] = bestnode->isleft ? 1 : 0;
bestnode = bestnode->parent;
}
for (int v : bestx)
{
cout << v << " ";
}
}
添加剪枝操作,限界
#include<iostream>
#include<queue>
using namespace std;
int w[] = {
12,8,15 };//集装箱的重量
int c = 27;//轮船的容量
const int n = sizeof(w) / sizeof(w[0]);//集装箱的数量
int cw = 0;//已选物品的重量
int bestw = 0;//记录最优装载量
int r = 0;
//描述节点类型
struct Node
{
Node(int w, int l, Node* p, bool left)
{
weight = w;
level = l;
parent = p;
isleft = left;
}
int level;//当前节点的层数
int weight;//从根节点到当前节点所选物品的总重量
Node* parent;//记录当前节点的父节点
bool isleft;//记录当前节点是否被选择
};
int i = 0;
Node* node = nullptr;
//广度优先遍历子集树的FIFO队列
queue<Node*> que;
Node* bestnode = nullptr;
//添加活结点到队列中
void addLiveNode(int w, int level, Node* parent, bool isleft)
{
Node* node = new Node(w, level, parent, isleft);
que.push(node);
//在最后一层,记录最优节点
if (level == n && w == bestw)
{
bestnode = node;
}
}
int maxBound(int level)
{
int s = 0;
for (int j = level + 1; j < n; j++)
{
s += w[i];
}
return s;
}
int main()
{
while (i < n)//当前节点是第i层
{
//处理左孩子,表示选择i节点
if (cw + w[i] <= c)//选择i节点后,其总重量不能超过轮船的总重量
{
if (cw + w[i] > bestw)
{
bestw = cw + w[i];
}
addLiveNode(cw + w[i], i + 1, node, true);
}
//处理右孩子,表示不选择i节点
r = maxBound(i);//求第i个节点的重量值上界
if (cw + r >= bestw)//>=这里的=不能少,否则无法选择到叶子节点上c=20
{
addLiveNode(cw, i + 1, node, false);
}
//处理完i节点后,它成为死节点,然后出队列
node = que.front();
que.pop();
//恢复cw和i的值,表示从i节点跳到广度遍历的下一个节点
cw = node->weight;
i = node->level;
}
cout << bestw << endl;
int bestx[n] = {
0 };
for (int j = n - 1; j >= 0; j--)
{
bestx[j] = bestnode->isleft ? 1 : 0;
bestnode = bestnode->parent;
}
for (int v : bestx)
{
cout << v << " ";
}
}
2. 0-1背包问题
- FIFO队列
#include<iostream>
#include<queue>
using namespace std;
int w[] = {
16,15,15 };
int v[] = {
45,25,25 };
int c = 30;
const int n = sizeof(w) / sizeof(w[0]);
int cw = 0;
int cv = 0;
int bestv = 0;
struct Node
{
Node(int w, int v, int l, Node* p, bool left)
{
weight = w;
value = v;
level = l;
parent = p;
isleft = left;
}
int weight;
int value;
int level;
Node* parent;
bool isleft;
};
int i = 0;
queue<Node*> que;
Node* bestnode = nullptr;
void addLiveNode(int w, int v, int l, Node* p, bool left)
{
Node* node = new Node(w, v, l, p, left);
que.push(node);
if (l == n && v == bestv)
{
bestnode = node;
}
}
int maxBound(int i)
{
int s = 0;
for (int j = i + 1; j < n; j++)
{
s += v[i];
}
return s;
}
int main()
{
Node* node = nullptr;
while (i < n)
{
if (cw + w[i] <= c)
{
if (cv + v[i] > bestv)
{
bestv = cv + v[i];
}
addLiveNode(cw + w[i], cv + v[i], i + 1, node, true);
}
int r = maxBound(i);
if (cv + r >= bestv)
{
addLiveNode(cw, cv, i + 1, node, false);
}
node = que.front();
que.pop();
cw = node->weight;
cv = node->value;
i = node->level;
}
int bestx[n] = {
0 };
for (int j = n - 1; j >= 0; j--)
{
bestx[j] = bestnode->isleft ? 1 : 0;
bestnode = bestnode->parent;
}
cout << bestv << endl;
for (int v : bestx)
{
cout << v << " ";
}
}
- 优先级队列
#include<iostream>
#include<queue>
#include<functional>
using namespace std;
int w[] = {
16,15,15 };
int v[] = {
45,25,25 };
int c = 30;
const int n = sizeof(w) / sizeof(w[0]);
int cw = 0;
int cv = 0;
int bestv = 0;
struct Node
{
Node(int w, int v, int l, int up, Node* p, bool left)
{
weight = w;
value = v;
level = l;
upbound = up;
parent = p;
isleft = left;
}
int weight;
int value;
int level;
int upbound;//节点的价值上界,从这个节点往下,最多能选择的物品产生的总价值
Node* parent;
bool isleft;
};
int i = 0;
priority_queue<Node*, vector<Node*>, function<bool(Node*, Node*)>> que([](Node* n1, Node* n2)->bool {
return n1->upbound < n2->upbound;
});
void addLiveNode(int w, int v, int l, int up, Node* p, bool left)
{
Node* node = new Node(w, v, l, up, p, left);
que.push(node);
}
//求价值上界
int maxBound(int i)
{
int s = cv;
for (int j = i; j < n; j++)
{
s += v[i];
}
return s;
}
int main()
{
Node* node = nullptr;
int upbound = maxBound(0);
while (i < n)
{
if (cw + w[i] <= c)
{
if (cv + v[i] > bestv)
{
bestv = cv + v[i];
}
addLiveNode(cw + w[i], cv + v[i], i + 1, upbound, node, true);
}
//不选择物品i
upbound = maxBound(i + 1);//i+1表示第一个未被处理的物品的下标
if (upbound >= bestv)
{
addLiveNode(cw, cv, i + 1, upbound, node, false);
}
node = que.top();
que.pop();
cw = node->weight;
cv = node->value;
i = node->level;
upbound = node->upbound;
}
int bestx[n] = {
0 };
for (int j = n - 1; j >= 0; j--)
{
bestx[j] = node->isleft ? 1 : 0;
node = node->parent;
}
cout << bestv << endl;
for (int v : bestx)
{
cout << v << " ";
}
}