国科大刘玉贵老师 2023算法设计与分析速通期末考试
本文参考:国科大刘玉贵老师计算机算法设计与分析2021年期末
国科大2022计算机算法设计与分析期末考试-刘玉贵老师
一、填空
-
下面说法,正确的是:(1,3).
(1)P类问题是存在多项式时间算法的问题。 (2)NP类问题是不存在多项式时间算法的问题。
(3)P类问题一定也是NP类问题。 (4)NP类问题比P类问题难解。 -
下面说法,正确的是:(2).
(1)P ? \subset ?NP (2)P ? \subseteq ?NP (3)P=NP (4)P≠NP -
下面说法,正确的是:(2,3,4).
(1)NP-难问题是NP中最难的问题
(2)NP-完全问题是NP中最难的问题
(3)NP-难不比任何NP问题容易
(4)NP-完全问题也是NP-难问题。 -
下面属于模拟退火算法实现的关键技术问题的有(1,2,3)。
(1)初始温度 (2)温度下降控制 (3)邻域定义 (4)目标函数 -
下面属于遗传算法实现的关键技术问题的有(1,4)。
(1)解的编码 (2)初始种群的选择 (3)邻域定义 (4)适应函数 -
设Las Vegas算法获得解的概率为p(x) ≥ δ \delta δ , 0 < δ \delta δ < 1,则调用 k 次算法后,获得解的概率为: 1 ? ( 1 ? δ ) k 1-(1-\delta)^{k} 1?(1?δ)k。
-
对判定问题 ∏ \prod ∏的Monte Carlo算法,当返回false时解总是正确的,但当返回true时解可能有错误,该算法是(2)。当返回true时解总是正确的,但当返回false时解可能有错误,该算法是(1)。
(1)偏真的Monte Carlo算法 (2)偏假的Monte Carlo算法
(3)一致的Monte Carlo算法 (4)不一致的Monte Carlo算法 -
皇后问题,每一个皇后的位置无任何规律,要求求得一个解即返回。下面合适的方法有(2,4)
(1) 贪心算法 (2)回溯算法
(3)分枝限界算法 (4) Las Vegas 算法 -
为避免陷入局部最优(小),模拟退火算法以概率 e ? Δ f i j t k e^{-\frac{\Delta f_{ij}}{t_{k}}} e?tk?Δ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?越小接受退步解的概率越大。 -
用遗传算法解某些问题,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)等。 -
用非常规编码染色体实现的遗传算法,如TSP问题使用1,2,…,n的排列编码,简单交配会产生什么问题?如何解决?
答:简单交配可能产生非解编码(染色体)。
解决办法:改变交配规则,如交配位后基因按异方基因顺序选取不重复基因、不变位法等。 -
设旅行商问题的解表示为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)} -
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)} -
考虑下面的函数f(n)和g(n) ,比较他们的阶
(1)g(n)=O(f(n)) (2)f(n)=O(g(n))
(3)f(n)=O(g(n)) (4)f(n)=O(g(n))
二、判断
- Las Vegas算法不会得到不正确的解。(√)
- Las Vegas算法总能求得一个解。(×)
- Monte Carlo算法不会得到不正确的解。(×)
- Monte Carlo算法总能求得一个解。(√)
- 一般情况下,无法有效判定Las Vegas算法所得解是否肯定正确。(×)
- 一般情况下,无法有效判定Monte Carlo算法所得解是否肯定正确。(√)
- 虽然在某些步骤引入随机选择,但Sherwood算法总能求得问题的一个解,且所求得的解总是正确的。 (√)
- 虽然在某些步骤引入随机选择,但Sherwood算法总能求得问题的一个解,但一般情况下,无法有效判定所求得的解是否正确。 (×)
[助记]:以下纯为考试记忆,不对算法做过多探讨
Monte Carlo算法是蒙的,所以肯定有答案,就是不知道对错
Las Vegas算法想到last才想出来,一定是对的,但是能不能想出来就另说了
Sherwood算法引入随机性,舍弃了精确性,所以肯定有解,且为正解,但是精确性有待考量 - 旅行商问题存在多项式时间近似方案。( × )
- 0/1背包问题存在多项式时间近似方案。( √ )
- 0/1背包问题的贪心算法(单位价值高优先装入)是绝对近似算法。(× )
- 多机调度问题的贪心近似算法(按输入顺序将作业分配给当前最小负载机器)是 ? \epsilon ?-近似算法。( √ )
- 禁忌搜索中,禁忌某些对象是为了避免邻域中的不可行解。( × )
- 禁忌长度越大越好。( × )
- 禁忌长度越小越好。( × )
三、简答
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}}} e?tk?Δ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?=∑1≤j≤N?fj?(?)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
S′?S,验证
∣
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
c∈C,S′∩c非空可在
∣
C
∣
?
∣
c
∣
?
∣
S
′
∣
=
∣
C
∣
?
n
2
\mid C \mid \ast \mid c \mid \ast \mid S\prime \mid = \mid C \mid \ast n^{2}
∣C∣?∣c∣?∣S′∣=∣C∣?n2内完成,所以问题是多项式可验证的,因此相遇集问题
∈
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
V′,∣V′∣≤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
∈NP?H
综上所述,相遇集问题
∈
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:给定V′?V,∣V′∣=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,则每个三角形必须取一个点,因为取两个则不独立,不取则<m。\\令if 取点对应的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}可满足 综上所述,独立集问题\in NPC
再证3SAT∝独立集问题:对任意Cj?=Z1?∧Z2?∧Z3?,j=1,2,…m,构造以z1?,z2?,z3?为顶点的三角形,若顶点zi?和zl?为某xk?和?xk?则增加连接边,构成图G。若3SAT有成真赋值,对每个Cj?=1,必有某个zl?=1,将该元素对应顶点选入S,S一定是独立集,因为该顶点仅与?zl?相连,?zl?=0,在另一三角形中不会被选中。∣S∣=m。反之,若存在独立集S,∣S∣=m,则每个三角形必须取一个点,因为取两个则不独立,不取则<m。令if取点对应的zi?=xk?,xk?=1;zi?=?xk?,xk?=0,由于xk?与?xk?之间有边,不可能同时取到,上述赋值不矛盾。如果(x1?,x)2,…,x)n)里有未赋值着,可任意赋值。则对任意Cj?=Z1?∧Z2?∧Z3?,取点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
AX≤b和
C
T
X
≤
D
C^{T}X \le D
CTX≤D需计算
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}
a11?x1?+a12?x2?+…a1n?xn?≤b1?,
判定且
C
T
X
≥
D
C^{T}X ≥ D
CTX≥D问题,这就是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)的一个顶点排序v1?v2?..vn?和数L,验证(vi?,vi+1?)∈E和(vn?,v1?)∈E可在O(n?∣E∣)≤O(n3)完成,验证路径和≤L需0(n)时间,所以TSP∈NP问题。
对于
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>1。L=n是G的顶点数,得到旅行商问题实例I。则图G有Hamilton回路当且仅当图G1?有长为n的环游路线。因为,若G有Hamilton回路,则取此回路为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)={0,N<=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)={a,N=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)={a,n<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}<d_{2}<…<d_{n}) 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]<d1+4=a[1]。
B[1]覆盖的是距离在[d1,b[1]+4]之间的房子。A[1]覆盖的是距离[d1,a[1]+4]的房子,
因为b[1]<a[1],b[1]覆盖的房子都在a[1]覆盖的区域内,用a[1]替换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}]<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[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步选择的测试点不能测到的进程构成集合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′′也是原问题的最优解,得证
罚款
设有作业集合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的安排次序是<i1,i2,…,in>,那么罚款为F(f)= ,任给k<=D<j,m(ik)>=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}
t1?≤t2?≤t2?≤…≤tn?,算法的调度 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?,..ik?1?,ik?,ik+1?…in?,必有
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}
f??:1,2…k,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??)=(n?k)tik?+(n?j)tk??(n?k)tk??(n?j)tik?=(j?k)tik?+(k?j)tk?=(j?k)(tik??tk?)>=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=1n?ai?>L,要求选取一个能放在带上的程序的最大子集和(即其中含有最多个数的程序)Q。构造Q的一种贪心策略是按 a i a_{i} ai?的非降序将程序计入集合。
- 证明这一策略的总能找到最大子集Q,使得 ∑ p i = 1 a i > L \sum_{p_{i}=1}a_{i}>L ∑pi?=1?ai?>L。
- 设Q是使用上述贪心算法得到的子集和,磁带的利用率可以小到何种程度?
- 说明问题1中提到的设计策略不一定能使 ∑ p i = 1 a i / L \sum_{p_{i}=1}a_{i}/L ∑pi?=1?ai?/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,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=1j?ak?的子段和的最大值:max{0, max ? ∑ k = 1 j a k , i < = i < = j < = n \max \sum_{k=1}^{j}a_{k},i<=i<=j<=n max∑k=1j?ak?,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?,…,xij?1?}是子问题以输入为A={
x
1
,
x
2
,
…
,
x
i
j
?
1
x_{1},x_{2},…,x_{ij-1}
x1?,x2?,…,xij?1?}的问题的最优解,
假设存在不等于{
x
1
,
x
2
,
…
,
x
i
j
?
1
x_{1},x_{2},…,x_{ij-1}
x1?,x2?,…,xij?1?}的{
x
′
i
1
,
x
′
i
2
,
…
,
x
′
i
j
?
1
x\prime_{i1},x\prime_{i2},…,x\prime_{ij-1}
x′i1?,x′i2?,…,x′ij?1?}是子问题的最优解,
则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,max1≤i≤j≤n?∑k=ij?1?x′ik?} > 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,max1≤i≤j≤n?∑k=ij?1?xik?},
则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,max1≤i≤j≤n?∑k=ij?1?x′ik?+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,max1≤i≤j≤n?∑k=ij?1?xk?+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}
x′i1?,x′i2?,…,x′ij?1?,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}))
∑0≤i≤jj?(ai?xi?+bi?(1?xi?))}
证明{
x
1
,
x
2
,
…
,
x
j
?
1
x_{1},x_{2},…,x_{j-1}
x1?,x2?,…,xj?1?}是子问题以输入为A={
x
1
,
x
2
,
…
,
x
j
?
1
x_{1},x_{2},…,x_{j-1}
x1?,x2?,…,xj?1?}的问题的最优解,
假设存在不等于{
x
1
,
x
2
,
…
,
x
j
?
1
x_{1},x_{2},…,x_{j-1}
x1?,x2?,…,xj?1?}的{
x
′
1
,
x
′
2
,
…
,
x
′
j
?
1
x\prime_{1},x\prime_{2},…,x\prime_{j-1}
x′1?,x′2?,…,x′j?1?}是子问题的最优解,
则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}))
∑0≤i≤jj?1?(ai?x′i?+bi?(1?x′i?))} < 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}))
∑0≤i≤jj?1?(ai?xi?+bi?(1?xi?))},
则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}
∑0≤i≤jj?1?(ai?x′i?+bi?(1?x′i?))+aj?xj?+bj?(1?xj?} < 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}
∑0≤i≤jj?1?(ai?xi?+bi?(1?xi?))+aj?xj?+bj?(1?xj?},
则存在{
x
′
1
,
x
′
2
,
…
,
x
′
j
?
1
,
x
j
x\prime_{1},x\prime_{2},…,x\prime_{j-1},x_{j}
x′1?,x′2?,…,x′j?1?,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
max∑i=1n?ci?xi?,∑i=1n?ai?xi?≤b,xi?∈ { 0,1,2} ,
1
≤
i
≤
n
1\le i \le n
1≤i≤n
证明最优子结构:
设
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?,???,yn?1?是下面子问题的最优解:
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
max∑i=1n?1?ci?xi?,S.T.∑i=1n?1?ai?xi?≤b?an?yn?,xi?∈ {1,2,3},
1
≤
i
≤
n
?
1
1\le i \le n-1
1≤i≤n?1
如不然,
y
′
1
,
y
′
2
,
?
?
?
,
y
′
n
?
1
y\prime_{1},y\prime_{2},···,y\prime_{n-1}
y′1?,y′2?,???,y′n?1?是子问题的最优解,
则
∑
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=1n?1?ci?y′i?>∑i=1n?1?ci?yi?,
则
∑
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=1n?1?ai?y′i?+an?yn?≤b,
y
′
1
,
y
′
2
,
?
?
?
,
y
′
n
?
1
,
y
n
y\prime_{1},y\prime_{2},···,y\prime_{n-1}, y_{n}
y′1?,y′2?,???,y′n?1?,yn?将是原问题的可行解,
且
∑
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=1n?1?ci?y′i?+cn?yn?>∑i=1n?ci?yi?与
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
问题可描述为:max∏1≤i≤n?gi?(mi?),s.t.∑i=1n?mi?ci?≤C
证明问题满足最优子结构性质:
设
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?,…mn?1?,mn?是原问题的最优解,则其可靠性为∏1≤i≤n?gi?(mi?)=gn?(mn?)∏1≤i≤n?1?gi?(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?,…mn?1?是下面问题的最优解:max∏1≤i≤n?1?gi?(mi?),s.t.∑i=1n?1?mi?ci?≤C?mn?cn?
假设
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}是最优解矛盾,故假设不成立
假设m′1?,m′2?,…m′n?1?是子问题最优解,则∏1≤i≤n?1?gi?(m′i?)>∏1≤i≤n?1?gi?(mi?),则∏1≤i≤n?1?gi?(m′i?)+gn?(mn?)≤C,m′1?,m′2?,…m′n?1?,mn?是原问题的可行解,而∏1≤i≤n?1?gi?(m′i?)+gn?(mn?)>∏1≤i≤n?gi?(mi?),与m1?,m2?,…mn?1?,mn?是最优解矛盾,故假设不成立
设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
i1<i2<…<ik
i1<i2<…<ik,且
x
i
1
<
x
i
2
<
…
<
x
i
k
x_{i1}<x_{i2}<…<x_{ik}
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?,…,xij?1?}是子问题以输入为A={
x
1
,
x
2
,
…
,
x
i
j
?
1
x_{1},x_{2},…,x_{ij-1}
x1?,x2?,…,xij?1?}的问题的最优解,
假设存在不等于{
x
1
,
x
2
,
…
,
x
i
j
?
1
x_{1},x_{2},…,x_{ij-1}
x1?,x2?,…,xij?1?}的{
x
′
i
1
,
x
′
i
2
,
…
,
x
′
i
j
?
1
x\prime_{i1},x\prime_{i2},…,x\prime_{ij-1}
x′i1?,x′i2?,…,x′ij?1?}是子问题的最优解,
则
x
′
i
1
<
x
′
i
2
<
…
<
x
′
i
j
?
1
x\prime_{i1}<x\prime_{i2}<…<x\prime_{ij-1}
x′i1?<x′i2?<…<x′ij?1?,于是
x
′
i
j
?
1
<
x
i
j
x\prime_{ij-1}<x_{ij}
x′ij?1?<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}
x′i1?,x′i2?,…,x′ij?1?,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
max∑i=1j?vi?,S.T.∑i=1j?ti?≤D,1≤i≤n?1
则
a
1
,
a
2
,
?
?
?
,
a
n
?
1
a_{1},a_{2},···,a_{n-1}
a1?,a2?,???,an?1?是下面子问题的最优解:
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,
max∑i=1j?1?vi?,S.T.∑i=1j?1?ti?≤D,
假设存在
a
′
1
,
a
′
2
,
?
?
?
,
a
′
n
?
1
a\prime_{1},a\prime_{2},···,a\prime_{n-1}
a′1?,a′2?,???,a′n?1?是子问题的最优解,
则
∑
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=1j?1?v′i?>∑i=1j?1?vi?,
则
∑
i
=
1
j
?
1
v
i
+
v
n
≤
D
\sum_{i=1}^{j-1}v_{i}+v_{n} \le D
∑i=1j?1?vi?+vn?≤D,
a
′
1
,
a
′
2
,
?
?
?
,
a
′
n
?
1
,
a
n
a\prime_{1},a\prime_{2},···,a\prime_{n-1}, a_{n}
a′1?,a′2?,???,a′n?1?,an?将是原问题的可行解,
且
∑
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=1j?1?v′i?+vn?>∑i=1j?vi?与
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<j≤n,划分后三角形ijk的权值等于其周长。求具有最小权值的划分方案,设计一个动态规划算法求解这个问题,分析算法复杂度。(提示:参考矩阵连乘问题)。
/*
* 输入一个二维数组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;
}
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!