【贪心法】最优装载问题/(0-1)背包问题,最小延迟调度问题,硬币贪心策略

本文介绍了最优装载问题和最小延迟调度问题。最优装载问题通过贪心策略解决,每次选择最轻的物品装船,直到无法再装。最小延迟调度问题中,要求服务开始时间最小化延迟,同样可以采用贪心策略,按照截至时间从小到大安排服务。此外,还讨论了贪心法在某些情况下可能无法得到最优解的情况,并举例说明了找零钱问题的动态规划和贪心解法。

最优装载问题/0-1背包问题,最小延迟调度问题,硬币贪心策略

最优装载问题

介绍

最优装载问题:某艘船的载重量为C,每件物品的重量为 w i ≤ C w_i≤C wiC,要将尽量多的物品装入到船上,问如何选择而使能装的上船的集装箱个数最多?
x i = 1 x_i=1 xi=1表示第i个集装箱可以装上船,否则 x i = 0 x_i=0 xi=0,则问题可以描述为:

目标函数 m a x ∑ i = 1 n x i max\displaystyle∑_{i=1}^nx_i maxi=1nxi
约束条件 ∑ i = 1 n w i x i ≤ C \displaystyle∑_{i=1}^nw_ix_i≤C i=1nwixiC
x i = 0 , 1 x_i=0,1 xi=0,1 i = 1 , 2 , . . , n i=1,2,..,n i=1,2,..,n

分析:这是一个整数规划问题,也是0-1背包问题的特殊情况。对于0-1背包问题可以使用动态规划算法求解,但是对于这个问题有更好的算法:贪心策略。贪心法:每次都选择最轻的,然后再从剩下的n-1件物品中选择最轻的。
算法策略:把n件物品 从小到大排序,然后根据贪心策略尽可能多的选出前i个物品,直到不能装为止。

代码

注意:输入的重量序列已经是递增的序列。

/*
 * @Description: 
 * @Author: dive668
 * @Date: 2021-04-30 22:12:23
 * @LastEditTime: 2021-04-30 22:21:55
 */
#include <iostream>
#include <queue>
using namespace std;

//输入:集装箱集合N={1,2...,n},集装箱i的重量wi,i=1,2..n,注意wi已经按升序排好了
//输出:集装箱集合I包含于N
int main()
{
    int n;
    cout << "集装箱个数:";
    cin >> n;
    int C;
    cout << "集装箱限重:";
    cin >> C;
    int *w = new int[n + 1];//重量数组
    w[0] = 0;
    int i;
    for (i = 1; i <= n;++i)
    {
        cout << "输入第" << i << "个集装箱的重量";
        cin >> w[i];
    }
    queue<int> q;
    q.push(1);//先把第一个最轻的入队列
    int weight = w[1];//先把第一个最轻的加入到当前重量weight中
    for (i = 2; i <= n;++i)
    {
        if(weight+w[i]<=C)
        {
            weight += w[i];//增加当前重量
            q.push(i);//将这个第i件货物入队列
        }
    }
    while(!q.empty())//将装载的物品序列顺序输出
    {
        cout << q.front();
        q.pop();
    }
    return 0;
}

最小延迟调度问题–客户服务问题

介绍

给定等待服务的客户的集合 A = 1 , 2 , . . . n A={1,2,...n} A=1,2,...n,预计对客户 i i i的服务时间是 t i t_i ti,该客户希望完成的时间是 d i d_i di,即 T = < t 1 , t 2 , . . . , t n > T=<t_1,t_2,...,t_n> T=<t1,t2,...,tn> D = < d 1 , d 2 , . . , d n > D=<d_1,d_2,..,d_n> D=<d1,d2,..,dn>。如果对客户i的服务在 d i d_i di之前结束,那么对客户i的服务没有延迟;如果在di之后结束,那么这个服务就被延迟了,延迟的时间等于该服务结束时间减去 d i d_i di,假设 t i , d i t_i,d_i tidi都是正整数,一个调度是函数 f : A → B f:A→B f:AB其中 f ( i ) f(i) f(i)是对客户i的服务开始时间,要求所有区间 ( f ( i ) , f ( i ) + t i ) (f(i),f(i)+t_i) (f(i),f(i)+ti)互不重叠。一个调度f的最大延迟是所有客户延迟时间的最大值。
例如:

客户集: A = 1 , 2 , 3 , 4 , 5 A={1,2,3,4,5} A=1,2,3,4,5
服 务 时 间 集 : T = < 5 , 8 , 4 , 10 , 3 > 服务时间集:T=<5,8,4,10,3> T=<5,8,4,10,3>
预 期 服 务 完 成 时 间 : D = < 10 , 12 , 15 , 11 , 20 > 预期服务完成时间:D=<10,12,15,11,20> D=<10,12,15,11,20>
那么对于调度:
f 1 : 1 , 2 , 3 , 4 , 5 → N f_1:{1,2,3,4,5}→N f1:1,2,3,4,5N
f 1 ( 1 ) = 0 , f 1 ( 2 ) = 5 , f 1 ( 3 ) = 13 , f 1 ( 4 ) = 17 , f 1 ( 5 ) = 27 f_1(1)=0,f_1(2)=5,f_1(3)=13,f_1(4)=17,f_1(5)=27 f1(1)=0,f1(2)=5,f1(3)=13,f1(4)=17,f1(5)=27
客户1,2,3,4,5的延迟分别是0,1,2,16,10;最大为max:16。
延迟时间的计算:第二件事情的开始时间减去第一件事情的要求截止时间,如果为正值,那么就是延迟了,如果为负值,就是没延迟。

调度问题的建模:
给定 A = < 1 , 2 , . . . , n > , T = < t 1 , t 2 , . . . , t n > , D = < d 1 , d 2 , . . . , d n > A=<1,2,...,n>,T=<t_1,t_2,...,t_n>,D=<d_1,d_2,...,d_n> A=<1,2,...,n>T=<t1,t2,...,tn>D=<d1,d2,...,dn>,求具有最小延迟的调度。
即求函数f:A→N使得
min ⁡ f { max ⁡ i ∈ A { f ( i ) + t i − d i } } \displaystyle\min_f\{\displaystyle\max_{i∈A}\{f(i)+t_i-d_i\}\} fmin{iAmax{f(i)+tidi}}
∨ i , j ∈ A , i ≠ j , f ( i ) + t i ≤ f ( j ) 或 者 f ( j ) + t j ≤ f ( i ) ∨i,j∈A,i≠j,f(i)+t_i≤f(j)或者f(j)+t_j≤f(i) i,jAi=jf(i)+tif(j)f(j)+tjf(i)
考虑贪心算法:按照截至时间 d i d_i di从小到大选择任务,在安排时不留空闲时间。

/*
 * @Description: 
 * @Author: dive668
 * @Date: 2021-04-30 23:40:37
 * @LastEditTime: 2021-05-01 00:08:01
 */
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

//输入A,T,D
//输出f
struct customer
{
    int a;//客户序号
    int t;//客户服务时间
    int d;//客户要求截至时间
};
bool cmp(customer c1,customer c2)
{
    return c1.d <= c2.d;
}
int main()
{
    //排序A使得d1<=d2<=d3...<=dn
    int n;
    cout << "input the number:";
    cin >> n;
    vector<customer> c;
    vector<customer>::iterator p;
    customer t;//临时结构体变量,用于压入容器
    int i;
    for (i = 0; i < n;++i)
    {
        cout << "客户号:";
        cin >> t.a;
        cout << "服务时间:";
        cin >> t.t;
        cout << "要求截至时间:";
        cin >> t.d;
        c.push_back(t);
    }
    sort(c.begin(), c.end(), cmp);
    int f[n+1];
    f[0] = 0;
    i = 1;
    for (p=c.begin();p!=c.end();++p)
    {
        f[i] = f[i - 1] + p->t;
        ++i;
    }
    cout << "最终截至时间"<<f[n];
}

经过证明,没有逆序,没有空闲时间的调度具有相同的最大延迟。

对贪心法得不到最优解的情况的处理

方法主要有两个:
一是对输入做分析,指出输入在满足哪些条件下贪心法是正确的,而且判定这些条件的时间比较少,至多不超过算法本身的运行时间。
二是分析贪心法的误差,确定它对所有的输入实例得到的解(近似解)与最优解的误差至少有多大。

典型应用实在找零钱问题上,此处仅给出题干:

设有n种硬币,其重量分别为 w 1 , w 2 , . . . , w n w_1,w_2,...,w_n w1,w2,...,wn,币值分别为 v 1 = 1 , v 2 , . . . , v n v_1=1,v_2,...,v_n v1=1,v2,...,vn。现在需要用这些硬币付款的总钱数是Y,问:如何选择这些硬币而使得付钱的硬币总重最轻?
问题建模:
不妨设币值和钱数都为正整数,令选用第i种硬币的数目是 x i x_i xi,那么这个那么这个问题可以建模如下:

m i n { ∑ i = 1 n w i x i } min\{\displaystyle∑_{i=1}^nw_ix_i\} min{i=1nwixi}
∑ i = 1 n v i x i = Y \displaystyle∑_{i=1}^nv_ix_i=Y i=1nvixi=Y
x i ∈ N , i = 1 , 2... n x_i∈N,i=1,2...n xiNi=12...n

可以用动态规划算法求解:采用背包问题的第二种形式的解法。
递推方程:
F k + 1 ( y ) = min ⁡ 0 ⩽ x k + 1 ⩽ y w k ​ ( F k ( y − v k + 1 ∗ x k + 1 ) + w k + 1 ∗ x k + 1 ) , k = 1 , 2 , . . . , n − 1 , y = 0 , 1 , . . . Y F_{k+1}(y)=\displaystyle\min_{0⩽x_{k+1}⩽\frac{y}{w_k}​}({ F_k(y−v_{k+1}*x_{k+1}) + w_{k+1} *x_{k+1}}),k=1,2,...,n-1,y=0,1,...Y Fk+1(y)=0xk+1wkymin(Fk(yvk+1xk+1)+wk+1xk+1)k=1,2,...,n1,y=0,1,...Y
起始条件:
F 1 ( y ) = w 1 [ y / v 1 ] = w 1 y F_1(y)=w_1[y/v_1]=w_1y F1(y)=w1[y/v1]=w1y y=0,1,…,Y
时间复杂度为 O ( n 2 ) O(n^2) O(n2)
贪心法解:先选"单位价值重量最小的硬币"。
不妨设:
w 1 v 1 ​ ≥ w 2 v 2 ≥ . . . ≥ w n v n \frac{w_1}{v_1}​≥\frac{w_2}{v_2}≥...≥\frac{w_n}{v_n} v1w1v2w2...vnwn
根据贪心法进行选择时,应该尽可能使用标号大的硬币
设允许使用前k种零钱,总钱数为y时贪心法得到的总重为 G k ( y ) G_k(y) Gk(y),则有如下递推公式:
G k ( y ) = w k [ y v k ] + G k − 1 ( y m o d v k ) G_k(y)=w_k[\frac{y}{v_k}]+G_{k-1}(y mod v_k) Gk(y)=wk[vky]+Gk1(ymodvk)k=2,3,…,n
G 1 ( y ) = w 1 [ y v 1 ] = w 1 y G_1(y)=w_1[\frac{y}{v_1}]=w_1y G1(y)=w1[v1y]=w1y y=0,1…Y

上述递推公式的含义是:可选硬币是标号为1,2,…,k的硬币时,第k种硬币的个数至多可以用 [ y v k ] [\frac{y}{v_k}] [vky]个,按照上面的贪心策略,要尽可能使用第k种硬币,因此 x k = [ y v k ] x_k=[\frac{y}{v_k}] xk=[vky]。这些硬币的重量是 w k [ y v k ] w_k[\frac{y}{v_k}] wk[vky]。剩下的钱数时 y − v k [ y v k ] y-v_k[\frac{y}{v_k}] yvk[vky],这是在尽量使用标号为k的投硬币付款所剩的不足 v k v_k vk的余款。即 y m o d v k y mod v_k ymodvk。这部分钱只能用更小币值的零钱付款,也就是说只能使用标号为1,2,…,k-1的硬币,于是得到一个子问题。使用贪心法继续求解这个子问题,得到总重量是 G k − 1 ( y m o d v k ) G_{k-1}(ymodv_k) Gk1(ymodvk)。把这个重量与标号为k的硬币重量加起来就是最终的总重量。由于1号硬币的价值是1,而要付款的钱数是y,于是需要y枚硬币,因此硬币重量就是 w 1 y w_1y w1y。这就是递推关系的初值 G 1 ( y ) G_1(y) G1(y)
使用上述递推公式,可以从最大的硬币币值开始,找到第一个小于或等于y的 v k v_k vk,计算 x k = [ y v k ] x_k=[\frac{y}{v_k}] xk=[vky]。剩下的钱数 y 1 = y − v k [ y v k ] y1=y-v_k[\frac{y}{v_k}] y1=yvk[vky]。与上一步类似,继续使用贪心法对 y 1 y_1 y1进行类似处理,…,直到剩余钱数 y t y_t yt小于 v 2 v_2 v2或者 y t y_t yt=0为止,令 x 1 = y t x_1=y_t x1=yt,算法结束。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值