贪心算法解背包问题的基本步骤:
•1)计算每种物品单位重量的价值Vi / Wi
•2)依贪心选择策略,将尽可能多的单位重量价值最高的物品装入背包。
•3)若将这种物品全部装入背包后,背包内的物品总重量未超过C,则选择单位重量价值次高的物品并尽可能多地装入背包。
•4)依此策略一直进行下去,直到背包装满为止。
0-1背包:给定n种物品和一个背包。物品i的重量是Wi,其价值为Vi,背包的容量为C。应如何选择装入背包的物品,使得装入背包中物品的总价值最大? 在选择装入背包的物品时,对每种物品i只有2种选择,即装入背包或不装入背包。不能将物品i装入背包多次,也不能只装入部分的物品i。
一般背包问题(简称背包问题): 与0-1背包问题类似,所不同的是在选择物品i装入背包时,可以只选择物品i的一部分,而不一定要将i整个装入背包,0≤xi≤1。
用贪心算法解一般背包问题的基本步骤:
首先,计算每种物品单位重量的价值Vi/Wi ; 然后,依贪心选择策略,将尽可能多的单位重量价值最高的物品装入背包。若将这种物品全部装入背包后,背包内的物品总重量未超过C,则选择单位重量价值次高的物品并尽可能多地装入背包。依此策略一直地进行下去,直到背包装满为止。 具体算法可描述如下页:
void Knapsack(int n,float M,float v[],float w[],float x[])
{ Sort(n,v,w);
for (int i=1;i<=n;i++) x[i]=0;
float c=M;
for (i=1;i<=n;i++) {
if (w[i]>c) break;
x[i]=1; c-=w[i]; }
if (i<=n) x[i]=c/w[i]; }
0-1背包问题:
解空间: 子集树
可行性约束函数:
上界函数:
cp+r>bestp
r是尚未考虑的剩余物品价值总和。
Void Knap<Typew, Typep>::backtrack (int i)
{// 搜索第i层结点
if (i > n) { // 到达叶结点
bestp=cp;
return; }
if (cw + w[i] <= c) {// 搜索左子树,x[i] = 1
cw += w[i];
cp += p[i];
backtrack(i + 1);
cw -= w[i];
cp -= p[i]; }
if (bound(i+1) > bestp) // 搜索右子树,x[i] = 0
backtrack(i + 1);
}
0-1背包问题-上界函数:
template<class Typew, class Typep>
Typep Knap<Typew, Typep>::Bound(int t)
{ // 计算上界
Typew cleft = c - cw; // 剩余容量
Typep b = cp; // 当前价值
while (t <= n && w[t] <= cleft) {
// 以物品单位重量价值递减序装入物品
// 为了方便计算上界函数,可先将物品按单位重量价值从大到小排序
cleft -= w[t];
b += p[t];
t++;
}
if (t <= n) b += p[t]/w[t] * cleft; // 装满背包
return b;
}
回溯算法也叫试探法,它是一种利用试探或回溯(Backtracking)的搜索技术求解的方法。 回溯算法在问题的解空间中使用一种可以避免不必要搜索的穷举式搜索法,可以系统的搜索一个问题的所有解或任一解。
回溯法的搜索方式:
通常将问题的解空间组织成树或图的形式,使得回溯法能方便地搜索整个解空间。 回溯法在问题的解空间树中,按深度优先策略(或先序遍历,根-左-右顺序),从根结点出发搜索解空间树。 注意:这棵解空间树不是遍历前预先建立的,而是隐含在遍历过程中。
从开始结点(根结点)出发, 这个开始结点就成为一个活结点,同时也成为当前的扩展结点。 活结点:一个自身已生成但其子结点还没有全部生成的节点称做活结点。 扩展结点:一个正在产生子结点的结点称为扩展结点。
在当前扩展结点处,搜索向纵深方向移至一个新结点。这个新结点就成为一个新的活结点,并成为当前扩展结点。 如果在当前扩展结点处不能再向纵深方向移动,则当前扩展结点就成为死结点。 死结点:一个所有子结点已经全部产生的结点称做死结点。
当前扩展结点成为死结点时,应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点。 回溯法即以这种工作方式递归地在解空间中搜索,直至找到所要求的解,或解空间中已无活结点时终止。
回溯法的基本思想
在用回溯法搜索解空间树时,通常采用两种策略来避免无效搜索,提高回溯法的搜索效率。 a.用约束函数在扩展结点处剪去不满足约束的子树; b.用限界函数剪去得不到最优解的子树。 这两类函数统称为剪枝函数。
运用回溯法解题通常包含以下三个步骤: (1)定义问题的解空间; (2)确定易于搜索的解空间结构; (3)以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。
子集树:
用回溯法搜索子集树的算法框架:
void backtrack (int i)
{ if (i>n) output(x);
else
for (int j=1;j>=0;j--) { //0、1两个分支
x[i]=j;
if (legal(i)) backtrack(i+1); }
//legal(i)即剪枝函数
}//遍历子集树需(2n)计算时间
排列树:
用回溯法搜索排列树的算法框架:
void backtrack (int i)
{
if (i>n) output(x);
else
for (int j=i;j<=n;j++) {
swap(x[i], x[j]);
if (legal(i)) backtrack(i+1);
swap(x[i], x[j]);
}
} //遍历排列树需要(n!)计算时间
n后问题:
n后问题中的剪枝函数;
bool Queen::Place(int k) //place函数测试皇后互不攻击
{
for (int j=1;j<k;j++)
if ((abs(k-j)==abs(x[j]-x[k]))||(x[j]==x[k])) return false;
return true;
}
void Queen::Backtrack(int t)
{
if (t>n) sum++; //sum为可行方案数
else
for (int i=1;i<=n;i++) {
x[t]=i;
if (Place(t)) Backtrack(t+1);
}
}