⎨ ⎧ i = 1 ∑ n w i x i ≤ W x i = 0 , 1 , i = 1 , 2 , ... , n 输入:w , W w,W w , W (此处w w w 为由各集装箱重量组成的数组) 输出:max x ∑ i = 1 n x i \max\limits_{\boldsymbol x}\sum\limits_{i=1}^{n}x_i x max i = 1 ∑ n x i 算法分析 选择“轻者优先 ”的贪心策略,直接将重量从小到大进行排序,依次放入轮船,直到不能放为止。伪代码如下:
Algorithm: best-loading ( w , W ) 1. s u m ← 0 2. r e s u l t ← 0 3. sort ( w ) / / 升序排列 w [ 1.. n ] 4. f o r i = 1 t o length ( w ) d o 5. i f s u m + w [ i ] ≤ W t h e n 6. s u m ← s u m + w [ i ] 7. r e s u l t ← r e s u l t + 1 8. e l s e exit 9. r e t u r n r e s u l t \begin{aligned} &\text{Algorithm: }\;\text{best-loading}(w,W)\\\\ 1.&\;sum\leftarrow0\\ 2.&\;result\leftarrow0\\ 3.&\;\text{sort}(w)\quad//\text{升序排列}w[1..n]\\ 4.&\;\mathbf{for}\;i\;=1\;\mathbf{to}\;\text{length}(w)\;\mathbf{do}\\ 5.&\;\qquad \mathbf{if}\;sum+w[i]\leq W\;\mathbf{then}\\ 6.&\;\qquad\qquad sum\leftarrow sum+w[i]\\ 7.&\;\qquad\qquad result\leftarrow result+1\\ 8.&\;\qquad \mathbf{else}\;\text{exit}\\ 9.&\;\mathbf{return}\;result \end{aligned} 1. 2. 3. 4. 5. 6. 7. 8. 9. Algorithm: best-loading ( w , W ) s u m ← 0 res u lt ← 0 sort ( w ) // 升序排列 w [ 1.. n ] for i = 1 to length ( w ) do if s u m + w [ i ] ≤ W then s u m ← s u m + w [ i ] res u lt ← res u lt + 1 else exit return res u lt
注:该伪代码并未给出如何反推出选取的集装箱的编号,事实上这只需在对w w w 进行排序时记住排序之后的下标即可.
时间复杂度为O ( n log n ) + O ( n ) = O ( n log n ) O(n\log n)+O(n)=O(n\log n) O ( n log n ) + O ( n ) = O ( n log n ) ,空间复杂度为O ( 1 ) O(1) O ( 1 ) .
算法实现 采用 c++
进行编程.
1 2 3 4 5 6 7 8 9 10 11 12 int best_loading (vector<int > &w, int W) { int sum = 0 ; int result = 0 ; for (int i = 0 ; i < w.size (); i++){ if (sum + w[i] <= W){ sum += w[i]; result++; }else break ; } return result; }
测试样例如下所示 :
1 2 3 输入:[7 5 8 1 5 3 9],16 输出:4
正确性证明 利用数学归纳法.
假设 对于规模为n n n 的问题,贪心法都能得到最优解。
若输入:N = { 1 , 2 , . . . , n , n + 1 } N=\{1,2,...,n,n+1\} N = { 1 , 2 , ... , n , n + 1 } . 其中,w 1 ≤ w 2 ≤ ⋯ ≤ w n ≤ w n + 1 w_1\leq w_2\leq \cdots\leq w_n\leq w_{n+1} w 1 ≤ w 2 ≤ ⋯ ≤ w n ≤ w n + 1 . 则根据假设,对于N ′ = N − { 1 } , W ′ = W − w 1 N'=N-\{1\},W'=W-w_1 N ′ = N − { 1 } , W ′ = W − w 1 这一问题,贪心法可以得到最优解,记为X ′ X' X ′ .
注:此处对于解的定义是包含装入轮船的集装箱编号. 如X ′ = { 1 , 2 , 3 } X'=\{1,2,3\} X ′ = { 1 , 2 , 3 } 表示标号为 1、2、3的集装箱均放入轮船,此时得到最优解。
这里解的结构定义区别于上面用 0-1向量的定义,这也是为了更好地证明问题,并不影响解的正确性.
此时算法对( N , W ) (N,W) ( N , W ) 的这个问题,给出的算法解为X = X ′ ∪ { 1 } X=X'\cup \{1\} X = X ′ ∪ { 1 } . 于是有X X X 是该问题的最优解,否则存在最优解X ∗ X^* X ∗ ,使得∣ X ∗ ∣ > ∣ X ∣ |X^*|\gt |X| ∣ X ∗ ∣ > ∣ X ∣ 且1 ∉ X ∗ 1\notin X^* 1 ∈ / X ∗ . 那么,也有∣ X ∗ − { 1 } ∣ > ∣ X − { 1 } ∣ = ∣ X ′ ∣ |X^*-\{1\}|\gt|X-\{1\}|=|X'| ∣ X ∗ − { 1 } ∣ > ∣ X − { 1 } ∣ = ∣ X ′ ∣ ,而这与X ′ X' X ′ 是问题( N ′ , W ′ ) (N',W') ( N ′ , W ′ ) 的最优解矛盾。
最小延迟调度| Scheduling to Minimize Lateness 给定一系列工作( Jobs )J = [ J 1 , J 2 , . . . , J n ] \boldsymbol J=[J_1,J_2,...,J_n] J = [ J 1 , J 2 , ... , J n ] ,及其对应的截止时间( deadline )d i d_i d i 和完成这项工作所需的执行时间t i t_i t i .
如果第i i i 个工作的开始执行时间记为s i s_i s i ,则它的完成( finish )时间f i = s i + t i f_i=s_i+t_i f i = s i + t i . 所以一项工作的延迟 (Lateness ) 时间就是L i = max { 0 , f i − d i } L_i=\max\{0,f_i-d_i\} L i = max { 0 , f i − d i } .
本问题要求找出这样一个调度( Schedule )T : J → J ′ T:\boldsymbol J\to \boldsymbol J' T : J → J ′ 使得延迟最大的那项工作的延迟能在调度的作用下达到最小。即:
min T { max 1 ≤ i ≤ n L i } s . t . s i + t i ≤ s i + 1 \begin{aligned} &\min_{T}\{\max_{1\leq i\leq n} L_i\}\\\\ s.t.&\quad s_i+t_i\leq s_{i+1} \end{aligned} s . t . T min { 1 ≤ i ≤ n max L i } s i + t i ≤ s i + 1
输入:J , d , t \boldsymbol{J,d,t} J , d , t ,分别表示由工作编号、对应截止时间、对应执行时间组成的数组 输出:最迟延迟时间 算法分析 选择 “截止时间早的任务优先” 的贪心策略,选取这样的映射T : J → J ′ T:J\to J' T : J → J ′ 使得新的调度中,d 1 ≤ d 2 ≤ ⋯ ≤ d n d_1\leq d_2\leq \cdots\leq d_n d 1 ≤ d 2 ≤ ⋯ ≤ d n . 并且要求上一项工作完成后立刻处理下一项工作.
从而直接对这样的调度进行最迟延迟时间的计算,结果即是最优解,伪代码如下:
Algorithm: minimize-lateness ( J , d , t ) J [ 1.. n ] / / J o b s d [ 1.. n ] / / D e a d l i n e t [ 1.. n ] / / P r o c e s s i n g t i m e 1. sort J , t , d order by d so that d 1 ≤ d 2 ≤ ⋯ ≤ d n 2. s ← 0 3. e n d t i m e ← 0 4. f o r i = 1 t o length ( J ) d o 5. s ← s + t [ i ] 6. e n d t i m e ← max ( e n d t i m e , s − d [ i ] ) 7. r e t u r n e n d t i m e \begin{aligned} &\text{Algorithm: }\;\text{minimize-lateness}(J,d,t)\\\\ &J[1..n]\quad//Jobs\\ &d[1..n]\quad//Deadline\\ &t[1..n]\quad//Processing\;time\\\\ 1.&\;\text{sort }J,t,d\text{ order by }d\text{ so that }d_1\leq d_2\leq \cdots\leq d_n\\ 2.&\;s\leftarrow0\\ 3.&\;endtime\leftarrow0\\ 4.&\;\mathbf{for}\;i\;=1\;\mathbf{to}\;\text{length}(J)\;\mathbf{do}\\ 5.&\;\qquad s\leftarrow s+t[i]\\ 6.&\;\qquad endtime\leftarrow \text{max}(endtime,s-d[i])\\ 7.&\;\mathbf{return}\;endtime \end{aligned} 1. 2. 3. 4. 5. 6. 7. Algorithm: minimize-lateness ( J , d , t ) J [ 1.. n ] // J o b s d [ 1.. n ] // De a d l in e t [ 1.. n ] // P rocess in g t im e sort J , t , d order by d so that d 1 ≤ d 2 ≤ ⋯ ≤ d n s ← 0 e n d t im e ← 0 for i = 1 to length ( J ) do s ← s + t [ i ] e n d t im e ← max ( e n d t im e , s − d [ i ]) return e n d t im e
时间复杂度为O ( n log n ) + O ( n ) = O ( n log n ) O(n\log n)+O(n)=O(n\log n) O ( n log n ) + O ( n ) = O ( n log n ) ,空间复杂度为O ( 1 ) O(1) O ( 1 ) .
算法实现 首先构建工作任务的结构体如下:
1 2 3 4 5 typedef struct JOB { int index; int deadline; int process_time; }Job;
按照d i d_i d i 升序排列:
1 2 3 bool compare (Job a, Job b) { return a.deadline < b.deadline; }
注:这将在 .cpp
文件中通过 #include<algorithm>
后调用 sort()
函数实现.
下面是具体算法:
1 2 3 4 5 6 7 8 9 10 11 12 int minimize_lateness (Job J[], int n) { sort (J, J+n, compare); int s = 0 ; int result = 0 ; for (int i = 0 ; i < n; i++){ s = s+J[i].process_time; result = max (result,s-J[i].deadline); } return result; }
测试样例如下所示 :
1 2 3 4 5 6 7 输入:Job J[5] = {{1,10,5}, {2,12,8}, {3,15,4}, {4,11,10}, {5,20,3}}; 输出:12
正确性证明 分析上述贪心算法中解的性质 可以看出,算法解给出的排序是没有空闲时间也不存在逆序的。 这里对逆序的定义如下:
τ ( i , j ) = 1 where s i > s j , but d i < d j \tau(i,j)=1\quad \text{ where }s_i\gt s_j,\text{ but }d_i\lt d_j τ ( i , j ) = 1 where s i > s j , but d i < d j
这表示,对于某一调度T : J → J ′ T:J\to J' T : J → J ′ ,如果调度后的某两个工作J i , J j J_i,J_j J i , J j 对应的截止时间前者优先级高,但是却让后者优先执行,即经调度后的任务排序J ′ \boldsymbol J' J ′ 中 任务J i J_i J i 排在了 任务J j J_j J j 后面,出现这种情况的一对i , j i,j i , j ,其逆序数 记为1.
为了证明贪心算法的正确性,此处采用交换论证法 .
给出 引理 :所有没有逆序、没有空闲时间的调度具有相同的最大延迟.
证明 假设存在多个没有逆序、没有空闲时间的调度。既然满足这样的调度存在有多个,当且仅当 存在有限个 Jobs ∈ J ~ ⊂ J \in \tilde{J}\subset J ∈ J ~ ⊂ J ,它们的 deadline 都相同,记为d d d .
在这种情况下把这些 Jobs 进行交换才不会影响逆序数,不仅如此它们一定连续排列,因此这系列 Jobs 中的最大延迟就是最后一个 Job 的延迟:L max = f last job − d L_{\max}=f_{\text{last job}}-d L m a x = f last job − d . 证毕.
假设存在一个最优调度T ′ T' T ′ ,那么有以下三种情况:
T ′ T' T ′ 生成的新序列中逆序数τ = 0 \tau=0 τ = 0 ,此时根据引理,T T T ,即算法解仍是最优解;T ′ T' T ′ 生成的新序列中存在逆序 ,将证明对其进行调整,使其转化为算法解后,解仍为最优。要证明情况2,只需利用以下两个要点:
要点1:序列存在逆序⇔ \Leftrightarrow ⇔ 序列存在有限个相邻逆序 要点2:交换相邻逆序i , j i,j i , j 得到的解仍为最优解 证明 交换相邻任务前后不影响两个任务合在一起后的开始时间和完成时间,即交换行为只会对i , j i,j i , j 的延迟时间产生影响。i j j i T1 T2 Begin Time Finish Time
如图所示,设两个任务开始时间为s 0 s_0 s 0 ,则结束时间为s 0 + t i + t j s_0+t_i+t_j s 0 + t i + t j . 对于调度T 1 T_1 T 1 ,有延迟时间L ( i , j ) = ( s 0 + t i + t j ) − d j L(i,j)=(s_0+t_i+t_j)-d_j L ( i , j ) = ( s 0 + t i + t j ) − d j ; 对于调度T 2 T_2 T 2 ,有延迟时间L ( j , i ) = ( s 0 + t i + t j ) − d i L(j,i)=(s_0+t_i+t_j)-d_i L ( j , i ) = ( s 0 + t i + t j ) − d i ; 因为( i , j ) (i,j) ( i , j ) 是逆序的,所以有d i > d j d_i\gt d_j d i > d j ,从而L ( i , j ) > L ( j , i ) L(i,j)\gt L(j,i) L ( i , j ) > L ( j , i ) 。
对于整个问题来说,调度T 1 T_1 T 1 情况下的最大延迟时间:
max T 1 L ≥ L ( i , j ) > L ( j , i ) \max_{T_1} L\geq L(i,j)\gt L(j,i) T 1 max L ≥ L ( i , j ) > L ( j , i )
即交互相邻逆序之后,不影响最大延迟时间。 因此在有限次的交换之后,可以将最优解转化为算法解,仍为最优。
田忌赛马问题 可图性问题 参考资料 《算法导论 (原书第三版)》 最大子数组和 - 力扣(LeetCode) 算法设计与分析|中国大学MOOC 使用贪心算法和动态规划团灭活动选择问题 Scheduling to Minimize Lateness|伊利诺伊大学课件