回溯法与分支限界法(以0-1背包问题为例)
回溯法
总体上概述来讲:
回溯法:
原则上依据深度优先遍历的寻找方式,每次都是一直深入寻找的过程,只不过在这个深入的过程中,我们限定一定的上界条件,可以实现事先判断该条深入路线是否可以得到目前情况下的一个比我们手上有的最优值要优的或者是压根这条路得到的值明显离我们的期望值差的很远的,可以直接舍弃。舍弃后便会发生回溯,重新选择一条路继续探索直到到达叶子节点。(这里提一句,很多人不刚开始理解不了这个树是怎么来的,实际上就是一颗全排列树,针对每一层,这一层有多少个选择就有多少的分支。0-1问题就只有两种选择:选或者是不选则就是二叉树的结构。)
这种方式适用于寻找全部的解空间。
分支限界法
分支限界法和回溯法相似,寻找方式是依据广度优先原则。
利用分支限界法和回溯法,事实上可以设计更优的全局最优法。
只不过需要注意这样一种方式,适用于寻找问题的最优解,时间复杂度就会很低。
数据结构
简单概述上来说,只用记住:
回溯法用到栈;栈的操作比较好写。
分支限界法(全局最优策略)需要用到根堆。堆的操作需要记住插入和删除。
附代码
回溯法
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#define M 100
#define num 10
int n;//表示物品的个数
double W;//表示最大的容量
double w[num+1];//表示num数量个物品的重量
int x[num+1];//表示一个解向量——是否放入背包
double v[num+1];//表示每个物品的价值
double V;//当前放入背包的总价值和
double best;//当前最优值
double wx;//表示当前已经放入的容量
int bestx[num+1];//记录最优解
double boundary(int i)//计算上界的函数——上界函数具体内容自己定义
{
int temp=0;
while(i<=num)
{
temp+=v[i];
i++;
}
return V+temp;
}
void backtrack(int t)//回溯函数
{
if(t==num)//深度达到叶子节点
{
if(V>best)//如果当前算入的V比best要大
{
for(int j=1;j<=num;j++)
{
bestx[j]=x[j];//最优向量更新为当前解向量
}
best=V;//best更新为当前V值
}
return ;
}
if((boundary(t)>best)&&wx+w[t]<=W)//如果当前节点后续可以预估的价值比已经得到的最大价值要高则可以继续向后走,如果加上当前w[t]要小于等于最大容量则可以装
{
x[t]=1;
wx=wx+w[t];
V=V+v[t];
//此时,相当于向下深入了一层,这个时候深度就是t+1,
backtrack(t+1);
wx-=w[t];
V-=v[t];
}
if((boundary(t)>best))
{
x[t]=0;
wx=wx;
V=V;
backtrack(t+1);
}
}
void knapsack(double W,int n)
{
wx=0;
V=0;
best=0;
backtrack(1);
printf("放入背包的最大价值为:%lf",best);
printf("\n放进背包的物品的序号:");
for(int i=1;i<=n;i++)
{
if(bestx[i]==1)
printf("%d ",i);
}
}
int main()
{
printf("请输入物品的个数:\n");
scanf("%d",&n);
printf("请输入背包最大容量:\n");
scanf("%lf",&W);
printf("请输入每个物品的重量w和价值v:\n");
for(int i=1;i<=n;i++)
scanf("%lf %lf",&w[i],&v[i]);
knapsack(W,n);//背包问题回溯算法
return 0;
}
分支限界法
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
int n;//物品个数
int W;//背包容量
int *v;//每个物品对应的价值
int *w;//每个物品对应的重量
#define N 11
typedef struct nodeH
{
int w;//当前重量
int v;//当前价值
int level;//层级
int bound;//上界
int route[N];//路径
}Heapnode;//堆结点
Heapnode heap[N];
int length=0;//记录目前堆的长度
void insertHeap(Heapnode heap[],Heapnode x)
{
int i;
for(i=length+1;i>1;i=i/2)
{
if(x.bound<heap[i/2].bound)
{
break;
}
else
{
heap[i]=heap[i/2];
}
}
heap[i]=x;
length=length+1;
}
void deleteHeap(Heapnode heap[])//根节点删除
{
int i;
Heapnode temp=heap[length];
heap[1]=heap[length];
heap[length].bound=0;
for(i=2;i<length;i=i*2)
{
if(heap[i].bound<heap[i+1].bound)
i++;
if(temp.bound>heap[i].bound)
{
break;
}
heap[i/2]=heap[i];
}
i=i/2;
heap[i]=temp;
length=length-1;
}
int maxup(Heapnode heap[])
{
int max=0;
for(int i=1;i<=length;i++)
{
if(max<heap[i].bound)
max=heap[i].bound;
}
return max;
}
void pack(int n,int *w,int *v,int W)
{
Heapnode root;
root.v=0;
root.w=0;
root.level=0;
root.bound=W*(v[1]/w[1]);
for(int i=1;i<=n;i++)
{
root.route[i]=0;
}
insertHeap(heap,root);
while(length>0)
{
Heapnode temp;
temp=heap[1];
int level=temp.level;
deleteHeap(heap);
if(level==n&&temp.v>=maxup(heap))
{
for(int i=1;i<=n;i++)
printf("%d ",temp.route[i]);
printf("\n %d ",temp.v);
exit(-1);
}
else
{
int j;
for(j=0;j<=1;j++)
{
if(temp.w+j*w[level+1]<=W)
{
Heapnode nodeh;
nodeh.w=temp.w+j*w[level+1];
nodeh.v=temp.v+j*v[level+1];
if(level<=n-1)
{
nodeh.bound=nodeh.v+(W-nodeh.w)*(v[level+1]/w[level+1]);
}
else
{
nodeh.bound=nodeh.v;
}
for(int i=1;i<=level;i++)
nodeh.route[i]=temp.route[i];
nodeh.route[level+1]=j;
nodeh.level=level+1;
insertHeap(heap,nodeh);
}
}
}
}
}
int main()
{
printf("输入物品数量n:");
scanf("%d",&n);
v=(int *)malloc(sizeof(int)*(n+1));
w=(int *)malloc(sizeof(int)*(n+1));
printf("输入背包的容量:");
scanf("%d",&W);
printf("输入对应的重量和价值:\n");
for(int i=1;i<=n;i++)
{
scanf("%d%d",&w[i],&v[i]);
}
pack(n,w,v,W);
return 0;
}