考虑使用动态规划方法求解下列问题:0/1背包数据如下表,求:能够放入背包的最有价值的物品集合。
0/1背包数据如下表,求:能够放入背包的最有价值的物品集合。
一辆汽车加满油后可行驶n公里。旅途中有若干个加油站,设计一个有效算法,指出在哪些加油站停靠,使加油次数最少。并证明算法能产生一个最优解。编程任务,对于给定的n和k+1个加油站位置,编程计算出最少加油次数,第0个加油站表示出发地,汽车已加满油,第k个加油站表示目的地,数组x的下标表示加油站号,x[i]对应i加油站到i+1加油站之间距离。
有有7个独立作业{1,2,3,4,5,6,7}由3台机器M1,M2和M3加工处理。各作业所需的处理时间分别为{2,14,4,16,6,5,3}。
- 按处理时间非递增{16,14,6,5,4,3,2};
- 按机器个数建立最小堆,将前三个作业插入最小堆;
- 从堆中取出Min元素;
- 将其加上后序作业的加工时间,插入堆;
- 从第{3}步循环执行,直到所有作业处理完毕。
设G=<V,E>是一个带权图。图中各边的费用(权)为正数。图中的一条周游路线是包括V中的每个顶点在内的一条回路。请画出邻接矩阵和解空间树,并给出回溯法和分支限界法第一个解的搜索路径。
旅行售货员问题
某售货员要到若干城市去推销商品,已知各城市之间的路程(或旅费)。他要选定一条从驻地出发,经过每个城市一次,最后回到驻地的路线,使总的路程(或总旅费)最小。
路线是一个带权图。图中各边的费用(权)为正数。图的一条周游路线是包括V中的每个顶点在内的一条回路。周游路线的费用是这条路线上所有边的费用之和。
旅行售货员问题的解空间可以组织成一棵排列树,从树的根结点到任一叶结点的路径定义了图的一条周游路线。
旅行售货员问题要在图G中找出费用最小的周游路线。
用优先队列来存储活结点。优先队列中每个活结点都存储从根到该活结点的相应路径。
while循环完成排列树内部结点的扩展。对于当前扩展结点,算法分两种情况进行处理:
动态规划算法与分治法的基本思想都是将待求解问题分成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。它们的主要区别是分治法求解时,有些问题会重复计算,而动态规划采用一个表避免子问题的重复计算。
假设某算法在输入规模为n时的计算时间为T(n)=3×2n,在某台计算机上实现并完成该算法的时间为t秒,现另有一台计算机,其运行速度为第一台的64倍,那么在这台新机器上用同一算法在t秒内能输入规模多大的问题?
算法 MINSTOPS
输入:A、B两地间的距离s,A、B两地间的加油站为n,车加满油可行驶的公里数为m,存储各加油站到起点A的距离的数组d[1...n]
输出:从A地到B地的最少加油次数k以及最优解x[1...k],其中x[i]表示第i次加油的加油站号,若问题无解,则输出no solution
d[n+1] = s; //在终点设置虚拟加油站第n+1站
for(int i=1; i<=n; i++){
if(d[i+1]-d[i] > m){
printf("no solution");
return;
}
}
k = 1; //加油次数
x[k] = 1;//在第1站加满油
sl = m; //加过的油可行驶距离A多远的距离
i = 2;
while(sl < s){
// 不能隔一个加油站
if(sl < d[i+1]){
sl = d[i] + m;
k++;
x[k] = i;
}
i++;
}
return k;
已知有7个独立作业{1,2,3,4,5,6,7}由3台机器M1,M2,M3加工处理。各作业所需的处理时间分别为{2,14,4,16,6,5,3}。请给出利用贪心算法实现多机调度。
- 首先将作业按处理时间非递增排序{16,14,6,5,4,3,2}。
- 按机器个数建立最小堆,将前3个作业插入最小堆。
- 从堆中取出MIN元素。
- 将其加上
装载问题算法的改进
while(true){
//检查左儿子
int wt = Ew + w[i];
if(wt <= c){
if(wt > bestw){
bestw = wt;
}
if(i < n){
Q.Add(wt);
}
}
if(Ew+r>bestw && i<n){
Q.Add(Ew);
}
Q.Delete(Ew);
if(Ew == -1){ //检查是否到了同层
if(Q.isEmpty()){
return bestw; //遍历完毕,返回最优值
}
Q.Add(-1); //添加分层标志
Q.Delete(Ew); //取下一扩展结点
}
i++;
r-=w[i];//剩余集装箱重量
}
while(true){
for(int j=1; j<=n; j++){
if((c[E.i][j]<inf) && (E.length+c[E.i][j]<dist[j])){
//顶点i到顶点j可达,且满足控制条件
dist[j] = E.length+c[E.i][j];
prev[j] = E.i;
//加入活结点优先队列
MinHeapNode<Type> N;
N.i = j;
N.length = dist[j];
H.insert(N);
}
}
// 取下一扩展结点
try{
H.DeleteMin(E);
}catch(OutOfBounds){
break;
}
}
N皇后问题
- 依次在棋盘的每一行上摆放一个皇后。
- 每次摆放都要检查当前摆放是否可行,如果当前摆放引发冲突,则将当前皇后摆放在当前行的下一列上,并重新检查冲突。
- 如果当前皇后在当前行的每一列都不可摆放,则回溯到上一个皇后并将其摆放在下一列,并重新检查冲突。
- 如果所有皇后都被摆放成功,则表明成功找到一个解,记录下该解并回溯到上一个皇后。
用贪心算法解决背包问题
首先计算每种物品的单位重量价值vi/wi,然后,依贪心选择策略,将尽可能多的单位重量价值最高的物品装入背包。若将这种物品全部装入背包后,背包内的物品总重量仍未超过C,则选择单位重量价值次高的物品尽可能多地装入背包。依此策略一直进行下去,直到背包装满为止。若最后一个物品不能全部装入时,仅装其一部分使背包装满即可。
循环赛日程表问题——分治法
- 将所有的选手分为两半,n个选手的比赛日可以通过n/2选手设计的比赛日来决定。
- 递归地对选手进行分割,直到只剩下2个选手时,让这2个选手进行比赛就可以了。
什么是算法?
算法是求解某一特定问题的一组有穷规则的集合,它是由若干条指令构成的有穷符号串。
算法的重要特性
- 有穷性:一个算法必须总是在有穷步之后结束,且每一步都在有穷时间内完成。
- 确定性:算法每一条指令必须有确切的含义,不存在二义性——只有一个入口和一个出口。
- 可行性:算法描述的操作可以通过已经实现的基本运算执行有限次来实现。
- 输入:一个算法有零个或多个输入,这些输入取自于某个特定对象的集合。
- 输出:一个算法有一个或多个输出,这些输出同输入有着某些特定关系的量。
算法设计的质量指标
正确性、可读性、健壮性、效率与存储量
算法和程序的区别
程序:一个计算机程序是对一个算法使用某种程序设计语言的具体实现。
任何一种程序设计语言都可以实现一个算法
算法的有穷性意味着不是所有的程序都是算法
算法所需的计算机资源=时间复杂性+空间复杂性
一般情况下只考虑三种情况下的时间性:最坏情况、最好情况和平均情况下的复杂性
上界函数:f(n)=O(g(n)),如果算法用n值不变的同一类数据在某台机器上运行,所运行时间总是小于|g(n)|的一个常数倍。所以g(n)是f(n)的上界函数,f(n)最多增长得像g(n)那样快
当n取值较大时,指数时间算法和多项式时间算法在计算时间上非常悬殊。
用分支限界法解决装载问题
while(true){
int wt = Ew+w[i];//wt为左儿子结点重量
if(wt <= c){//若装载之后不超过船体可承受范围
if(wt > bestw){
bestw = wt;
}
if(i < n){
Q.Add(wt);//把左儿子添加到队列
}
}
if(Ew+r > bestW){
Q.Add(Ew);//可能含有最优解
}
Q.Delete(Ew);//取下一个节点作为扩展结点,并把重量保存在Ew
if(Ew == -1){ //检查是否到了同层结束
if(Q.isEmpty()){
return bestW;
}
Q.Add(-1);//添加分层标志
Q.Delete(Ew);//选取下一扩展结点
i++;
r-=w[i];//剩余集装箱重量
}
}
斜线标识的部分完成的功能:提前更新bestw值。
这样做可以尽早地对右子树进行剪枝,因为算法Maxloading初始时将bestw设置为0,直到搜索到第一个叶结点时才更新bestw。因此在算法搜索到第一个叶结点之前,总有bestW=0,r>0,故Ew+r>bestW总是成立的,此时右子树测试不起作用。
为了使右子树测试尽早生效,应提早更新bestw。又知道算法最终找的最优值是在所求问题的子集树中所有可行结点相应重量的最大值。而结点所相应的重量仅在搜索左子树时增加,因此,可以在算法每一次进入左子树时更新bestw。
分支限界法的搜索策略
在扩展结点处,生成其所有的儿子结点,然后从当前的活结点中选择下一个扩展结点。为了有效地选择下一扩展结点,加速搜素的进程,在每一个活结点处,计算一个函数值(限界),并根据函数值,从当前活结点表中找出一个最有利的结点作为扩展结点,使搜素空间朝着解空间上有最优解的分支推进,以便尽快找出一个最优解。
分支限界法如何剪枝
设r是尚未考虑的剩余物品价值总和,Cv是当前价值,bestV是当前最有价值,若r+CV≤bestv时,可剪去右子树。