本文参考:国科大刘玉贵老师计算机算法设计与分析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))
基本思想:禁忌搜索算法是局部搜索算法的扩展,用一个禁忌表记录下已经到达过的局部最优点,在下一次搜索中,禁忌表中的信息不再或有选择地搜索这些点,以此来跳出局部最优点。
基本思想:模拟退火算法是局部搜索算法的扩展,以一定的概率选择邻域中费用值大的状态(求最小)。理论上是一个全局最优算法。
按照以下步骤进行贪心算法的求解
这种贪心算法的正确性可以通过反证法证明。假设存在一种更优的调度方案,使得所有作业的完成时间更短。那么在这个调度方案中,必然存在某个作业在执行时会被分配到一个执行时间更长的机器上,从而导致整个调度方案时间更长。因此,原来的方案就是最优的。
基于贪心的近似算法步骤如下:
近似比= 2。这个贪心算法找到的解的大小最多是最优解的两倍。
考虑任何一条边 e=(u,v),当这条边被选择时,我们都至少需要选择其中一个顶点来覆盖它。所以,每选择一条边,我们都会覆盖两个顶点(即 u 和 v)。但是,这两个顶点可能重复被其他边覆盖,所以我们最多覆盖两倍的顶点。
例:给定集合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
例:对于给定的无向图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,则每个三角形必须取一个点,因为取两个则不独立,不取则
[注]:不会做,直接背吧,有兴趣的可自行学习
典型NPC问题及其证明
算法设计与分析笔记之独立集问题
【计算理论】计算复杂性 ( 无向图独立集问题 | 独立集问题是 NP 完全问题证明思路 | 证明独立集问题是 NP 完全问题 )
已知带权集合的划分问题是 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} a11x1+a12x2+…a1nxn≤b1,
判定且 C T X ≥ D C^{T}X ≥ D CTX≥D问题,这就是0/1背包判定问题。
0/1背包判定问题是NPC,所以这个特殊整数规划问题也是NPC问题,从而一般的整数规划问题是NPC问题.
综上所述, 0/1 背包判定问题是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(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问题。
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)}
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)}
设有一条边远山区的道路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 其中元素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;
}
有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;
}
设有作业集合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;
}
设有 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;
}
设 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不是最优的,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。
提供的Java代码可能与答案所给不同,谨慎食用
最大字段和问题:给定整数序列 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 max∑k=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,…,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−1x′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−1xik},
则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−1x′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−1xk+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;
}
用两台处理机 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(aixi+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(aix′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(aixi+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(aix′i+bi(1−x′i))+ajxj+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(aixi+bi(1−xi))+ajxj+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=1ncixi,∑i=1naixi≤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−1cixi,S.T.∑i=1n−1aixi≤b−anyn,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−1ciy′i>∑i=1n−1ciyi,
则 ∑ 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−1aiy′i+anyn≤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−1ciy′i+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 问题可描述为:max∏1≤i≤ngi(mi),s.t.∑i=1nmici≤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≤ngi(mi)=gn(mn)∏1≤i≤n−1gi(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−1gi(mi),s.t.∑i=1n−1mici≤C−mncn
假设 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−1gi(m′i)>∏1≤i≤n−1gi(mi),则∏1≤i≤n−1gi(m′i)+gn(mn)≤C,m′1,m′2,…m′n−1,mn是原问题的可行解,而∏1≤i≤n−1gi(m′i)+gn(mn)>∏1≤i≤ngi(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
证明最优子结构:
设{ 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 ′ 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=1jvi,S.T.∑i=1jti≤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−1vi,S.T.∑i=1j−1ti≤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−1v′i>∑i=1j−1vi,
则 ∑ i = 1 j − 1 v i + v n ≤ D \sum_{i=1}^{j-1}v_{i}+v_{n} \le D ∑i=1j−1vi+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−1v′i+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 给n个人分派n件工作,给第i人分派第j件工作的成本是C(i,j) ,试用分枝限界法求成本最小的工作分配方案。 假设有n 个任务要由k 个可并行工作的机器来完成,完成任务需要的时间为ti 。试设计一个分枝限界算法,找出完成这n 个任务的最佳调度,使得完成全部任务的时间最短。请使用回溯法或者是分支限界法来设计算法解决这个问题,要求写出剪枝过程。 一个4阶Latin方是一个4X4的方格,在它的每个方格内填入1,2,3或4,并使得每个数字在每行、每列都恰好出现一次。用回溯法求出所有第一行为1,2,3,4的所有4阶Latin方。将每个解的第2行到第4行的数字从左到右写成一个序列。 设给定有限集A={a1,a2,…,an}和A的子集的集合W={S1,S2,…,Sm} 。求子集W的子集U, 使得U中的子集都不相交且它们的并集等于A。求满足条件的所有子集U。 假设对称旅行商问题的邻接矩阵如图 1 所示,试用优先队列式分枝限界算法给出最短环游。画出解空间树的搜索图,并说明搜索过程。 试写出 0/1 背包问题的队列式分枝限界算法程序,并找一个物品个数是16 的例子检验程序的运行情况。/*
* 输入一个二维数组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考)
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考)
Latin
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();
}
}
恰好覆盖问题:
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
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 背包
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;
}