初始条件:
K(0,j)=0,K(w,0)=0.
初始化条件表示把前面i个物品装入容量为0的背包和把0个物品装入容量为j的背包,得到的价值均为0。
第二个式子说明:
如果第i个物品的重量大于背包的容量,则装入第i个物品得到的最大价值和装入第i-1个物品得到的最大价值是相同的,即物品i不能装入背包中。
第一个式子说明:
如果第i个物品的重量小于背包的容量,则会有两种情况:
(1)如果把第i个物品装入背包,则背包中物品的价值就等于把前i-1个物品装入容量为iwj的背包中的价值加上第i个物品的价值iv;
(2)如果第i个物品没有装入背包,则背包中物品的价值就是等于把前i-1个物品装入容量为j的背包中所取得的价值。
显然,取二者中价值较大者作为把前i个物品装入容量为j的背包中的最优解。
w-wj
…
w
W
j-1
K[w-wj, j-1]
+vj
K[w,j-1]
j
K[w, j]
n
目标
构建的表格:
时间复杂度为:
O(nw)
分支限界法:
1.按单位价值对待处理的物品进行排序
2.计算可行结点可获得的最大价值的上界
boundfunction(上界函数):
ub=v+(W-w)(vi+1/wi+1)
3.用结点的上界值作为优先级,建立优先队列
4.按照优先队列对解空间进行搜索
回溯法:
回溯法应用深度优先遍历解空间,如果按单位价值排序可以剪掉部分右边的枝,如果设置约束条件可以剪掉不是最优解的枝。
在搜索解空间树时,只要其左儿子结点是一个可行结点,搜索就进入其左子树。
当右子树有可能包含最优解时才进入右子树搜索。
否则将右子树剪去。
设r是当前剩余物品价值总和;cp是当前价值;bestp是当前最优价值。
当cp+r≤bestp时,可剪去右子树。
计算右子树中解的上界的更好方法是将剩余物品依其单位重量价值排序,然后依次装入物品,直至装不下时,再装入该物品的一部分而装满背包。
由此得到的价值是右子树中解的上界。
确定了解空间的组织结构后,回溯法就从开始结点(根结点)出发,以深度优先的方式搜索整个解空间。
这个开始结点就成为一个活结点,同时也成为当前的扩展结点。
在当前的扩展结点处,搜索向纵深方向移至一个新结点。
这个新结点就成为一个新的活结点,并成为当前扩展结点。
如果在当前的扩展结点处不能再向纵深方向移动,则当前扩展结点就成为死结点。
此时,应往回溯至最近的一个活结点处,并使这个活结点成为当前的扩展结点。
回溯法即以这种工作方式递归地在解空间中搜索,直至找到所要求的所有解或解空间中已没有活结点时为止。
贪心法:
贪心法是“步步贪心”,从局部最优推进得到全局最优的近似解,所以用贪心法存在问题:
1).不能保证求得的最后解是最佳的;
2).不能用来求最大或最小解问题;
3).只能求满足某些约束条件的可行解的范围。
目标是价值最大
约束条件是装入的物品总重量不超过背包容量:
∑wi<=M(M=150)
(1)根据贪心的策略,每次挑选价值最大的物品或者所占空间最小的物品装入背包,似乎不行
(2)每次选取单位容量价值最大的物品,成为解题的策略。
关键代码:
publicvoidgetMaxValue(){
PriorityQueuepq=newPriorityQueue();//根节点
thingNodeinitial=newthingNode();
initial.level=-1;
initial.upprofit=26;
pq.add(initial);
while(!
pq.isEmpty()){
thingNodefatherNode=pq.poll();//叶子节点
if(fatherNode.level==n-1){
if(fatherNode.value>maxValue){
maxValue=(int)fatherNode.value;
for(inti=n-1;i>=0;i--){
bestWay[i]=fatherNode.Left;
fatherNode=fatherNode.father;
}
}
}
else{//判断是否加入队列
if(weight[fatherNode.level+1]+fatherNode.weight<=capacity){
thingNodenewNode=newthingNode();
newNode.level=fatherNode.level+1;
newNode.value=fatherNode.value+value[fatherNode.level+1];
newNode.weight=weight[fatherNode.level+1]+fatherNode.weight;
newNode.upprofit=Bound(newNode);
newNode.father=fatherNode;
newNode.Left=1;
if(newNode.upprofit>maxValue)
pq.add(newNode);}
//向右节点搜索,其能够取到的价值上界通过父亲节点的上界减去本层物品的价值。
if((fatherNode.upprofit-value[fatherNode.level+1])>maxValue){
thingNodenewNode2=newthingNode();
newNode2.level=fatherNode.level+1;
newNode2.value=fatherNode.value;
newNode2.weight=fatherNode.weight;
newNode2.father=fatherNode;
newNode2.upprofit=fatherNode.upprofit-value[fatherNode.level+1];
newNode2.Left=0;
pq.add(newNode2);
}
}
}
//用于计算该节点的最高价值上界
publicdoubleBound(thingNodeno){
doublemaxLeft=no.value;
intleftWeight=capacity-no.weight;
inttemplevel=no.level;//尽力依照单位重量价值次序装剩余的物品
while(templevel<=n-1&&leftWeight>weight[templevel]){
leftWeight-=weight[templevel];
maxLeft+=value[templevel];
templevel++;}//不能装时,用下一个物品的单位重量价值折算到剩余空间。
if(templevel<=n-1){
maxLeft+=value[templevel]/weight[templevel]*leftWeight;}
returnmaxLeft;}
运行结果截屏
输入:
intn=9;
intcapacity=36;
int[]weight={6,3,4,2,5,7,9,10,13};
double[]value={30,14,16,9,20,18,34,45,27};
输出:
c.N皇后问题
问题描述:
n皇后问题等于于在n×n格的棋盘上放置n个皇后,任何2个皇后不放在同一行或同一列或同一斜线上。
即规定每一列放一个皇后,不会造成列上的冲突;当第i行被某个皇后占领后,则同一行上的所有空格都不能再放皇后,要把以i为下标的标记置为被占领状态。
算法设计:
首先想到的就是把棋盘存储为一个二维数组,然后在需要在第i行第j列放置皇后时,根据问题的描述,首先判断是在第i行是否有皇后,然后判断第j列是否有皇后,最后需要判断在同一斜线上是有皇后,按照该方法需要判断两次,正对角线方向和负对角线方向。
后来发现可以把棋盘存储为一个N维数组a[N],数组中第i个元素的值代表第i行的皇后位置,方便将问题的空间规模压缩为一维O(N)。
在判断是否冲突时,首先每行只有一个皇后,在数组中只占据一个元素的位置,行冲突就不存在了,其次是列冲突,判断一下是否有a[i]与当前要放置皇后的列j相等即可。
至于斜线冲突,通过观察可以发现所有在斜线上冲突的皇后的位置都有规律即它们所在的行列互减的绝对值相等。
这样某个位置是否可以放置皇后的问题就解决了。
n皇后问题是回溯法的经典应用,所以使用回溯法来解决该问题。
具体实现也有两个途径,递归和非递归。
递归方法大致如下:
voidqueen(inti)
{
if(n==i)//如果已经找到结果,则打印结果
print_result();
else{
for(k=0toN){//第i行每一个列
if(can_place(i,k){
place(i,k);//放置皇后
queen(i+1);}
}
}
}
非递归方法的一个关键何时回溯及如何回溯的问题。
程序首先对N行中的每一行进行搜索,寻找可以放置皇后的位置,具体方法是对该行的每一列进行搜索,看是否可以放置皇后,如果可以,则在该列放置一个皇后,然后继续搜索下一行的皇后位置。
如果已经搜索完所有的列都没有找到可以放置皇后的列,此时就应该回溯到上一层,把上一行皇后的位置往后移,如果上一行皇后移动后也找不到位置,则继续回溯直至某一行找到皇后的位置或回溯到第一行,如果第一行皇后也无法找到可以放置皇后的位置,则说明已经找到所有的解程序终止。
如果该行已经是最后一行,则搜索完该行后,如果找到放置皇后的位置,则说明找到一个结果,打印出来。
如果我们要找的是所有N皇后问题所有的解,那么此时并不能再此处结束程序。
此时应该清除该行的皇后,从当前放置皇后列数的下一列继续搜索。
部分代码:
publicvoidcalc(){sp=0;
stack[sp++]=newQueen();
while(sp>=0&&sp<=num-1){
Queenqueen=getQueen(sp);
if(null==queen){
booleanflag=true;
while(flag){
--sp;
if(sp<0)break;
if(stack[sp].pos.y==num-1){
}
else{
stack[sp++].pos.y++;
flag=false;
for(intk=0;kif(stack[k].isUnderControl(stack[sp-1].pos)){
flag=true;
break;
}
}
}
}
}
else{
stack[sp++]=queen;
}
}
}
publicQueengetQueen(intx){
booleanflag=true;
inty=0;
while(flag){flag=false;
for(inti=0;iif(stack[i].isUnderControl(newPoint(x,y)))
{flag=true;break;}
}
if(flag&&y<=num-1){y++;}
elseif(y>=num){returnnull;}}
returnnewQueen(newPoint(x,y));
}
判定是否冲突
publicbooleanisUnderControl(Pointpoint){
booleanret=true;
if(point.x!
=pos.x&&point.y!
=pos.y&&Math.abs(point.x-pos.x)!
=Math.abs(point.y-pos.y)&&Math.abs(point.x+point.y)!
=Math.abs(pos.x+pos.y))
{ret=false;}
returnret;
}
运行结果截屏:
输入n=10
输入n=20