回溯法采用DFS+剪枝的方式,通过剪枝删掉不满足条件的树,提高本身作为穷举搜索的效率。
回溯法一般有子集树和排列树两种方式,下面的装载问题和01背包问题属于子集树的范畴。
解空间类型:
子集树:所给的问题是从n个元素的集合S中找出满足某种性质的子集,例如装载问题、0-1背包问题。
排列树:所给的问题是确定n个元素满足某种性质的排列,例如旅行商问题。
回溯法所搜索的解结果,都在树的叶子结点上,一般左树为添加元素(1),右树为不添加元素(0)。
剪枝策略:
左剪枝:当前扩展节点,加入左枝后,不符合约束条件要求,那么就直接剪掉这个子节点及其所有子树,不再继续搜索。
右剪枝:当前扩展节点,加入右枝后,已经无法继续寻求最优解,后续子树也无法存在最优解,或者相比于之前遍历的叶子结点来说,更好的最优解,那么就剪掉这个子节点及其所有子树,不再继续搜索。
简单装载问题:n个集装箱装进一艘载重量为W的轮船,其中集装箱的重量为,不考虑集装箱体积。设计一个算法,要求选择若干集装箱装进轮船,使得不超过载重量W,且给出可行解,和最优解的箱子装载方式。
算法:回溯法,子集树算法。
算法参数表:
num:选择的集装箱数
tw:选择的集装箱重量和
rw:剩余的集装箱重量和
op:表示是否选择该集装箱,op=1则选择,op=0则不选
x[ ]:最优解的op操作符选择,可以用来输出最优的集装箱装载方案
静态变量:
n:物品个数
w[ ]:各种物品的重量
W:轮船总重量限额
minnum:最优解集装箱存放个数
maxw:最优解的总重量
dfs策略:
(1)先判断是否为叶子结点,若是则判断该解是否为最优解,若是最优解则把op数组存入x数组。最优解条件:当前解集装箱存放个数是否小于最优解集装箱存放个数,且选择的集装箱和小于轮船限额
(2)若不是叶子结点则进行扩展操作。
(3)先进行左扩展,若tw+w[i]<=W,即加入这个物品后,总重量不大于轮船限额,则继续扩展,否则剪枝。
(4)再进行右扩展,若tw+rw-w[i]>=W,即不加入这个物品时,所选集装箱总重和未选集装箱总重的和仍然不小于轮船限额,则继续扩展,否则剪枝。
- //回溯法最优装载问题
- public class bestload {
- static int n=5;
- static int minnum=9999;
- static int W=10; //w为每个箱子重量
- static int maxw=0; //存放最优解总重量
- static int w[]={0,5,2,6,4,3};
- public static void main(String []args)
- {
- int rw=0; //rw为剩余集装箱重量和
- for(int num:w)
- rw+=num;
- int op[]=new int[w.length]; //存放一个箱子是否装载
- int x[]=new int[w.length];
- System.out.println("所有可行解:");
- dfs(0,0,rw,op,1,x);
- System.out.println("最少物品的解:");
- for(int i=1;i
- if(x[i]==1)
- System.out.print(w[i]+" ");
- }
- public static void dfs(int num,int tw,int rw,int op[],int i,int x[])
- {
- if(i>n)
- {
- if(tw<=W&&num
- {
- maxw=tw;
- minnum=num;
- for(int j=1;j<=n;j++)
- x[j]=op[j];
- }
- for(int j=1;j<=n;j++)
- if(op[j]==1)
- System.out.print(w[j]+" ");
- System.out.println(" ");
- }
- else
- {
- op[i]=1; //优先左分枝
- if(tw+w[i]<=W) //左分枝条件
- dfs(num+1,tw+w[i],rw-w[i],op,i+1,x);
- op[i]=0;
- if(tw+rw-w[i]>=W)
- dfs(num,tw,rw-w[i],op,i+1,x);
- }
- }
- }
子集树如下:(右树部分省略)
三、复杂装载问题
1、算法设计
复杂装载问题:n个集装箱要装进两艘载重量分别为c1和c2的轮船,其中集装箱的重量为,不考虑集装箱体积,设计一个算法,使得这些集装箱装上这两艘轮船,如果不能装载则返回load false。
算法:回溯法,子集树算法,优先第一个轮船装载,判断第二个轮船是否能够装载剩余集装箱。
dfs策略:
(1)先判断是否为叶子结点,若是则判断该解是否为最优解,若是最优解则把op数组存入x数组。最优解条件:当前选择的集装箱和是否大于第一个轮船的最优集装箱装载重量,且选择的集装箱和小于第一个轮船限额
(2)若不是叶子结点则进行扩展操作。
(3)先进行左扩展,若tw+w[i]<=c1,即加入这个物品后,总重量不大于第一个轮船限额,则继续扩展,否则剪枝。
(4)再进行右扩展,若tw+rw-w[i]>=maxw,即不加入这个物品时,所选集装箱总重和未选集装箱总重的和仍然不小于第一个轮船的最优装载重量和,则继续扩展,否则剪枝。
2、代码
- //回溯法复杂装载问题
- public class complexbestload {
- static int n=3;
- static int minnum=9999;
- static int c1=50; //w为每个箱子重量
- static int c2=50;
- static int maxw=0; //存放最优解总重量
- static int w[]={0,10,40,40};
- public static void main(String []args)
- {
- int rw=0; //rw为剩余集装箱重量和
- for(int num:w)
- rw+=num;
- int op[]=new int[w.length]; //存放一个箱子是否装载
- int x[]=new int[w.length];
- dfs(0,0,rw,op,1,x);
- judge(x);
- }
- public static void dfs(int num,int tw,int rw,int op[],int i,int x[])
- {
- if(i>n) //dfs搜索到最后一层,所有的货物都试了一遍,则输出最优解
- {
- if(tw<=c1&&tw>maxw)
- {
- maxw=tw;
- for(int j=1;j<=n;j++)
- x[j]=op[j];
- }
- }
- else
- {
- op[i]=1; //优先左分枝
- if(tw+w[i]<=c1) //左分枝条件
- dfs(num+1,tw+w[i],rw-w[i],op,i+1,x);
- op[i]=0;
- if(tw+rw-w[i]>=maxw)
- dfs(num,tw,rw-w[i],op,i+1,x);
- }
- }
-
- public static void judge(int x[])
- {
- int total=0;
- for(int i=1;i
- if(x[i]==0)
- total+=w[i];
- if(total<=c2)
- {
- System.out.print("c1:");
- for(int i=1;i
- if(x[i]==1)
- System.out.print(w[i]+" ");
- System.out.println();
- System.out.print("c2:");
- for(int i=1;i
- if(x[i]==0)
- System.out.print(w[i]+" ");
- }
- else
- System.out.println("load false");
- }
- }
四、0-1背包问题
1、算法设计
0-1背包问题:给定n个物品和一个背包,物品重量为,价值为,背包容量为c,请设计一种算法,放入若干物品后,背包中物品总价值最大。
算法:回溯法,子集树算法。左剪枝结合贪心算法。
初始化:首先对w数组和v数组进行重新排序,按照a数组,即单位重量价值最高的优先。下面代码使用快速排序。
dfs策略:
(1)先判断是否为叶子结点,若是则判断该解是否为最优解,若是最优解则把op数组存入x数组,最优解maxv替换为当前所选物品的总重量。最优解条件:当前所选物品总重量不大于背包限额,且所选物品的价值大于当前最优解maxv。
(2)若不是叶子结点则进行扩展操作。
(3)先进行左扩展,计算左分枝后,使用贪心算法计算已选物品与若干剩余物品,在背包未超重情况下的最大价值。若tw+w[i]<=W当前已选物品的总重量加上新物品仍然不大于背包限额,则进行扩展,否则剪枝。
(4)再进行右扩展,若tw+rw-w[i]>=maxw不加入这个物品时,所选物品总重和剩余物品总重的和仍然不小于背包限额,且greed>maxv该最大价值已经大于当前已知最优解,则继续扩展,否则剪枝。(还是遵循书上右剪枝计算贪心算法,其实计算左剪枝进行贪心一样可以推理)
2、代码
- //回溯法解决0-1背包问题
- public class backage {
- static int W=10;
- static int maxv=0; //存放最优解价值
- public static void main(String[] args)
- {
- double w[]={0,2,1,3,4,6};
- double v[]={0,3,2,4,5,8};
- int n=w.length-1;
- double rw=0;
- double a[]=new double[w.length];
- int op[]=new int[w.length]; //存放当前叶子结点的解
- int x[]=new int[w.length]; //存放最优解
- for(int i=1;i
- a[i]=v[i]/w[i];
- for(int i=1;i
- rw+=w[i];
- //快排
- quickSort(a, w, v, 1, n);
- //回溯
- dfs(w,v,0,0,rw,op,x,1);
- int total=0;
- for(int j=1;j
- {
- if(x[j]==1)
- {
- System.out.print(w[j]+" ");
- total+=v[j];
- }
- }
- System.out.println();
- System.out.println("Max value:"+total);
-
-
- }
- //快速排序
- public static void quickSort(double arr[],double w[],double v[],int low,int high)
- {
- int i=low;
- int j=high;
- int t;
- if(low>high)
- return;
- double tmp=arr[low];
- while(i
- {
- while(i
=arr[j]){j--;}; //注意由于降序排列,所以为tmp>=arr[j] - while(i
//同理,tmp<=arr[i] - if(i
- {
- swap(arr,i,j);
- swap(w, i, j);
- swap(v,i,j);
- }
- }
- swap(arr,low,i);
- swap(w,low,i);
- swap(v,low,i);
- quickSort(arr, w,v,low, j-1);
- quickSort(arr,w,v,j+1,high);
- }
- //交换同一数组的两个值
- public static void swap(double arr[],int i,int j)
- {
- double t;
- t=arr[i];
- arr[i]=arr[j];
- arr[j]=t;
- }
- //回溯法
- public static void dfs(double w[],double v[],int num,double tw, double rw, int op[],int x[],int i)
- {
- if(i>=w.length-1)
- {
- if(tw<=W)
- {
- int tot=0;
- for(int j=1;j
- {
- if(op[j]==1)
- {
- tot+=v[j];
- }
- }
- if(tot>maxv)
- {
- for(int j=1;j
- x[j]=op[j];
- maxv=tot;
- }
- }
- }
- else
- {
- op[i]=1;
- if(tw+w[i]<=W)
- dfs(w,v,num+1,tw+w[i],rw-w[i],op,x,i+1);
- op[i]=0;
- double greed=greedy(op, v, w, i);
- if(tw+rw-w[i]>=W&&greed>maxv)
- dfs(w,v,num,tw,rw-w[i],op,x,i+1);
- }
- }
- //右剪枝贪心计算
- public static double greedy(int op[],double v[],double w[],int i)
- {
- double totv=0;
- double totw=0;
- for(int j=1;j
- {
- if(op[j]==1)
- {
- totv+=v[j];
- totw+=w[j];
- }
- }
- for(int j=i+1;j
- {
- if(totw+w[j]
- {
- totw+=w[j];
- totv+=v[j];
- }
- else
- {
- totv+=(W-totw)*v[j]/w[j];
- totw=W;
- break;
- }
- }
- return totv;
- }
- }
-
相关阅读:
【学习笔记】数据一致性分发
C/C++|智能指针的shared_from_this和enable_shared_from_this
LabVIEW 应用程序视窗始终置于顶层
【笔试题】【day21】
攻防演练中攻击队需要的安全技能第二篇
web前端零基础入门2
npm install 安装包时,常用的-S 、-D 、-g 有什么区别?
基于流谱理论的SSL/TLS协议攻击检测方法
IMX6ULL移植篇-编译单个指定的设备树文件
线性代数的本质(五)——矩阵的运算
-
原文地址:https://blog.csdn.net/m0_60177079/article/details/134091178