国科大刘玉贵老师 2023算法设计与分析速通期末考试

本文参考:国科大刘玉贵老师计算机算法设计与分析2021年期末
国科大2022计算机算法设计与分析期末考试-刘玉贵老师

一、填空

  1. 下面说法,正确的是:(1,3).
    (1)P类问题是存在多项式时间算法的问题。 (2)NP类问题是不存在多项式时间算法的问题。
    (3)P类问题一定也是NP类问题。 (4)NP类问题比P类问题难解。

  2. 下面说法,正确的是:(2).
    (1)P ⊂ \subset NP (2)P ⊆ \subseteq NP (3)P=NP (4)P≠NP

  3. 下面说法,正确的是:(2,3,4).
    (1)NP-难问题是NP中最难的问题
    (2)NP-完全问题是NP中最难的问题
    (3)NP-难不比任何NP问题容易
    (4)NP-完全问题也是NP-难问题。

  4. 下面属于模拟退火算法实现的关键技术问题的有(1,2,3)。
    (1)初始温度 (2)温度下降控制 (3)邻域定义 (4)目标函数

  5. 下面属于遗传算法实现的关键技术问题的有(1,4)。
    (1)解的编码 (2)初始种群的选择 (3)邻域定义 (4)适应函数

  6. 设Las Vegas算法获得解的概率为p(x) ≥ δ \delta δ , 0 < δ \delta δ < 1,则调用 k 次算法后,获得解的概率为: 1 − ( 1 − δ ) k 1-(1-\delta)^{k} 1(1δ)k

  7. 对判定问题 ∏ \prod 的Monte Carlo算法,当返回false时解总是正确的,但当返回true时解可能有错误,该算法是(2)。当返回true时解总是正确的,但当返回false时解可能有错误,该算法是(1)。
    (1)偏真的Monte Carlo算法 (2)偏假的Monte Carlo算法
    (3)一致的Monte Carlo算法 (4)不一致的Monte Carlo算法

  8. 皇后问题,每一个皇后的位置无任何规律,要求求得一个解即返回。下面合适的方法有(2,4)
    (1) 贪心算法 (2)回溯算法
    (3)分枝限界算法 (4) Las Vegas 算法

  9. 为避免陷入局部最优(小),模拟退火算法以概率 e − Δ f i j t k e^{-\frac{\Delta f_{ij}}{t_{k}}} etkΔfij 接受一个退步(比当前最优解差的)的解,以跳出局部最优。试说明参数 t k t_{k} tk Δ f i j \Delta f_{ij} Δfij 对是否接受退步解的影响。
    答: t k t_{k} tk越高接受的概率越大, Δ f i j \Delta f_{ij} Δfij越小接受退步解的概率越大。

  10. 用遗传算法解某些问题,fitness=f(x)可能导致适应函数难以区分这些染色体。请给出一种解决办法?
    答:排序适应函数: p ( i ) = 2 i m ( m + i ) p(i)=\frac{2i}{m(m+i)} p(i)=m(m+i)2i
    线性加速适应函数:fitness(x)= α \alpha αf(x)+ β \beta βf(x)等。

  11. 用非常规编码染色体实现的遗传算法,如TSP问题使用1,2,…,n的排列编码,简单交配会产生什么问题?如何解决?
    答:简单交配可能产生非解编码(染色体)。
    解决办法:改变交配规则,如交配位后基因按异方基因顺序选取不重复基因、不变位法等。

  12. 设旅行商问题的解表示为D=F={S|S=(i1,i2,…,in),i1,i2,…,in是1,2,…,n的一个排列},邻域定义为2-OPT,求s=(3,1,2,4)的邻域N(s)。
    对于给定的解s=(3,1,2,4),如果考虑所有可能的2-OPT邻域,可以生成如下的排列:
    交换城市1和城市2的位置:(1,3,2,4)
    交换城市1和城市3的位置:(2,1,3,4)
    交换城市1和城市4的位置:(4,1,2,3)
    交换城市2和城市3的位置:(3,2,1,4)
    交换城市2和城市4的位置:(3,4,2,1)
    交换城市3和城市4的位置:(3,1,4,2)
    综上得N(x) = {(1,3,2,4),(2,1,3,4),(4,1,2,3),(3,2,1,4),(3,4,2,1),(3,1,4,2)}

    由于旅行商问题的对称性,如果考虑非等价的邻域,则有:
    交换城市1和城市2的位置: (1,3,2,4)
    交换城市2和城市3的位置: (3,2,1,4)
    交换城市3和城市4的位置: (3,1,4,2)
    综上得N(x) = {(1,3,2,4),(3,2,1,4),(3,1,4,2)}

  13. 0/1背包问题的解X=(x1,x2,…,xn),xi∈{0,1},邻域定义为N(x)={Y| },X=(1,1,0,0,1),求N(X)。
    给定X=(1,1,0,0,1),我们可以应用邻域定义来找到N(X)
    翻转x1 :(0,1,0,0,1)
    翻转x2 :(1,0,0,0,1)
    翻转x3 :(1,1,1,0,1)
    翻转x4 :(1,1,0,1,1)
    翻转x5 :(1,1,0,0,0)
    综上得N(x) = {(0,1,0,0,1),(1,0,0,0,1),(1,1,1,0,1),(1,1,0,1,1),(1,1,0,0,0)}

  14. 考虑下面的函数f(n)和g(n) ,比较他们的阶
    国科大刘玉贵老师 2023算法设计与分析速通期末考试_第1张图片
    (1)g(n)=O(f(n)) (2)f(n)=O(g(n))
    (3)f(n)=O(g(n)) (4)f(n)=O(g(n))

二、判断

  1. Las Vegas算法不会得到不正确的解。(√)
  2. Las Vegas算法总能求得一个解。(×)
  3. Monte Carlo算法不会得到不正确的解。(×)
  4. Monte Carlo算法总能求得一个解。(√)
  5. 一般情况下,无法有效判定Las Vegas算法所得解是否肯定正确。(×)
  6. 一般情况下,无法有效判定Monte Carlo算法所得解是否肯定正确。(√)
  7. 虽然在某些步骤引入随机选择,但Sherwood算法总能求得问题的一个解,且所求得的解总是正确的。 (√)
  8. 虽然在某些步骤引入随机选择,但Sherwood算法总能求得问题的一个解,但一般情况下,无法有效判定所求得的解是否正确。 (×)
    [助记]:以下纯为考试记忆,不对算法做过多探讨
    Monte Carlo算法是蒙的,所以肯定有答案,就是不知道对错
    Las Vegas算法想到last才想出来,一定是对的,但是能不能想出来就另说了
    Sherwood算法引入随机性,舍弃了精确性,所以肯定有解,且为正解,但是精确性有待考量
  9. 旅行商问题存在多项式时间近似方案。( × )
  10. 0/1背包问题存在多项式时间近似方案。( √ )
  11. 0/1背包问题的贪心算法(单位价值高优先装入)是绝对近似算法。(× )
  12. 多机调度问题的贪心近似算法(按输入顺序将作业分配给当前最小负载机器)是 ϵ \epsilon ϵ-近似算法。( √ )
  13. 禁忌搜索中,禁忌某些对象是为了避免邻域中的不可行解。( × )
  14. 禁忌长度越大越好。( × )
  15. 禁忌长度越小越好。( × )

三、简答

1. 简述算法主要步骤

禁忌搜索算法

基本思想:禁忌搜索算法是局部搜索算法的扩展,用一个禁忌表记录下已经到达过的局部最优点,在下一次搜索中,禁忌表中的信息不再或有选择地搜索这些点,以此来跳出局部最优点。

  • Step1 选定一个初始可行解 x c b x^{cb} xcb;初始化禁忌表H={};
  • Step2 若满足停止规则,停止计算;
    否则,在 x c b x^{cb} xcb的邻域中选出满足禁忌要求的候选集Can_N( x c b x^{cb} xcb),从该候选集中选出一个评价值最佳的解 x l b x^{lb} xlb;令 x c b x^{cb} xcb = x l b x^{lb} xlb
  • 更新记录H,重复Step2。

模拟退火算法(21考)

基本思想:模拟退火算法是局部搜索算法的扩展,以一定的概率选择邻域中费用值大的状态(求最小)。理论上是一个全局最优算法。

  • Step1.任选一个初始解 x 0 x_{0} x0 x i = x 0 x_{i}=x_{0} xi=x0;k:=0; t 0 = t m a x t_{0}=t_{max} t0=tmax(初始温度);
  • Step2. 若在该温度达到内循环停止条件,则转Step3;
    否则,从邻域N( x i x_{i} xi)中随机选择一 x j x_{j} xj,计算 Δ f i j = f ( x j ) − f ( x i ) \Delta f_{ij}=f(x_{j})-f(x_{i}) Δfij=f(xj)f(xi)
    Δ f i j \Delta f_{ij} Δfij ≤ 0,则 x i = x j x_{i}=x_{j} xi=xj
    否则,若 e − Δ f i j t k e^{-\frac{\Delta f_{ij}}{t_{k}}} etkΔfij>random(0,1)时, x i : = x j x_{i}:=x_{j} xi:=xj,重复Step2。
  • Step3. t k + 1 : = d ( t k ) t_{k+1}:=d(t_{k}) tk+1:=d(tk);k:=k+1;若满足停止条件,终止计算;否则,回到Step2。

遗传算法(22考)

  • Step1 选择问题的一个编码、给出一个有N个染色体的初始群体 pop(1)={popj(1)|j=1,2,…,N}, t:=1;
  • Step2 对群体pop(1)中的每个染色体 p o p i ( t ) pop_{i}(t) popi(t)计算它的适应性函数 fi=fitness( p o p i ( t ) pop_{i}(t) popi(t));
  • Step3 若停止规则满足,则算法停止;否则,计算概率 p i = f i ∑ 1 ≤ j ≤ N f j ( ∗ ) p_{i} = \frac{f_{i} }{{\textstyle \sum_{1\le j\le N}^{}f_{j}(\ast )} } pi=1jNfj()fi ,并以概率分布(*)从pop(t)中随机选出N个染色体(可能有重复),形成一个种群newpop(t+1)={popj(t)|j=1…N)}。
  • Step4 通过交叉(交叉概率为 P c P_{c} Pc)得到一个有N个染色体的种群crosspop(t+1);
  • Step5 以一个较小的概率p,使得染色体的一个基因发生变异,形成种群 mutpop(t+1); t:=t+1, 一个新的群体诞生pop(t):=mutpop(t);
  • 转到Step2。

2.基于贪心规则写近似算法,并求算法的近似比(不要求证明)

多机调度问题(21、22考)

按照以下步骤进行贪心算法的求解

  • Step1 将所有作业按照执行时间从大到小排序
  • Step2 将第一个作业分配给第一台机器执行;
  • Step3 对于接下来的每一个作业,选择当前剩余执行时间最短的机器进行分配
  • Step4 重复Step3,直到所有作业都被分配。
    近似比= 3 2 \frac{3}{2} 23
    在这里插入图片描述
    感谢同学的指正:GMPS是2近似算法,上面的DGMPS是改进的贪心近似,近似比为3/2

这种贪心算法的正确性可以通过反证法证明。假设存在一种更优的调度方案,使得所有作业的完成时间更短。那么在这个调度方案中,必然存在某个作业在执行时会被分配到一个执行时间更长的机器上,从而导致整个调度方案时间更长。因此,原来的方案就是最优的。

顶点覆盖问题

基于贪心的近似算法步骤如下:

  • Step1 初始化 C=∅。(一个空的集合), E ′=E(所有的边)。
  • Step2 选择 e=(u,v) 是 E ′中的任意一条边。
  • Step3 将 u 和 v 都加入 C。
  • Step4 从 E ′ 中移除所有与 u 或 v 相关的边
  • Step5 重复Step2、Step3、Step4,直至E ′=∅ 。

近似比= 2。这个贪心算法找到的解的大小最多是最优解的两倍。
考虑任何一条边 e=(u,v),当这条边被选择时,我们都至少需要选择其中一个顶点来覆盖它。所以,每选择一条边,我们都会覆盖两个顶点(即 u 和 v)。但是,这两个顶点可能重复被其他边覆盖,所以我们最多覆盖两倍的顶点。

3.NPC证明

相遇集问题是 NP-C问题(21考)

例:给定集合S 的一个子集族C 和一个正整数 K ;
问: S 是否包含子集 S’,| S’|≤ K ,使得 S’与C 中的任何一个子集的交均非空?( S’称为C 的相遇子集)试判定相遇集问题是 P 类的还是 NP 完全的

证明:
首先证明相遇集问题 ∈ N P : \in NP: NP:
给定 S ′ ⊆ S S\prime \subseteq S SS,验证 ∣ S ′ ∣ ≤ K \mid S\prime \mid \le K S∣≤K,需要 min ⁡ S ∣ = O ( 0 ) \min S \mid = O(0) minS∣=O(0)时间,验证任给 c ∈ C , S ′ ∩ c c \in C,S\prime \cap c cCSc非空可在 ∣ C ∣ ∗ ∣ c ∣ ∗ ∣ S ′ ∣ = ∣ C ∣ ∗ n 2 \mid C \mid \ast \mid c \mid \ast \mid S\prime \mid = \mid C \mid \ast n^{2} CcS∣=∣Cn2内完成,所以问题是多项式可验证的,因此相遇集问题 ∈ N P \in NP NP
再证顶点覆盖问题 ∝ ∣ c ∣ = 2 \propto \mid c\mid =2 ∝∣c∣=2的相遇集问题:
任给图 G ( V , E ) ,设 E = ( x 1 , y 1 ) , ( x 2 , y 2 ) ⋅ ⋅ ⋅ ( x m , y m ) , G(V,E),设E={(x_{1},y_{1}),(x_{2},y_{2})···(x_{m},y_{m})}, G(V,E),设E=(x1,y1),(x2,y2)⋅⋅⋅(xm,ym) 令 S = V 令S=V S=V
C = ( x 1 , y 1 ) , ( x 2 , y 2 ) ⋅ ⋅ ⋅ ( x m , y m ) C={(x_{1},y_{1}),(x_{2},y_{2})···(x_{m},y_{m})} C=(x1,y1),(x2,y2)⋅⋅⋅(xm,ym),那么,若G存在定点覆盖 V ′ , ∣ V ′ ∣ ≤ K V\prime ,\mid V \prime \mid \le K VV∣≤K,则 V ′ V\prime V 覆盖 E E E的每条边的至少一个顶点,即 V ′ V\prime V 包含 x i , y i x_{i},y_{i} xi,yi至少一个, i = 1 , 2 , ⋅ ⋅ ⋅ , m i=1,2,···,m i=1,2,⋅⋅⋅,m
S ′ = V ′ S\prime = V\prime S=V ,显然, S ′ S\prime S C C C中任意子集 x i , y i {x_{i},y_{i}} xi,yi交集不为空。
反之,若存在相遇集 S ′ S\prime S ,则令 V ′ = S ′ V\prime = S\prime V=S ,由于 S ′ S\prime S C C C中子集交不为空,则 S ′ S\prime S 必含有 x i , y i x_{i},y_{i} xi,yi之一,说明他是 G G G的定点覆盖。因此 ∣ c ∣ = 2 \mid c \mid = 2 c∣=2的相遇集问题是NP难问题,所以相遇集问题 ∈ N P − H \in NP-H NPH
综上所述,相遇集问题 ∈ N P C \in NPC NPC

独立集问题是 NP-C问题

例:对于给定的无向图G = (V, E)和正整数k ≤ |V|
问 : G 是否包含一个于 k-独立集 V ’ ,即是否存在一个子集V ’ ⊆ V, | V ’ |= k ,使得V '中的任何两个顶点在图中G 都不相邻。

证明:
首先证明独立集问题 ∈ N P : 给定 V ′ ⊆ V , ∣ V ′ ∣ = k ,验证 V ′ 中的任意两顶点在图 G 中是否相邻,需要将图遍历一遍,可以在多项式时间内完成, 所以问题是多项式可验证的,因此独立集问题 ∈ N P 首先证明独立集问题\in NP:\\ 给定V\prime \subseteq V,\mid V\prime \mid = k,验证V\prime中的任意两顶点在图G中是否相邻,需要将图遍历一遍,可以在多项式时间内完成,\\所以问题是多项式可验证的,因此独立集问题\in NP 首先证明独立集问题NP:给定VVV∣=k,验证V中的任意两顶点在图G中是否相邻,需要将图遍历一遍,可以在多项式时间内完成,所以问题是多项式可验证的,因此独立集问题NP
再证 3 S A T ∝ 独立集问题 : 对任意 C j = Z 1 ∧ Z 2 ∧ Z 3 , j = 1 , 2 , … m , 构造以 z 1 , z 2 , z 3 为顶点的三角形, 若顶点 z i 和 z l 为某 x k 和 ¬ x k 则增加连接边,构成图 G 。若 3 S A T 有成真赋值,对每个 C j = 1 ,必有某个 z l = 1 ,将该元素对应顶点选入 S , S 一定是独立集,因为该顶点仅与 ¬ z l 相连, ¬ z l = 0 , 在另一三角形中不会被选中。 ∣ S ∣ = m 。 反之,若存在独立集 S , ∣ S ∣ = m ,则每个三角形必须取一个点,因为取两个则不独立,不取则 < m 。 令 i f 取点对应的 z i = x k , x k = 1 ; z i = ¬ x k , x k = 0 , 由于 x k 与 ¬ x k 之间有边, 不可能同时取到,上述赋值不矛盾。如果 ( x 1 , x ) 2 , … , x ) n ) 里有未赋值着, 可任意赋值。则对任意 C j = Z 1 ∧ Z 2 ∧ Z 3 , 取点 z i = 1 , C j 可满足综上所述,独立集问题 ∈ N P C 再证3SAT\propto 独立集问题:\\ 对任意C_{j}=Z_{1}∧Z_{2}∧Z_{3},j=1,2,…m,构造以z_{1},z_{2},z_{3}为顶点的三角形,\\若顶点z_{i}和z_{l}\\为某x_{k}和¬x_{k}则增加连接边,构成图G。 若3SAT有成真赋值,对每个C_{j}=1,必有某个z_{l}=1,将该元素对应顶点选入S,\\S一定是独立集,因为该顶点仅与¬z_{l}相连,¬z_{l}=0,在另一三角形中不会被选中。|S|=m。\\ 反之,若存在独立集S,|S|=m,则每个三角形必须取一个点,因为取两个则不独立,不取则再证3SAT独立集问题:对任意Cj=Z1Z2Z3j=1,2,m,构造以z1,z2,z3为顶点的三角形,若顶点zizl为某xk¬xk则增加连接边,构成图G。若3SAT有成真赋值,对每个Cj=1,必有某个zl=1,将该元素对应顶点选入S,S一定是独立集,因为该顶点仅与¬zl相连,¬zl=0,在另一三角形中不会被选中。S=m反之,若存在独立集SS=m,则每个三角形必须取一个点,因为取两个则不独立,不取则<mif取点对应的zi=xk,xk=1;zi=¬xk,xk=0,由于xk¬xk之间有边,不可能同时取到,上述赋值不矛盾。如果(x1,x)2,,x)n)里有未赋值着,可任意赋值。则对任意Cj=Z1Z2Z3,取点zi=1,Cj可满足综上所述,独立集问题NPC

[注]:不会做,直接背吧,有兴趣的可自行学习
典型NPC问题及其证明
算法设计与分析笔记之独立集问题
【计算理论】计算复杂性 ( 无向图独立集问题 | 独立集问题是 NP 完全问题证明思路 | 证明独立集问题是 NP 完全问题 )

0/1 背包判定问题是NP-C问题(22考)

已知带权集合的划分问题是 NP-C 问题,试证明 0/1 背包判定问题是NPC问题(22考)

例:给定一个m× n矩阵 A和一个m 元整数向量b 以及n维列向量C,整数D,使得AX≤b,且 C T C^{T} CTX≥D);
问:是否存在一个n 元 0/1 向量 x ,使得 Ax ≤ b ?

证明:
首先证明原问题 ∈ N P : \in NP: NP
任给X,原问题的判定 A X ≤ b AX \le b AXb C T X ≤ D C^{T}X \le D CTXD需计算 m n 2 + n mn^{2}+n mn2+n次乘法和加法及2次比较,可在多项式时间内完成,所以原问题 ∈ N P \in NP NP
将矩阵A和b的各行取相同值 a 11 , a 12 , … a 1 n a_{11},a_{12},…a_{1n} a11,a12,a1n b 1 b_{1} b1
则问题变为:判定是否存在X, a 11 x 1 + a 12 x 2 + … a 1 n x n ≤ b 1 a_{11}x_{1}+a_{12}x_{2}+…a_{1n}x_{n}≤b_{1} a11x1+a12x2+a1nxnb1
判定且 C T X ≥ D C^{T}X ≥ D CTXD问题,这就是0/1背包判定问题。
0/1背包判定问题是NPC,所以这个特殊整数规划问题也是NPC问题,从而一般的整数规划问题是NPC问题.
综上所述, 0/1 背包判定问题是NPC问题

TSP判定问题是NPC问题

已知Hamilton圈问题是NPC问题,证明TSP判定问题是NPC问题

证明:
首先证明TSP问题是NP问题:
任给一个图 G = ( V , E ) 的一个顶点排序 v 1 v 2 . . v n 和数 L ,验证 ( v i , v i + 1 ) ∈ E 和 ( v n , v 1 ) ∈ E 可在 O ( n ∗ ∣ E ∣ ) ≤ O ( n 3 ) 完成,验证路径和 ≤ L 需 0 ( n ) 时间,所以 T S P ∈ N P 问题。 任给一个图G=(V,E)的一个顶点排序v_{1}v_{2}..v_{n}和数L,验证(v_{i},v_{i+1})∈E和(v_{n},v_{1})∈E\\可在O(n*|E|)≤O(n^{3})完成,验证路径和≤L需0(n)时间,所以TSP\in NP问题。 任给一个图G=(V,E)的一个顶点排序v1v2..vn和数L,验证(vi,vi+1)E(vn,v1)E可在O(nE)O(n3)完成,验证路径和L0(n)时间,所以TSPNP问题。
对于 H a m i l t o n 问题实例 I ,其图为 G = ( V , E ) ,构造一个新的图 G 1 = ( V , E 1 ) 它是一个完全图, 各边赋权如下:若 ( u , v ) ∈ E , w ( u , v ) = 1 ; 否则 w ( u , v ) = k > 1 。 L = n 是 G 的顶点数,得到旅行商问题实例 I 。 则图 G 有 H a m i l t o n 回路当且仅当图 G 1 有长为 n 的环游路线。 因为,若 G 有 H a m i l t o n 回路,则取此回路为 G 1 的解, 环路长度 = n 。反之,若 G 1 存在一条长度 < = n 的环游回路,则回路的每条边权值一定为 1 , 否则回路长 > n 。这说明, T S P 的回路全在 G 的边 E 中,取此正是 G 的一条 H m i l t o n 回路。 对于Hamilton问题实例 I , 其图为G=(V,E),构造一个新的图G_{1}=(V,E_{1})它是一个完全图,\\各边赋权如下: 若(u,v) \in E, w(u,v)=1 ; 否则 w(u,v)=k>1。L=n是G的顶点数,得到旅行商问题实例I。\\ 则图G有Hamilton回路当且仅当图G_{1}有长为n的环游路线。\\因为,若G有Hamilton回路,则取此回路为G_{1}的解,\\环路长度=n。反之,若G_{1}存在一条长度<=n的环游回路,则回路的每条边权值一定为1,\\否则回路长>n。这说明,TSP的回路全在G的边E中,取此正是G的一条Hmilton回路。 对于Hamilton问题实例I,其图为G=(V,E),构造一个新的图G1=(V,E1)它是一个完全图,各边赋权如下:若(u,v)E,w(u,v)=1;否则w(u,v)=k>1L=nG的顶点数,得到旅行商问题实例I则图GHamilton回路当且仅当图G1有长为n的环游路线。因为,若GHamilton回路,则取此回路为G1的解,环路长度=n。反之,若G1存在一条长度<=n的环游回路,则回路的每条边权值一定为1否则回路长>n。这说明,TSP的回路全在G的边E中,取此正是G的一条Hmilton回路。
Hamilton圈问题是NPC,所以旅行商问题也是NPC问题。

4.读程序,并使用迭代法分析程序的时间复杂度

  • 考虑此部分最近两年的考题都来自PPT,翻阅PPT后发现分治算法的PPT中还有:
    • 快排最差 T(n)=T(n-1)+n-1=O(n^2)
    • 快排最优 T[n] = 2T[n/2] + n=O(nlogn)
    • 2路归并 T ( N ) = { 0 , N < = 1 2 T ( N 2 ) + C ( N ) , N > 1 ) } T(N)=\left \{\begin{matrix} 0, N <= 1\\2T(\frac{N}{2})+C(N),N > 1) \end{matrix}\right \} T(N)={0N<=12T(2N)+C(N)N>1)}

快速傅里叶变换(21考)

	Proc FFT(N, a,w,A)
     # N=2n,w是n次单位根,a是已知的N元数组,代表多项式a(x)的系数,
     # A是计算出来的N元数组,A[j]=a(wj), j=0,1,…,N-1.
       real b[ ], c[ ];  int j;
       complex B[ ], C[ ], wp[ ];
       if  N=1 then A[0]:=a[0];
       else
          n:=N/2;
          for j from 0 to n-1 do
            b[j]:=a[2*j+1];  c[j]:=a[2*j];
          end{for}
       end{if}
       FFT(n,b,w*w,B);
       FFT(n,c,w*w,C);
       wp[0]:=1;
       for j from 0 to n-1 do
           wp[j+1]:=w*wp[j];
           A[j]:= C[j]+B[j]*wp[j];
           A[j+n]:= C[j]-B[j]*wp[j];
       end{for}
     end{FFT} 

这个式子考试不会给,需要自己推:
T ( N ) = { a , N = 1 2 T ( N 2 ) + C ( N ) , N > 1 ) } T(N)=\left \{\begin{matrix} a, N = 1\\2T(\frac{N}{2})+C(N),N > 1 ) \end{matrix}\right \} T(N)={aN=12T(2N)+C(N)N>1)}

一维最接近点对(22考)

    proc ClosPair1(S,d)
    //S是实轴上点的集合,参数d表示S中最  //近对的距离
        global S,d;     integer n;     float m,p,q;
        n:=|S|;
        if n<2 then d:=; return(false); end{if}
        m:=S中各点坐标的中位数;
        //划分集合S
        S1:={x∈S| x<=m};  S2:={x∈S| x>m};
        ClosPair1(S1,d1);
        ClosPair1(S2,d2);
        p:=max(S1);  q:=min(S2);
        d:=min(d1,d2,q-p);
        return(true);
    end{ClosPair1}

T ( N ) = { a , n < 4 2 T ( N 2 ) + C ( N ) , n > = 4 ) } T(N)=\left \{\begin{matrix} a,n < 4 \\ 2T(\frac{N}{2})+C(N),n >= 4 ) \end{matrix}\right \} T(N)={an<42T(2N)+C(N)n>=4)}

四、算法

1. 贪心(需要证明最优解)

建基站(22考)

设有一条边远山区的道路AB,沿着道路AB分布着n所房子。这些房子到A的距离分 d 1 , d 2 , … , d n ( d 1 < d 2 < … < d n ) d_{1},d_{2},…,d_{n}(d_{1}d1,d2,,dn(d1<d2<<dn)。为了给所有房子的用户提供移动电话服务,需要在这条道路上设置一些基站。为了保证通讯质量,每所房子应该位于距离某个基站的4Km范围内。设计一个算法找基站的位置,并且使得基站的总数最少。证明算法的正确性.

  • 贪心算法:首先在 d 1 + 4 d_{1}+4 d1+4处建立基站,然后对 d 2 , d 3 , … d n d_{2},d_{3},…d_{n} d2,d3,dn依次检查,找到下一个不能被该基站覆盖的房子。如果 d k < = a 1 + 4 d_{k}<=a_{1}+4 dk<=a1+4 d k + 1 > a 1 + 4 d_{k+1}>a_{1}+4 dk+1>a1+4,那么第k+1个房子不能被基站覆盖,于是取 a 2 = d k + 1 + 4 a_{2}=d_{k+1}+4 a2=dk+1+4作为下一个基站的位置,照此下去,直到检查完dn为止。
  • 正确性证明:
    归纳法:对任何正正整数k,存在最优解包含算法前k步选择的基站位置。
    证明:k=1,存在最优解包含a[1]。若不然,有最优解OPT,
    其第一个基站位置是b[1],b[1]≠a[1]。那么 d 1 d_{1} d1-4≤b[1] B[1]覆盖的是距离在[d1,b[1]+4]之间的房子。A[1]覆盖的是距离[d1,a[1]+4]的房子,
    因为b[1] 假设对于k存在最优解A包含算法前k步的选择的基站,即A={a[1],a[2]…a[k]}∪B,
    其中a[1],a[2]…a[k]覆盖了距离d1,d2,…,dj的房子,那么B是关于L={dj+1,dj+2,…,dn}的最优解。
    否则,存在关于L的最优解B*,那么用B替换B得得到A,且|A*|<|A|,与A是最优解矛盾。
    根据归纳假设,L有一个最优解B/={a[k+1],…,},|B/|=|B|。
    于是,A/={a[1],a[2],…,a[k]}∪B/= {a[1],a[2],…,a[k+1]…}也是最优解,
    且|A|=|A/|,从而证明了结论对k+1也真。证毕。
    算法复杂度 T(n)=O(n)。
    /*
     * 接收一个数组D 其中元素di表示到A的距离,元素数为村庄数
     * 返回一个数组nums 其中元素为基站到A的距离,元素数为基站数
     * 算法只包含一次对房屋数组D的遍历,故时间复杂度为O(n)
     * */
    public ArrayList<Integer> jianJiZhan(int[] D) {
        ArrayList<Integer> list = new ArrayList<>();//不知道建设多少基站,用集合list存储基站到A的距离
        if (D.length == 0) return list;//D中没有村庄,返回空list
        list.add(D[0] + 4);//建设第一个基站
        int dis = list.get(0);//dis表示当前基站到A的距离
        for (int i = 0; i < D.length; i++) {//遍历房屋数组D
            if (D[i] >= dis + 4) {//如果下一房屋到当前基站的距离大于4
                list.add(D[i] + 4);//则在下一房屋之后的4处建基站
                dis = D[i] + 4;//更新最新基站到A的距离
            }
        }
        return list;
    }

test检测作业(21考)

有n个进程p1,p2,…,pn,进程pi的开始时间为s[i],截止时间为d[i]。可以通过检测程序Test来测试正在运行的进程,Test每次测试时间很短,可以忽略不计,即,如果Test在时刻t测试,那么它将对满足s[i]<=t<=d[i]的所有进程同时取得测试数据。问:如何安排测试时刻,使得对每个进程至少测试一次,且Test测试的次数达到最少?设计算法并证明正确性,分析算法复杂度。

class Program implements Comparable<Program>{
        private Integer start;
        private Integer end;
        public Program(Integer start, Integer end) {
            this.start = start;
            this.end = end;
        }
        @Override
        public int compareTo(Program o) {
            return this.end.compareTo(o.end);
        }
    }
    /*
    * 将进程按照结束时间排序
    * 取第一个进程的结束时间为测试点t
    * 再取下一个开始时间大于t的进程的结束时间作为测试点t
    * 直至遍历完成
    * 进行一次遍历,耗时为O(n)
    * 进行排序,故综合时间复杂度为O(nlogn)
    * 接收一个无序数组T,其中元素表示进程P的结束时间
    * 返回一个list表示测试点
    * */

    public ArrayList<Integer> Test(Program[] P){
        ArrayList<Integer> list = new ArrayList<>();//用list存储测试点
        if (P.length<1)return list;//没有程序,直接返回空list
        Arrays.sort(P);//按结束时间进行排序,Program重写了CompareTo方法,以end为比较对象
        int t = P[0].end;
        list.add(t);
        for (int i = 1; i < P.length; i++) {//遍历所有程序
            if (P[i].start>t) {//如果程序的开始时间大于测试点t
                t=P[i].end;//更新测试点为该程序的结束时间
                list.add(t);//将测试点加入集合list
            }//否则继续遍历至终止
        }
        return list;
    }
  • 用归纳法证明其正确性:任给k,存在最优解包含算法前k步选择的结果。
    证明:任给k,存在最优解包含算法前k步选择的结果。
    证明 : 任给 k ,存在最优解包含算法前 k 步选择的结果。证明: k = 1 ,设 s = t [ i 1 ] , t [ i 2 ] , … 是最优解,则一定 t [ i 1 ] < t [ 1 ] , 否则 t [ i 1 ] 不能测试 d [ 1 ] 的进程。设 p u 是在时刻 t [ i 1 ] 被检测到的任意进程,则 s [ u ] ≤ t [ i 1 ] ≤ d [ u ] ,从而 s [ u ] ≤ t [ i 1 ] < t [ 1 ] = d [ 1 ] ≤ d [ u ] ,因此, p u 也可以在 t [ 1 ] 时刻被测试,在 s 中将 t [ i 1 ] 替换为 t [ 1 ] 也是最优解。 证明:任给k,存在最优解包含算法前k步选择的结果。 证明:k=1,设s={t[i_{1}],t[i_{2}],…}是最优解,则一定t[i_{1}]证明:任给k,存在最优解包含算法前k步选择的结果。证明:k=1,设s=t[i1],t[i2]是最优解,则一定t[i1]<t[1],否则t[i1]不能测试d[1]的进程。设pu是在时刻t[i1]被检测到的任意进程,则s[u]t[i1]d[u],从而s[u]t[i1]<t[1]=d[1]d[u],因此,pu也可以在t[1]时刻被测试,在s中将t[i1]替换为t[1]也是最优解。
    假设结论对 k 成立,则存在最优解 T = t [ 1 ] , t [ 2 ] , … , t [ k ] ∪ T ′ 。设算法前 k 步选择的测试点不能测到的进程构成集合 Q ⊆ P , P 是全部进程集合,那么 T ′ 是问题 Q 的最优解。根据归纳假设,存在 Q 的最优解 T ∗ 包含测试点 t [ k + 1 ] ,即 T ∗ = t [ k + 1 ] ∪ T ′ ′ ,因此, t [ 1 ] , t [ 2 ] , … , t [ k ] ∪ T ∗ = t [ 1 ] , t [ 2 ] , … , t [ k ] , t [ k + 1 ] ∪ T ′ ′ 也是原问题的最优解,得证 假设结论对k成立,则存在最优解T={t[1],t[2],…,t[k]}\cup T\prime 。设算法前k步选择的测试点不能测到的进程构成集合Q\subseteq P,P是全部进程集合,那么T\prime 是问题Q的最优解。根据归纳假设,存在Q的最优解T \ast 包含测试点t[k+1],即T\ast={t[k+1]}\cup T\prime \prime ,因此, {t[1],t[2],…,t[k]} \cup T \ast = {t[1],t[2],…,t[k],t[k+1]} \cup T \prime \prime 也是原问题的最优解,得证 假设结论对k成立,则存在最优解T=t[1],t[2],,t[k]T。设算法前k步选择的测试点不能测到的进程构成集合QPP是全部进程集合,那么T是问题Q的最优解。根据归纳假设,存在Q的最优解T包含测试点t[k+1],即T=t[k+1]T′′,因此,t[1],t[2],,t[k]T=t[1],t[2],,t[k],t[k+1]T′′也是原问题的最优解,得证

罚款

设有作业集合J={1,2,…,n},每项作业的加工时间都是1,所有作业的截止时间是D。若作业i在D之后完成,则称为被延误的作业,需赔偿罚款m(i)(i=1,2,…n),这里D和m(i)都是正整数,且n项m(i)彼此不等。设计一个算法求出使总罚款最小的作业调度算法,证明算法的正确性并分析时间复杂度。

/*
     * 类似于顾客等待问题,按照罚款金额降序排列,先完成罚款金额大的作业
     * 排序后,在有限的时间D内,完成尽可能多的高罚款作业,即依次完成作业
     *
     * 接收一个无序数组m表示罚款额,一个int类型的D表示截至时间
     * 返回一个int类型W表示罚款总金额
     *
     * 只有一次遍历,和一次排序,故时间复杂度为O(nlogn)
     * */

    public int faKuan(int[] m, int D) {
        int W = 0;//W表示罚款总额
        if (m.length < 1) return W;//没有作业,返回0
        Integer[] M = Arrays.stream(m).boxed().toArray(Integer[]::new); //将int数组转换成Integer数组
        Arrays.sort(M, Collections.reverseOrder());//降序排列
        for (int i = D; i < M.length; i++) {//从D开始以后的作业都会被罚款
            W += M[i];
        }
        return W;
    }
  • 正确性证明:交换论证。设算法选择的作业调度f的安排次序是,那么罚款为F(f)= ,任给k<=D=m(ij)。显然最优调度没有空闲时间,不妨设作业是连续安排的。每项作业的加工时间都是1,在D之前可完成D项作业,在D之后安排的n-D项作业iD+1,iD+2,…,in是被罚款的作业。
    设算法得到的安排不是最优的,则存在一个最优调度f*,它的前D个作业包含了至少1个作业ij,j>D,从而至少有一个作业ik,k<=D被安排在了D之后。交换ik,ij得到新的调度f**,则F(f*)-F(f**)=m(ik)-m(ij)≥0,说明f** 也是最优调度,但f**与f的前D个相同作业多了1个,依次进行,可得最优作业与f相同,得证。

顾客等待

设有 n 个顾客同时等待一项服务。顾客 i 需要的服务时间为 t i t_{i} ti ,1 ≤ i ≤ n 。 应该如何安排 n 个顾客的服务次序才能使总的等待时间达到最小?总的等待时间是各顾客等待服务的时间的总和。试给出你的做法的理由(证明)。

/*
 * 采用短作业优先算法
 * 第i位顾客需要服务时间为t_i 每位顾客的等待时间为在其前面被服务的所有顾客的服务时间和
 * 按照服务时间从小到大给所有顾客编号
 * 则第一位顾客等待时间为0
 * 第二位顾客等待时间为T1 = t1
 * 第三位顾客等待时间为T2 = t1+t2
 * 设所有顾客的等待时间为W
 * W = T1+T2+T3+T4+…+Tn= 0+t1+t1+t2+t1+t2+t3+…+t1+t2+t3+…+tn-1
 * =(n-1)*t1+(n-2)*t2+(n-3)*t3+…+tn-1
 * 要使W最小,需要使得t1最小,其次t2以此类推
 * 因此,按照服务时间递增的次序为顾客提供服务,即采用SJF算法,按服务时间排序
 * 可以最小化总等待时间。
 * */

public class guKeDengDai {
    /*
     * 接收一个顾客等待时间数组m
     * 升序排列后计算等待时间
     * 返回总等待时间
     * */
    public int guKeDengDai(int[] m) {
        int W = 0;//W表示所有顾客的等待时间
        if (m.length < 1) return W;//没有顾客,返回0
        Integer[] M = Arrays.stream(m).boxed().toArray(Integer[]::new); //将int数组转换成Integer数组
        Arrays.sort(M);//升序排列
        for (int i = 0; i < M.length; i++) {//等待时间计算
            W += M[i] * (m.length - i - 1);
        }
        return W;
    }
  • 正确性:交换论证。不妨假设 t 1 ≤ t 2 ≤ t 2 ≤ … ≤ t n t_{1}≤t_{2}≤t_{2}≤…≤t_{n} t1t2t2tn,算法的调度 f 结果为1,2,…,n。设它不是最优的,存在最优调度f*,设其最早第k项作业 i k i_{k} ik与 f 不同,即 f ∗ : i 1 , i 2 , . . i k − 1 , i k , i k + 1 … i n f*:i_{1},i_{2},..i_{k-1},i_{k},i_{k+1}…i_{n} f:i1,i2,..ik1,ik,ik+1in,必有 t i k > = t k t_{ik}>=t_{k} tik>=tk。 将f*中作业K与作业 i k i_{k} ik置换,得到调度 f ∗ ∗ : 1 , 2 … k , i k + 1 , … i n f**:1,2…k,i_{k+1},…i_{n} f1,2k,ik+1,in。其中 i k i_{k} ik位置为j,则j>k, t_{ik{>=t_{k}。则:
    T ( f ∗ ) − T ( f ∗ ∗ ) = ( n − k ) t i k + ( n − j ) t k − ( n − k ) t k − ( n − j ) t i k = ( j − k ) t i k + ( k − j ) t k = ( j − k ) ( t i k − t k ) > = 0 T(f*)-T(f**)=(n-k)t_{ik}+(n-j)t_{k}-(n-k)t_{k}-(n-j)t_{ik}=(j-k)t_{ik}+(k-j)t_{k}=(j-k)( t_{ik}-t_{k})>=0 T(f)T(f)=(nk)tik+(nj)tk(nk)tk(nj)tik=(jk)tik+(kj)tk=(jk)(tiktk)>=0
    说明f**也是最优调度,但它与f不同的次序项后移了一位。重复此过程最多n步,可得f最优。

磁带存储

p 1 , p 2 , p n p_{1} ,p_{2},p_{n} p1,p2,pn 是准备存放到长为 L 的磁带上的 n 个程序,程序 p i p_{i} pi 需要的带长为 a i a_{i} ai。设 ∑ i = 1 n a i > L \sum_{i=1}^{n}a_{i}>L i=1nai>L,要求选取一个能放在带上的程序的最大子集和(即其中含有最多个数的程序)Q。构造Q的一种贪心策略是按 a i a_{i} ai的非降序将程序计入集合。

  1. 证明这一策略的总能找到最大子集Q,使得 ∑ p i = 1 a i > L \sum_{p_{i}=1}a_{i}>L pi=1ai>L
  2. 设Q是使用上述贪心算法得到的子集和,磁带的利用率可以小到何种程度?
  3. 说明问题1中提到的设计策略不一定能使 ∑ p i = 1 a i / L \sum_{p_{i}=1}a_{i}/L pi=1ai/L取的最大值的子集和。

(1)反正法。设算法得到的程序集合为Q不是最优的,Q是一个最优集合,则Q与Q无包含关系。Q不能包含Q* 是显然的,如果Q包含Q ,那么对p ∈ \in Q\Q,如果p不小于Q的成员,算法将会在接下来的判断中将其选入(不超L),否则,它先被检查,早应该是Q的成员。
取Q使得|Q∩Q|最大。令p是Q\Q中最小的,则Q中比p更小的程序一定在Q中(阴影部分)。则任给p* ∈ \in Q*\Q,必有p*>=P。否则,若p*在P前被算法检查,此时磁带装入的程序均在Q∩Q即Q* 中,P不被选入,说明此时磁带已装不下p ,但P在Q里,说明P在Q∩Q之外可以装入,矛盾。
将Q中的P换成P,显然可以装入,得到Q**,也是最优的,但|Q**∩Q|>|Q∩Q|,与Q的选取矛盾。得证。
(2)磁带利用率为该是集合Q中所有程序的长度之和/L,可以为0,如果一个也装入不了。
(3)反例,l=8,n=4,a1=1,a2=2,a3=3,a4=4,算法装入p1,p2,p3,带长浪费2,而装入p1,p3,p4,带长浪费0。

2. 动态规划(证明最优子结构)

提供的Java代码可能与答案所给不同,谨慎食用

最大子段和(22考)

最大字段和问题:给定整数序列 a 1 , a 2 , ⋅ ⋅ ⋅ , a n a_{1},a_{2},···,a_{n} a1,a2,⋅⋅⋅,an,设计动态规划算法求该序列形如 ∑ k = 1 j a k \sum_{k=1}^{j}a_{k} k=1jak的子段和的最大值:max{0, max ⁡ ∑ k = 1 j a k , i < = i < = j < = n \max \sum_{k=1}^{j}a_{k},i<=i<=j<=n maxk=1jak,i<=i<=j<=n}

证明最优子结构:
设{ x i 1 , x i 2 , … , x i j x_{i1},x_{i2},…,x_{ij} xi1,xi2,,xij}是以 x i j x_{ij} xij为最后元素的问题的最优解,
证明{ x i 1 , x i 2 , … , x i j − 1 x_{i1},x_{i2},…,x_{ij-1} xi1,xi2,,xij1}是子问题以输入为A={ x 1 , x 2 , … , x i j − 1 x_{1},x_{2},…,x_{ij-1} x1,x2,,xij1}的问题的最优解,
假设存在不等于{ x 1 , x 2 , … , x i j − 1 x_{1},x_{2},…,x_{ij-1} x1,x2,,xij1}的{ x ′ i 1 , x ′ i 2 , … , x ′ i j − 1 x\prime_{i1},x\prime_{i2},…,x\prime_{ij-1} xi1,xi2,,xij1}是子问题的最优解,
则max{ 0 , m a x 1 ≤ i ≤ j ≤ n ∑ k = i j − 1 x ′ i k 0,max_{1 \le i \le j\le n}\sum_{k=i}^{j-1} x\prime_{ik} 0,max1ijnk=ij1xik} > max{ 0 , m a x 1 ≤ i ≤ j ≤ n ∑ k = i j − 1 x i k 0,max_{1 \le i \le j\le n}\sum_{k=i}^{j-1} x_{ik} 0,max1ijnk=ij1xik},
则max{ 0 , m a x 1 ≤ i ≤ j ≤ n ∑ k = i j − 1 x ′ i k + x i j 0,max_{1 \le i \le j\le n}\sum_{k=i}^{j-1} x\prime_{ik}+x_{ij} 0,max1ijnk=ij1xik+xij} > max{ 0 , m a x 1 ≤ i ≤ j ≤ n ∑ k = i j − 1 x k + x i j 0,max_{1 \le i \le j\le n}\sum_{k=i}^{j-1} x_{k}+x_{ij} 0,max1ijnk=ij1xk+xij}
则存在{ x ′ i 1 , x ′ i 2 , … , x ′ i j − 1 , x i j x\prime_{i1},x\prime_{i2},…,x\prime_{ij-1},x_{ij} xi1,xi2,,xij1,xij}是问题的可行解,
这与{ x i 1 , x i 2 , … , x i j x_{i1},x_{i2},…,x_{ij} xi1,xi2,,xij}是以 x i j x_{ij} xij为最后元素的问题的最优解矛盾。

设dp[i]表示以nums[i]结尾的子段的最大子段和,即dp[i]是原问题的最优解:
假设dp[i]表示以nums[i]结尾的子段的最大子段和,则对于以nums[i+1]结尾的最大子段和dp[i+1]有两种可能,
1.原来的最大子段和dp[i]+当前值nums[i],此时dp[i+1]=dp[i]+nums[i+1]
2.仅由当前值构成,即dp[i+1]=nums[i+1]
dp[i+1]=max(dp[i-1]+nums[i],nums[i]) dp[0]=nums[0]

时间复杂度:O(n)

//1、解法(1)为暴力搜索算法,其中用了两个嵌套for循环对数组a进行遍历,故时间复杂度为O(n^2)

    public int[] maxSum(int[] nums, int l, int r) {
        int mid = l + (r - l >> 1);
        if (l == r) return new int[]{nums[l], l, r};
        int[] Lmax = new int[3];//0->子段和;1->左;2->右
        int[] Rmax = new int[3];
        Lmax = maxSum(nums, l, mid);// 递归左侧
        Rmax = maxSum(nums, mid, r - 1);// 递归右侧
        int sum = 0;// 计算一下左右加起来子段
        for (int i = Lmax[1]; i <= Rmax[2] - Lmax[1]; i++) {
            sum += nums[i];
        }
        // 三种情况作比较,返回最大子段
        int max = Math.max(Lmax[0], Rmax[0]);
        if (max < sum) return new int[]{sum, Lmax[1], Rmax[2]};
        if (Lmax[0] > Rmax[0]) return Lmax;
        else return Rmax;
    }

    //(3)、动态规划
    //如果加上当前值后,依然是和最大子段,则dp理所当然更新,否则dp变为当前单个数值
    public int MaxSum(int[] nums) {
    	if(nums.length==0) return 0;
        int dp[] =new int[nums.length] ;//dp[i]表示以nums[i]结尾的子段的最大子段和
        int cur = nums[0];//记录当前的最大字段和
        dp[0]=nums[0];
        for (int i = 1; i < nums.length; i++) {//dp[i]=max(dp[i-1]+nums[i],nums[i])
            dp[i] = Math.max(dp[i-1] + nums[i], nums[i]);
            cur = Math.max(cur, dp[i]);
        }
        return cur;
    }

双机调度(21考)

用两台处理机 A 和 B 处理n个作业。设第i个作业交给机器 A 处理时所需要的时间是 a i a_{i} ai,若由机器 B 来处理,则所需要的时间是 b i b_{i} bi。现在要求每个作业只能由一台机器处理,每台机器都不能同时处理两个作业。设计一个动态规划算法,使得这两台机器处理完这n个作业的时间最短(从任何-台机器开工到最后一台机器停工的总的时间)。
证明最优子结构:
设{ x 1 , x 2 , … , x j x_{1},x_{2},…,x_{j} x1,x2,,xj}是以 x j x_{j} xj为最后作业问题的最优解,原问题可表述为求
min{ ∑ 0 ≤ i ≤ j j ( a i x i + b i ( 1 − x i ) ) \sum_{0\le i\le j}^{j}(a_{i}x_{i}+b_{i}(1-x_{i})) 0ijj(aixi+bi(1xi))}
证明{ x 1 , x 2 , … , x j − 1 x_{1},x_{2},…,x_{j-1} x1,x2,,xj1}是子问题以输入为A={ x 1 , x 2 , … , x j − 1 x_{1},x_{2},…,x_{j-1} x1,x2,,xj1}的问题的最优解,
假设存在不等于{ x 1 , x 2 , … , x j − 1 x_{1},x_{2},…,x_{j-1} x1,x2,,xj1}的{ x ′ 1 , x ′ 2 , … , x ′ j − 1 x\prime_{1},x\prime_{2},…,x\prime_{j-1} x1,x2,,xj1}是子问题的最优解,
则min{ ∑ 0 ≤ i ≤ j j − 1 ( a i x ′ i + b i ( 1 − x ′ i ) ) \sum_{0\le i\le j}^{j-1}(a_{i}x\prime_{i}+b_{i}(1-x\prime_{i})) 0ijj1(aixi+bi(1xi))} < min{ ∑ 0 ≤ i ≤ j j − 1 ( a i x i + b i ( 1 − x i ) ) \sum_{0\le i\le j}^{j-1}(a_{i}x_{i}+b_{i}(1-x_{i})) 0ijj1(aixi+bi(1xi))},
则min{ ∑ 0 ≤ i ≤ j j − 1 ( a i x ′ i + b i ( 1 − x ′ i ) ) + a j x j + b j ( 1 − x j \sum_{0\le i\le j}^{j-1}(a_{i}x\prime_{i}+b_{i}(1-x\prime_{i}))+a_{j}x_{j}+b_{j}(1-x_{j} 0ijj1(aixi+bi(1xi))+ajxj+bj(1xj} < min{ ∑ 0 ≤ i ≤ j j − 1 ( a i x i + b i ( 1 − x i ) ) + a j x j + b j ( 1 − x j \sum_{0\le i\le j}^{j-1}(a_{i}x_{i}+b_{i}(1-x_{i}))+a_{j}x_{j}+b_{j}(1-x_{j} 0ijj1(aixi+bi(1xi))+ajxj+bj(1xj},
则存在{ x ′ 1 , x ′ 2 , … , x ′ j − 1 , x j x\prime_{1},x\prime_{2},…,x\prime_{j-1},x_{j} x1,x2,,xj1,xj}是问题的可行解,
这与{ x 1 , x 2 , … , x j x_{1},x_{2},…,x_{j} x1,x2,,xj}是以 x j x_{j} xj为最后元素的问题的最优解矛盾。

public int twoMachineScheduling(int[] a, int[] b) {
        int n = a.length;
        int suma = 0;//机器a最多需要suma的时间
        for (int i : a) suma += i;
        // 初始化动态规划数组
        int[][] F = new int[n + 1][];// F[i][j] 表示前i个作业,机器B最小工作时间为j时的总时间
        for (int i = 0; i <= n; i++) {
            F[i] = new int[suma]; // 机器a最多需要suma的时间
            for (int j = 0; j < F[i].length; j++) {
                F[i][j] = Integer.MAX_VALUE; // 初始化为一个较大的值
            }
        }
        F[0][0] = 0;// 初始条件
        for (int k = 1; k <= n; k++) {// 动态规划递推
            for (int x = 0; x < F[k].length; x++) {
                if (x >= b[k - 1]) {// 如果机器B可以在时间x处理第k个作业
                    F[k][x] = Math.min(F[k][x], F[k - 1][x - b[k - 1]]);
                }
                if (x >= a[k - 1]) {// 如果机器A可以在时间x处理第k个作业,那么机器B需要在时间x - a[k - 1]处理
                    F[k][x] = Math.min(F[k][x], F[k - 1][x - a[k - 1]] + b[k - 1]);
                }
            }
        }

        // 寻找最小时间
        int minTime = Integer.MAX_VALUE;
        for (int x = 0; x < F[n].length; x++) {
            minTime = Math.min(minTime, Math.max(x, F[n][x]));
        }
        return minTime;
    }

整数线性规划

设计动态规划算法,求: m a x ∑ i = 1 n c i x i , ∑ i = 1 n a i x i ≤ b , x i ∈ max\sum_{i=1}^{n}c_{i}x_{i}, \sum_{i=1}^{n}a_{i}x_{i} \le b, x_{i}\in maxi=1ncixii=1naixib,xi { 0,1,2} , 1 ≤ i ≤ n 1\le i \le n 1in
证明最优子结构:
y 1 , y 2 , ⋅ ⋅ ⋅ , y n y_{1},y_{2},···,y_{n} y1,y2,⋅⋅⋅yn是原问题的一个最优解,则 y 1 , y 2 , ⋅ ⋅ ⋅ , y n − 1 y_{1},y_{2},···,y_{n-1} y1,y2,⋅⋅⋅yn1是下面子问题的最优解:
m a x ∑ i = 1 n − 1 c i x i , S . T . ∑ i = 1 n − 1 a i x i ≤ b − a n y n , x i ∈ max\sum_{i=1}^{n-1} c_{i} x_{i} , S.T. \sum_{i=1}^{n-1}a_{i}x_{i}\le b-a_{n}y_{n} , x_{i}\in maxi=1n1cixi,S.T.i=1n1aixibanyn,xi {1,2,3}, 1 ≤ i ≤ n − 1 1\le i \le n-1 1in1
如不然, y ′ 1 , y ′ 2 , ⋅ ⋅ ⋅ , y ′ n − 1 y\prime_{1},y\prime_{2},···,y\prime_{n-1} y1,y2,⋅⋅⋅yn1是子问题的最优解,
∑ i = 1 n − 1 c i y ′ i > ∑ i = 1 n − 1 c i y i \sum_{i=1}^{n-1} c_{i} y\prime_{i} > \sum_{i=1}^{n-1} c_{i} y_{i} i=1n1ciyi>i=1n1ciyi,
∑ i = 1 n − 1 a i y ′ i + a n y n ≤ b \sum_{i=1}^{n-1}a_{i}y\prime_{i}+a_{n}y_{n} \le b i=1n1aiyi+anynb, y ′ 1 , y ′ 2 , ⋅ ⋅ ⋅ , y ′ n − 1 , y n y\prime_{1},y\prime_{2},···,y\prime_{n-1}, y_{n} y1,y2,⋅⋅⋅yn1yn将是原问题的可行解,
∑ i = 1 n − 1 c i y ′ i + c n y n > ∑ i = 1 n c i y i \sum_{i=1}^{n-1}c_{i}y\prime_{i}+c_{n}y_{n} > \sum_{i=1}^{n}c_{i}y_{i} i=1n1ciyi+cnyn>i=1nciyi y 1 , y 2 , ⋅ ⋅ ⋅ , y n y_{1},y_{2},···,y_{n} y1,y2,⋅⋅⋅yn是原问题最优解矛盾。
递推公式:设m[k][x]表示容量约束x,可装入1,2,…k件物品的最优解,则
m[k][x]=max{m[k-1][x],m[k-1][x-ak]+ck,m[k-1][x-2ak]+2ck} ,0≤x≤b
m[0][x]=0,if x>=0; m[0][x]=-∞,ifx<0; m[k][<0]= -∞,k=1,2,…n

/*    3、该题目属于多重背包问题,即每个物品最多可取2个x={0,1,2}
        c数组表示物品价值
        a数组表示物品重量
        b为背包容量
        方法一:将物品集合展开 有多少个物品就在物品集合里放多少次
        方法二:再遍历一次数量集合x*/
    /*
     * 接收4个变量
     * c数组表示物品价值
     * a数组表示物品重量
     * b为背包容量
     * x数组表示每个物品有多少个
     * 返回一个背包可容纳的最大价值int
     * dp[j]表示背包当前价值  dp[j]=max(dp[j], dp[j - k * a[i]] + k * c[i])
     *
     * 嵌套三个for循环,最内层为物品数量k,次内层背包容量b,最外层为物品种类a,故时间复杂度为O(a*b*k)
     * 本题目中k为2,故时间复杂度为O(a*b)
     */
    public int multiPackage(int[] a, int[] c, int[] x, int b) {
        int dp[] = new int[b + 1];
        for (int i = 0; i < a.length; i++) {//遍历物品
            for (int j = b; j >= a[i]; j--) {//从后向前遍历背包容量
                //遍历物品数量,数量从1开始增加,直到数量用完或者背包装不下该物品
                for (int k = 1; k <= x[i] && j - k * a[i] >= 0; k++) {
                    dp[j] = Math.max(dp[j], dp[j - k * a[i]] + k * c[i]);
                }
            }
        }
        return dp[b];
    }

可靠性设计:

一个系统由 n 级设备串联而成,为了增强可靠性,每级都可能并联了不止一台同样的设备。假设第 i 级设备 D i D_{i} Di , 用了 m i m_{i} mi台,该级设备的可靠性是 g i ( m i ) g_{i}(m_{i}) gi(mi),则这个系统的可靠性是 ∏ g i ( m i ) \prod g_{i}(m_{i}) gi(mi)。一般来说 ∏ g i ( m i ) \prod g_{i}(m_{i}) gi(mi)都是递增函数,所以每级用的设备越多系统的可靠性越高。但是设备都是有成本的,假定设备 D i D_{i} Di , 的成本是 c i c_{i} ci,设计该系统允许的投资不超过c,那么,该如何设计该系统 (即各级采用多少设备)使得这个系统的可靠性最高。试设计一个动态规划算法求解可靠性设计问题。
证明最优子结构:
问题可描述为: m a x ∏ 1 ≤ i ≤ n g i ( m i ) , s . t . ∑ i = 1 n m i c i ≤ C 问题可描述为:max\prod_{1\le i \le n }{g_{i}(m_{i})}, s.t . \sum_{i=1}^{n}{m_{i}c_i}\le C 问题可描述为:max1ingi(mi),s.t.i=1nmiciC
证明问题满足最优子结构性质:
设 m 1 , m 2 , … m n − 1 , m n 是原问题的最优解, 则其可靠性为 ∏ 1 ≤ i ≤ n g i ( m i ) = g n ( m n ) ∏ 1 ≤ i ≤ n − 1 g i ( m i ) , 设m_{1},m_{2},…m_{n-1},m_{n}是原问题的最优解,\\ 则其可靠性为\prod_{1\le i \le n}{g_{i}(m_{i})}=g_{n}(m_{n})\prod_{1\le i \le n-1}{g_{i}(m_{i})}, m1,m2,mn1,mn是原问题的最优解,则其可靠性为1ingi(mi)=gn(mn)1in1gi(mi),
证明 m 1 , m 2 , … m n − 1 是下面问题的最优解: m a x ∏ 1 ≤ i ≤ n − 1 g i ( m i ) , s . t . ∑ i = 1 n − 1 m i c i ≤ C − m n c n 证明m_{1},m_{2},…m_{n-1}是下面问题的最优解:\\ max\prod_{1\le i \le n-1}{g_i(m_i)}, s.t . \sum_{i=1}^{n-1}{m_ic_i}\le C-m_{n}c_{n} 证明m1,m2,mn1是下面问题的最优解:max1in1gi(mi),s.t.i=1n1miciCmncn
假设 m ′ 1 , m ′ 2 , … m ′ n − 1 是子问题最优解, 则 ∏ 1 ≤ i ≤ n − 1 g i ( m ′ i ) > ∏ 1 ≤ i ≤ n − 1 g i ( m i ) , 则 ∏ 1 ≤ i ≤ n − 1 g i ( m ′ i ) + g n ( m n ) ≤ C , m ′ 1 , m ′ 2 , … m ′ n − 1 , m n 是原问题的可行解, 而 ∏ 1 ≤ i ≤ n − 1 g i ( m ′ i ) + g n ( m n ) > ∏ 1 ≤ i ≤ n g i ( m i ) , 与 m 1 , m 2 , … m n − 1 , m n 是最优解矛盾 , 故假设不成立 假设m\prime_{1},m\prime_{2},…m\prime_{n-1}是子问题最优解,\\则\prod_{1 \le i \le n-1}{g_{i}(m\prime_{i})} > \prod_{1 \le i \le n-1}{g_{i}(m_{i})} ,\\则\prod_{1\le i \le n-1}{g_{i}(m\prime_{i})}+g_{n}(m_{n})\le C,m\prime_{1},m\prime_{2},…m\prime_{n-1},m_{n}是原问题的可行解,\\ 而\prod_{1\le i \le n-1}{g_{i}({m\prime}_{i})}+g_{n}(m_{n})>\prod_{1\le i\le n }{g_{i}(m_{i})},与m_{1},m_{2},…m_{n-1},m_{n}是最优解矛盾,故假设不成立 假设m1,m2,mn1是子问题最优解,1in1gi(mi)>1in1gi(mi),1in1gi(mi)+gn(mn)Cm1,m2,mn1,mn是原问题的可行解,1in1gi(mi)+gn(mn)>1ingi(mi),m1,m2,mn1mn是最优解矛盾,故假设不成立
设M[k][x]为成本x,前k级设备串联所得最优可靠性值,
则m[k][x]=max{m[k-1][x-mkck]×gk(mk)},1≤mk≤x/ck
m[0][x]=1,x>=0; m[k][ck]=1。

/*
     * 接收4个变量
     * n数组表示物品重量
     * m数组表示物品数量
     * g数组表示物品价值
     * c为背包容量
     * 返回一个背包可容纳的最大价值int
     * dp[j]表示背包当前价值  dp[j]=max(dp[j], dp[j - k * n[i]] * g[k]);
     *
     * 嵌套三个for循环,最内层为物品数量m,次内层背包容量c,最外层为物品n,故时间复杂度为O(m*c*n)
     *
     */
    public int SystemReliability(int[] n, int[] g, int[] m, int c) {
        int dp[] = new int[c + 1];
        for (int i = 0; i < n.length; i++) {//遍历物品
            for (int j = c; j >= n[i]; j--) {//从后向前遍历背包容量
                //遍历物品数量,数量从1开始,直到数量用完或者背包装不下
                for (int k = 1; k <= m[i] && j - k * n[i] >= 0; k++) {
                    dp[j] = Math.max(dp[j], dp[j - k * n[i]] * g[k]);
                }
            }
        }
        return dp[c];
    }

最长单调递增子序列:

设A={ x 1 , x 2 , … , x n x_{1},x_{2},…,x_{n} x1,x2,,xn}是n个不等的整数构成的序列,A的一个单调递增子序列是序列{ x i 1 , x i 2 , … , x i k x_{i1},x_{i2},…,x_{ik} xi1,xi2,,xik}, i 1 < i 2 < … < i k i1i1<i2<<ik,且 x i 1 < x i 2 < … < x i k x_{i1}xi1<xi2<<xik,子序列含有k个整数。例如,A={1,5,3,8,10,6,4,9},它的长度为4的递增子序列是:{1,5,8,10},{1,5,8,9},……。设计一个算法,求A的一个最长的单调递增子序列,分析算法的时间复杂度。对输入实例A={2,8,4,-4,5,9,11},给出算法的计算过程和最后的解。
证明最优子结构:
设{ x i 1 , x i 2 , … , x i j x_{i1},x_{i2},…,x_{ij} xi1,xi2,,xij}是以 x i j x_{ij} xij为最后元素的问题的最优解,
证明{ x i 1 , x i 2 , … , x i j − 1 x_{i1},x_{i2},…,x_{ij-1} xi1,xi2,,xij1}是子问题以输入为A={ x 1 , x 2 , … , x i j − 1 x_{1},x_{2},…,x_{ij-1} x1,x2,,xij1}的问题的最优解,
假设存在不等于{ x 1 , x 2 , … , x i j − 1 x_{1},x_{2},…,x_{ij-1} x1,x2,,xij1}的{ x ′ i 1 , x ′ i 2 , … , x ′ i j − 1 x\prime_{i1},x\prime_{i2},…,x\prime_{ij-1} xi1,xi2,,xij1}是子问题的最优解,
x ′ i 1 < x ′ i 2 < … < x ′ i j − 1 x\prime_{i1}xi1<xi2<<xij1,于是 x ′ i j − 1 < x i j x\prime_{ij-1}xij1<xij
则{ x ′ i 1 , x ′ i 2 , … , x ′ i j − 1 , x i j x\prime_{i1},x\prime_{i2},…,x\prime_{ij-1},x_{ij} xi1,xi2,,xij1,xij}是问题的可行解,
这与{ x i 1 , x i 2 , … , x i j x_{i1},x_{i2},…,x_{ij} xi1,xi2,,xij}是以 x i j x_{ij} xij为最后元素的问题的最优解矛盾

算法复杂度:对每个i需要检索比i小的所有j,需O(n)时间,i取值n种,所以T(n)=O(n2)。

/*
     * dp[i]表示以nums[i]结尾的最长单调递增子序列长度
     * 对所有的dp[i]初始化为1
     * 两层for循环,时间复杂度为O(n^2)
     * */
    public int[] riseSbuSeq(int[] nums) {
        int dp[] = new int[nums.length];
        for (int i = 0; i < nums.length; i++) {
            dp[i] = 1;//每次先将以nums[i]结尾的最大增序列长度dp[i]初始化为1
            for (int j = 0; j < i; j++) {
                if (nums[j] < nums[i]) {//当前值需要与之前所有值比较,若大于则比较dp
                    // 大于dp则+1,否则与dp[i]相同
                    dp[i] = dp[j] + 1 > dp[i] ? dp[j] + 1 : dp[i];
                }
            }
        }
        int maxLength = dp[0];//记录最长子序列长度
        int index = 0;//记录最长子序列结尾元素下标

        for (int i = 0; i < dp.length; i++) {
            if (maxLength < dp[i]) {
                maxLength = dp[i];
                index = i;
            }
        }

        int seq[] = new int[maxLength];    //创建数组用于保存最长增子序列
        seq[--maxLength] = nums[index];    //先找到最大值,也就是最大增子序列的最后一个
        for (int i = index; i >= 0; i--) {    //逆序寻找子结构,然后构成所求子序列
            //i从最大增子序列的最后一个元素的下标index开始,反向查找
            if (nums[i] < nums[index] && dp[i] == dp[index] - 1) {
                //满足前一项小于后一项,并且前一项的dp[]等于后一项的dp[]+1
                seq[--maxLength] = nums[i];
                index = i; //满足条件则赋值,并且将index向前移动。
            }
        }
        return seq;
    }

机器最大效益:

有n项作业集合J={1,2,3,…n},每项作业i的加工时间为t(i),t(1)≤t(2) ≤…≤t(n),获得收益v(i),任务的结束时间为。一个可行的调度是对J的子集A中任务的一个安排,对于i∈A,f(i)是开始时间,满足:f(i)+t(i) ≤ f(j) 或 f(j) + t(j) ≤ f(i), j ≠ i,i,j∈A; 机器从0时刻启动,只要有作业就不闲置,求具有最大效益的调度。给出算法和复杂度分析。
y 1 , y 2 , ⋅ ⋅ ⋅ , y j y_{1},y_{2},···,y_{j} y1,y2,⋅⋅⋅yj是原问题的一个最优解,
m a x ∑ i = 1 j v i , S . T . ∑ i = 1 j t i ≤ D , 1 ≤ i ≤ n − 1 max\sum_{i=1}^{j} v_{i} , S.T. \sum_{i=1}^{j}t_{i} \le D ,1\le i \le n-1 maxi=1jvi,S.T.i=1jtiD,1in1
a 1 , a 2 , ⋅ ⋅ ⋅ , a n − 1 a_{1},a_{2},···,a_{n-1} a1,a2,⋅⋅⋅an1是下面子问题的最优解: m a x ∑ i = 1 j − 1 v i , S . T . ∑ i = 1 j − 1 t i ≤ D , max\sum_{i=1}^{j-1} v_{i} , S.T. \sum_{i=1}^{j-1}t_{i}\le D, maxi=1j1vi,S.T.i=1j1tiD,
假设存在 a ′ 1 , a ′ 2 , ⋅ ⋅ ⋅ , a ′ n − 1 a\prime_{1},a\prime_{2},···,a\prime_{n-1} a1,a2,⋅⋅⋅an1是子问题的最优解,
∑ i = 1 j − 1 v ′ i > ∑ i = 1 j − 1 v i \sum_{i=1}^{j-1} v\prime_{i} > \sum_{i=1}^{j-1} v_{i} i=1j1vi>i=1j1vi,
∑ i = 1 j − 1 v i + v n ≤ D \sum_{i=1}^{j-1}v_{i}+v_{n} \le D i=1j1vi+vnD, a ′ 1 , a ′ 2 , ⋅ ⋅ ⋅ , a ′ n − 1 , a n a\prime_{1},a\prime_{2},···,a\prime_{n-1}, a_{n} a1,a2,⋅⋅⋅an1an将是原问题的可行解,
∑ i = 1 j − 1 v ′ i + v n > ∑ i = 1 j v i \sum_{i=1}^{j-1}v\prime_{i}+v_{n} > \sum_{i=1}^{j}v_{i} i=1j1vi+vn>i=1jvi a 1 , a 2 , ⋅ ⋅ ⋅ , a n a_{1},a_{2},···,a_{n} a1,a2,⋅⋅⋅an是原问题最优解矛盾。

/*
     * 背包问题
     * n项作业,加工时间数组t[]为物品重量,且为升序排列
     * 结束时间D为背包容量
     * 收益数组v为物品价值
     * 物品数量均为1
     * dp[i]表示i时刻所能获得的最大收益
     * dp[j] = max(dp[j],dp[j-t[i]]+v[i])
     *
     * 算法仅遍历一遍,故时间复杂度为O(n)
     * */

    public int maxProfit(int[] t, int[] v, int D) {
        int dp[] = new int[D];//初始化为0
        for (int i = 0; i < t.length; i++) {
            for (int j = D - 1; j - t[i] > 0; j--) {
                dp[j] = Math.max(dp[j], dp[j - t[i]] + v[i]);
            }
        }
        return dp[D - 1];
    }

凸多边形划分三角形:

设A是顶点为1,2,…n的凸多边形,可以用不在内部相交的n-3条对角线将A划分成三角形,如下图是5边形的所有划分方案。假设凸n边形的边及对角线的长度dij都是给定的正整数,1≤i

/*
     * 输入一个二维数组d[n][n],d[i][j]表示顶点i到顶点j的距离,d[i][i]=0,即到自身距离为0
     * 输出一个数表示最小权值和
     * dp[i][j]表示从顶点i到顶点j进行三角剖分所需的费用
     * 三层嵌套for循环,每层复杂度均为n,故时间复杂度为O(n^3)
     * */

    public int minWeight(int[][] d) {
        int[][] dp = new int[d.length][d[0].length];
        for (int i = d.length - 1; i >= 0; i--) {
            for (int j = 0; j < d[0].length; j++) {
                if (j - i == 1) {
                    dp[i][j] = 0;
                    continue;
                }
                dp[i][j] = Integer.MAX_VALUE;
                for (int k = i + 1; k < j; k++) {
                    dp[i][j] = Math.min(dp[i][j], dp[i][k] + dp[k][j] + d[i][k] + d[k][j] + d[i][j]);
                }
            }
        }
        return dp[0][d[0].length - 1];
    }

3. 回溯/分支限界

分派问题: n人分派n个工作(22考)

给n个人分派n件工作,给第i人分派第j件工作的成本是C(i,j) ,试用分枝限界法求成本最小的工作分配方案。

class Node implements Comparable<Node> {
    int v;          //已消耗成本
    int lb;         //目标函数值
    int c;          //执行人
    int t;          //任务
    boolean[] visited; //标记已分配任务

    public Node(int v, int lb, int c, int t, boolean[] visited) {
        this.v = v;
        this.lb = lb;
        this.c = c;
        this.t = t;
        this.visited = visited.clone();
    }

    @Override
    public int compareTo(Node p) {
        return Integer.compare(p.lb, this.lb);
    }
}

public class TaskAssignment {
    static final int maxtask = 10;
    static final int INF = 1000;
    static int[][] task = new int[maxtask][maxtask]; //员工-任务表
    static int n; //任务数
    static int down; //下界
    static int up; //上界
    static PriorityQueue<Node> pp = new PriorityQueue<>();

    public static void get_down() {
        down = 0;
        for (int i = 0; i < n; i++) {
            int min = INF;
            for (int j = 0; j < n; j++) {
                if (task[i][j] < min)
                    min = task[i][j];
            }
            down += min;
        }
    }

    public static void get_up() {
        up = 0;
        boolean[] visited = new boolean[n];
        int flag = -1;
        for (int i = 0; i < n; i++)
            visited[i] = false;
        for (int i = 0; i < n; i++) {
            int min = INF;
            for (int j = 0; j < n; j++) {
                if (!visited[j] && task[i][j] < min) {
                    min = task[i][j];
                    flag = j;
                }
            }
            up += min;
            visited[flag] = true;
        }
    }

    public static int get_lb(Node node) {
        node.lb = node.v;
        for (int i = node.c + 1; i < n; i++) {
            int min = INF;
            for (int j = 0; j < n; j++) {
                if (task[i][j] < min)
                    min = task[i][j];
            }
            node.lb += min;
        }
        return node.lb;
    }

    public static void solve() {
        get_down();
        get_up();
        Node first = new Node(0, down, -1, -1, new boolean[n]); //初始化第一个节点
        pp.add(first);
        int ret = INF;
        while (!pp.isEmpty()) {
            Node tmp = pp.poll();
            if (tmp.c == n - 2) {
                int min = INF;
                for (int i = 0; i < n; i++) {
                    if (!tmp.visited[i] && task[n - 1][i] < min)
                        min = task[n - 1][i];
                }
                int ans = tmp.v + min;
                if (ans <= tmp.lb) { //总成本小于所有目标函数值则该值为最优解
                    ret = ans;
                    break;
                } else {
                    if (ans < up) //更新上界
                        up = ans;
                    if (ans < ret)
                        ret = ans;
                    continue;
                }
            }
            for (int j = 0; j < n; j++) {
                if (!tmp.visited[j]) {
                    Node next = new Node(tmp.v + task[tmp.c + 1][j], 0, tmp.c + 1, j, tmp.visited);
                    next.visited[j] = true;
                    next.lb = get_lb(next);
                    if (next.lb <= up) {
                        pp.add(next);
                    }
                }
            }
        }
        System.out.println("最优解为:" + ret);
    }

    public static void main(String[] args) {
        System.out.print("请输入任务数:");
        n = new java.util.Scanner(System.in).nextInt();
        System.out.println("请录入工作信息,行(员工),列(任务):");
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                task[i][j] = new java.util.Scanner(System.in).nextInt();
            }
        }
        solve();
    }
}

最佳调度问题: n个作业k台机器(21考)

假设有n 个任务要由k 个可并行工作的机器来完成,完成任务需要的时间为ti 。试设计一个分枝限界算法,找出完成这n 个任务的最佳调度,使得完成全部任务的时间最短。请使用回溯法或者是分支限界法来设计算法解决这个问题,要求写出剪枝过程。

Latin

一个4阶Latin方是一个4X4的方格,在它的每个方格内填入1,2,3或4,并使得每个数字在每行、每列都恰好出现一次。用回溯法求出所有第一行为1,2,3,4的所有4阶Latin方。将每个解的第2行到第4行的数字从左到右写成一个序列。


public class Latin {
    List<List<Integer>> lists = new ArrayList<>();

    public List<List<Integer>> latin(int[] nums) {
        int n = nums.length;
        List<Integer> list = new ArrayList<>();
        for (int num : nums) {
            list.add(num);
        }
        backtracking(nums, n, list);
        return lists;
    }

    private void backtracking(int[] nums, int n, List<Integer> list) {
        if (list.size() == n * n) {
            lists.add(new ArrayList<>(list));
            return;
        }
        for (int i = 1; i <= n; i++) {
            if (!check(i, list, n)) continue;
            list.add(i);
            backtracking(nums, n, list);
            list.remove(list.size() - 1);
        }
    }

    private boolean check(int i, List<Integer> list, int n) {
        int row = list.size();
        int line = list.size();
        while (row - n >= 0) {
            if (list.get(row - n) == i) return false;
            else row = row - n;
        }
        while (line % n != 0) {
            if (list.get(--line) == i) return false;
        }
        return true;
    }

    @Test
    public void TestLatin() {
        int[] nums = {1, 2, 3, 4};
        for (List<Integer> list : latin(nums)) {
            System.out.println(list);
        }
//        System.out.println();
    }

}

恰好覆盖问题:

设给定有限集A={a1,a2,…,an}和A的子集的集合W={S1,S2,…,Sm} 。求子集W的子集U, 使得U中的子集都不相交且它们的并集等于A。求满足条件的所有子集U。

    List<List<List<Integer>>> listList = new ArrayList<>();

    public void exactlyToCoverTheProblem(List<Integer> A, List<List<Integer>> W) {
        List<List<Integer>> lists = new ArrayList<>();
        Set<Integer> set = new HashSet<>();
        backtracking(A.size(), W, 0, lists, set);
        System.out.println(listList);

    }

    private void backtracking(int size, List<List<Integer>> w, int index, List<List<Integer>> lists, Set<Integer> set) {
        if (set.size() == size) {
            listList.add(new ArrayList<>(lists));
            return;
        }
        for (int i = index; i < w.size(); i++) {
            Set<Integer> add = new HashSet<>(w.get(i));
            int sum = add.size() + set.size();
            add.addAll(set);
            if (add.size() < sum) continue;
            lists.add(w.get(i));
            backtracking(size, w, i + 1, lists, add);
            lists.remove(lists.size() - 1);
        }
    }

TSP

假设对称旅行商问题的邻接矩阵如图 1 所示,试用优先队列式分枝限界算法给出最短环游。画出解空间树的搜索图,并说明搜索过程。


public class TravelingSalesmanProblem {
    public int travelingSalesmanProblem(int[][] graph) {
        int[][] consume = new int[graph.length][graph.length];
        List<Integer> list = new ArrayList<>();
        backtracking(graph, consume, list, 0, 0);
        int max = Integer.MIN_VALUE;
        int min = Integer.MAX_VALUE;
        for (int[] ints : consume) {
            for (int i : ints) {
                max = Math.max(max, i);
                min = Math.min(min, i);
            }
        }
        System.out.println(min);
        return max;
    }

    private void backtracking(int[][] graph, int[][] consume, List<Integer> list, int index, int sum) {
        if (list.size() == graph.length) {
//            System.out.println(list);
            consume[index-1][list.get(0)] = sum + graph[index-1][list.get(0)];
            sum = 0;
        }
        for (int i = 0; i < graph.length; i++) {
            if (list.contains(i))continue;
            list.add(i);
            sum += graph[list.get(0)][i];
            backtracking(graph, consume, list, i+1, sum);
            sum -= graph[list.get(0)][i];
            list.remove(list.size() - 1);
        }
    }
}

0/1 背包

试写出 0/1 背包问题的队列式分枝限界算法程序,并找一个物品个数是16 的例子检验程序的运行情况。

    class Node implements Comparable<Node> {
        int currWeight;// 当前重量
        double currValue;// 当前价值
        double currBound;// 放入当前物品可能得到的价值上限
        int level;// 当前层级,也即第几个物品的选择
        Node parent;// 父节点
        int Tag;//是否是父亲的左儿子

        public Node() {
        }

        public Node(int currWeight, double currValue, double currBound, int level, Node parent, int tag) {
            this.currWeight = currWeight;
            this.currValue = currValue;
            this.currBound = currBound;
            this.level = level;
            this.parent = parent;
            Tag = tag;
        }

        // 重写compareTo,使得小根堆的堆顶放入当前物品可能得到的价值上限最大
        public int compareTo(Node node) {
            if (this.currBound < node.currBound)
                return 1;
            else if (this.currBound == node.currBound)
                return 0;
            else
                return -1;
        }
    }

    private int bag(int[] weights, int[] values, int totalWeight) {
        List<int[]> list = preProcess(weights, values);//weights和values数组不一定按照单位价值最大排列,此数组对两个数组作预处理
        weights = Arrays.copyOf(list.get(0), weights.length);
        values = Arrays.copyOf(list.get(1), values.length);
        int n = weights.length;
        int[] bestWay = new int[n];
        int maxValue = 0;
        PriorityQueue<Node> pq = new PriorityQueue<Node>();
        //构造一个初始化节点,属于-1层
        Node initial = new Node();
        initial.level = -1;
        int sum = Arrays.stream(values).sum();
        initial.currBound = sum;
        pq.add(initial);

        while (!pq.isEmpty()) {
            Node fatherNode = pq.poll();
            // 当已经搜索到叶子节点时
            if (fatherNode.level == n - 1) {// 如果现在这个节点是叶子节点
                if (fatherNode.currValue > maxValue) {// 并且当前背包的最大价值大于已知的最大价值
                    maxValue = (int) fatherNode.currValue;// 更新最大价值
                    for (int i = n - 1; i >= 0; i--) {// 从后向前遍历
                        bestWay[i] = fatherNode.Tag;// 用数组记录该叶子包含的路径
                        fatherNode = fatherNode.parent;// 向上查找
                        return maxValue;
                    }
                }
            } else {
                // 判断是否要将左节点加入队列,下一个物品放入背包,会不会超载
                if (weights[fatherNode.level + 1] + fatherNode.currWeight <= totalWeight) {// 如果不超载
                    Node newNode = new Node(
                            weights[fatherNode.level + 1] + fatherNode.currWeight,
                            values[fatherNode.level + 1] + fatherNode.currValue,
                            getBound(fatherNode, weights, values, totalWeight),
                            fatherNode.level + 1,
                            fatherNode,
                            1
                    ); //创建一个新的左节点
                    if (newNode.currBound > maxValue)//如果新节点的上限大于已知的最大值,将其加入优先队列
                        pq.add(newNode);
                }
                //不论左节点是否超载,右节点总不会超载,因为右边相当于不取下一个物品,其能够取到的价值上界通过父亲节点的上界减去本层物品的价值。
                if ((fatherNode.currBound - values[fatherNode.level + 1]) > maxValue) {
                    Node newNodeRight = new Node(
                            fatherNode.currWeight,
                            fatherNode.currValue,
                            fatherNode.currBound - values[fatherNode.level + 1],
                            fatherNode.level + 1,
                            fatherNode,
                            0
                    ); //创建一个新的左节点
                    pq.add(newNodeRight);
                }

            }
        }
        return -1;
    }

    private List<int[]> preProcess(int[] weights, int[] values) {
        class Pair implements Comparable<Pair> {
            int w;
            int v;

            public Pair(int w, int v) {
                this.w = w;
                this.v = v;
            }

            public Pair() {
            }

            @Override
            public int compareTo(Pair o) {
                return this.v / this.w - o.v / o.w;
            }
        }

        int n = weights.length;
        Pair[] p = new Pair[n];
        for (int i = 0; i < n; i++) p[i] = new Pair(weights[i], values[i]);
        Arrays.sort(p);

        for (int i = 0; i < n; i++) {
            weights[i] = p[i].w;
            values[i] = p[i].v;
        }
        List<int[]> list = new ArrayList<>();
        list.add(weights);
        list.add(values);
        System.out.println(list);
        return list;


    }

    private double getBound(Node node, int[] weights, int[] values, int totalWeight) {
        double maxLeft = node.currValue + values[node.level + 1];
        int leftWeight = totalWeight - node.currWeight - weights[node.level + 1];
        int templevel = node.level + 1;
        int n = weights.length;
        //尽力依照单位重量价值次序装剩余的物品
        while (templevel <= n - 1 && leftWeight > weights[templevel]) {
            leftWeight -= weights[templevel];
            maxLeft += values[templevel];
            templevel++;
        }
        //不能装时,用下一个物品的单位重量价值折算到剩余空间。
        if (templevel <= n - 1) {
            maxLeft += values[templevel] / weights[templevel] * leftWeight;
        }
        return maxLeft;
    }

你可能感兴趣的:(期末,算法)