1.利用回溯法求解01背包问题。
对于N个物品,在背包的总体积的限制下,有些物品肯定是该舍弃的。
对于每件物品都有取和不取俩种情况。
我们把所有的情况遍历一次,最后取最优的结果。
总体上来就是暴力枚举求最优解。
为了更快地求出最优解,有些情况枚举到一半可以立马舍弃。
如果拿了当前物品,背包总体积大小会溢出,那么这种情况到这一步就会立马放弃接下来的枚举。
代码

java
public class Main {
static int N=7;//物品总的个数
static int maxW=25;//背包容量
static int maxV=0;//当前已经装了的最大价值
static int w[]={0,11,7,9,12,3,10};//体积
static int v[]= {0,10,5,7,9,2,12};//价值
static int vis[]=new int[N];//用于标记哪些物品被选了
public static void main(String[] args) {
dfs(1,0,0,new int[N]);
int ans=0;//记录选择物品的价值
System.out.print("选择的物品是:");
for(int i=1;i<N;i++){
if(vis[i]==1) {
System.out.print(i + ",");
ans+=v[i];
}
}
System.out.println();
System.out.println("此时价值最大,即"+ans);
}
public static void dfs(int idx,int curV,int curW,int curVis[]){//第idx个物品,已经装了curV价值,curW重量,curVis用于标记选了哪些物品
if(idx==N) {//物品搜索完毕
if(curV>maxV){
maxV=curV;//更新已知的可装价值最大值
//vis=curVis;
for(int i=0;i<N;i++){//重新赋值记录选中的物品个数
vis[i]=curVis[i];
}
}
return;
}
curVis[idx]=0;//当前物品不取
dfs(idx+1,curV,curW,curVis);
if(curW+w[idx]<=maxW){//如果当前物品装得下(剪枝)
//这个物品装了
curVis[idx]=1;//标记装入
dfs(idx+1,curV+v[idx],curW+w[idx],curVis);
}
}
}
c++
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=7;//物品总的个数
int maxW=25;//背包容量
int maxV=0;//当前已经装了的最大价值
int w[]={0,11,7,9,12,3,10};//体积
int v[]= {0,10,5,7,9,2,12};//价值
int vis[N];//用于标记哪些物品被选了
void dfs(int idx,int curV,int curW,int curVis[]){//第idx个物品,已经装了curV价值,curW重量,curVis用于标记选了哪些物品
if(idx==N) {//物品搜索完毕
if(curV>maxV){
maxV=curV;//更新已知的可装价值最大值
//vis=curVis;
for(int i=0;i<N;i++){//重新赋值记录选中的物品个数
vis[i]=curVis[i];
}
}
return;
}
curVis[idx]=0;//当前物品不取
dfs(idx+1,curV,curW,curVis);
if(curW+w[idx]<=maxW){//如果当前物品装得下(剪枝)
//这个物品装了
curVis[idx]=1;//标记装入
dfs(idx+1,curV+v[idx],curW+w[idx],curVis);
}
}
int main(){
dfs(1,0,0,new int[N]);
int ans=0;//记录选择物品的价值
cout<<"选择的物品是:"<<endl;
for(int i=1;i<N;i++){
if(vis[i]==1) {
cout<<i << ",";
ans+=v[i];
}
}
cout<<endl;
cout<<"此时价值最大,即"<<ans<<endl;
}
结果
2.利用分支界限法求解旅行售货员问题(只有java代码)
下面代码采用的是一般的dfs解法(为什么会列出这个解法呢,因为我了解
分支界限法
之前只会这个解法,咳咳)
求出的是满足条件的所有解,然后才选取其中一种(dfs)。
而分支界限法是直接求出一个最优解,下面最后介绍(bfs)。
public class Main {
static int N=5;//城市个数
static int g[][]=new int[N][N];
static int ne[]=new int[N];//用于存当前点的下一个城市编号
static int vis[]=new int[N];//用于记录已经走过的城市
static int minV=Integer.MAX_VALUE;//用于存当前最小的路径花费
public static void main(String[] args) {
g[1][2]=30;g[2][1]=30;
g[1][4]=4;g[4][1]=4;
g[1][3]=6;g[3][1]=6;
g[2][3]=5;g[3][2]=5;
g[2][4]=10;g[4][2]=10;
g[3][4]=20;g[4][3]=20;
dfs(1,1,0,new int[N]);
System.out.println("最短回路长为:"+minV);
System.out.print("最短回路为:");
int t=1;//旅游开始城市
System.out.print("1 ");
for(int i=1;i<N-1;i++){
System.out.print(ne[t]+" ");
t=ne[t];
}
}
public static void dfs(int idx,int n,int curV,int curN[]){//第idx层,n号城市,当前路径花费curV,curN当前路径记录
if(idx==N-1){//所有城市走完后,选择最优解
if(curV+g[n][1]<minV){
minV=curV+g[n][1];
for(int i=1;i<N-1;i++){
ne[i]=curN[i];
}
ne[n]=1;//最后一个城市就是起点城市1
}
return;
}
vis[n]=1;//将当前城市标记已经走过
for(int i=1;i<N;i++){//遍历下一个城市
if(vis[i]==0&&g[n][i]!=Integer.MAX_VALUE){//如果下一个城市没有走过并且可以走通
//剪枝
if(curV+g[n][i]<minV){//当前的路径花费小于历史最优花费才有可能产生最优解
//走
vis[i]=1;//标记当前城市已经走
curN[n]=i;//记录当前路径
dfs(idx+1,i,curV+g[n][i],curN);
vis[i]=0;//还原现场
}
}
}
}
}
运行结果
进入正题,分支界限法,这个听着很厉害,有利于大量的需要剪枝的场景(剪枝极快)。
算法的核心是构建可行解树。采用bfs队列或者优先队列实现。
但是代码实现却有点难度
import java.util.Collections;
import java.util.LinkedList;
public class Main{
static int N=5;
static int g[][]=new int[N][N];
public static void main(String[] args){
g[1][2]=30;g[2][1]=30;
g[1][4]=4;g[4][1]=4;
g[1][3]=6;g[3][1]=6;
g[2][3]=5;g[3][2]=5;
g[2][4]=10;g[4][2]=10;
g[3][4]=20;g[4][3]=20;
int v[]={1,2,3,4,5};
BBTSP bbtsp = new BBTSP(g);
int ans = bbtsp.bbTsp(v);
System.out.println("最短回路长为:"+ans);
System.out.print("最短回路为:");
for(int i=1;i<N;i++){
System.out.print(v[i]+" ");
}
}
}
class BBTSP {
int[][] a;//图G的邻接矩阵
public BBTSP(int[][] a) {
this.a = a;
}
public static class HeapNode implements Comparable {
int lcost;//子树费用的下界
int cc;//当前费用
int rcost;//x[s:n-1]中顶点最小出边费用和
int s;//根节点到当前节点的路径为x[0:s]
int[] x;//需要进一步搜索的顶点是x[s+1:n-1]
//构造方法
public HeapNode(int lc, int ccc, int rc, int ss, int[] xx) {
lcost = lc;
cc = ccc;
s = ss;
x = xx;
}
public int compareTo(Object x) {
int xlc = ((HeapNode) x).lcost;
if (lcost < xlc) return -1;
if (lcost == xlc) return 0;
return 1;
}
}
public int bbTsp(int[] v) {//解决旅行售货员问题的优先队列分支界限法
int n = v.length - 1;//节点数
//MinHeap heap=new MinHeap();
LinkedList<HeapNode> heap = new LinkedList<HeapNode>();
//minOut[i]=i的最小出边费用
int[] minOut = new int[n + 1];
int minSum = 0;//最小出边费用和
for (int i = 1; i <= n; i++) {//针对每个节点,找到最小出边
//计算minOut[i]和minSum
int min = Integer.MAX_VALUE;
for (int j = 1; j <= n; j++)
if (a[i][j] < Integer.MAX_VALUE && a[i][j] < min)
min = a[i][j];
if (min == Integer.MAX_VALUE)
return Integer.MAX_VALUE;//没有回路
minOut[i] = min;
minSum += min;
}
//初始化
int[] x = new int[n];
for (int i = 0; i < n; i++)
x[i] = i + 1;
HeapNode enode = new HeapNode(0, 0, minSum, 0, x);
Integer bestc = Integer.MAX_VALUE;
//搜索排列空间树
while (enode != null && enode.s < n - 1) {
//非叶节点
x = enode.x;
if (enode.s == n - 2) {
//当前扩展结点是叶节点的父节点
//再加两条边构成回路
//所构成回路是否优于当前最优解
if (a[x[n - 2]][x[n - 1]] != -1 && a[x[n - 1]][1] != -1 && enode.cc + a[x[n - 2]][x[n - 1]] + a[x[n - 1]][1] < bestc) {
//找到费用更小的回路
bestc = enode.cc + a[x[n - 2]][x[n - 1]] + a[x[n - 1]][1];
enode.cc = bestc;
enode.lcost = bestc;
enode.s++;
heap.add(enode);
Collections.sort(heap);
}
} else {//内部结点
//产生当前扩展结点的儿子结点
for (int i = enode.s + 1; i < n; i++) {
if (a[x[enode.s]][x[i]] != -1) {
//可行儿子结点
int cc = enode.cc + a[x[enode.s]][x[i]];
int rcost = enode.rcost = minOut[x[enode.s]];
int b = cc + rcost;//下界
if (b < bestc) {
//子树可能含有最优解,结点插入最小堆
int[] xx = new int[n];
for (int j = 0; j < n; j++)
xx[j] = x[j];
xx[enode.s + 1] = x[i];
xx[i] = x[enode.s + 1];
HeapNode node = new HeapNode(b, cc, rcost, enode.s + 1, xx);
heap.add(node);
Collections.sort(heap);
}
}
}
}
//取下一个扩展结点
enode = heap.poll();
}
//将最优解复制到v[1...n]
for (int i = 0; i < n; i++)
v[i + 1] = enode.x[i];
return bestc;
}
}
运行结果
总结:承认别人比自己优秀,自己是个普普通通的凡人不难。第二个分支界限法核心代码是摘自网络上别人写的。
我经过一个小时成功大致搞懂了分支界限法的理论,只是理论!!而代码现在却无法成功实现。
另外,有个运行时间,可以看到同样的数据内容,分支界限法用了
,而我自己写的dfs只用了
83ms
(咳咳,给自己个台阶下~)
76ms