贪心算法和拟阵
动态规划能够很好的帮助我们解决很多的最优解问题,但是对于许多的最优解问题,使用动态规划进行求解会显得问题过于复杂,造成复杂度过高。贪心算法和动态规划一样是用于求解最优解问题的方法,它期望通过所做的局部最优解选择来产生一个全局最优解。
活动选择问题
有一系列相互竞争的活动,他们都需要占用同一资源,这意味着在任意时刻都不能够出现两个活动在同时进行,那么如何进行调度,能够在一段时间内进行的活动数目最大呢?
在这里,我们定义活动集合为
A
=
a
1
,
a
2
,
.
.
.
,
a
n
A={a_1,a_2,…,a_n}
A
=
a
1
,
a
2
,
.
.
.
,
a
n
,他们的开始时间和结束时间定义为
s
i
s_i
s
i
和
f
i
f_i
f
i
,那么活动不冲突就是他们的活动区间
[
s
i
,
f
i
)
[s_i,f_i)
[
s
i
,
f
i
)
和
[
s
j
,
f
j
)
[s_j,f_j)
[
s
j
,
f
j
)
不重合。我们先按照活动结束时间进行排序:
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
---|---|---|---|---|---|---|---|---|---|---|---|
s i s_i s i |
1 | 3 | 0 | 5 | 3 | 5 | 6 | 8 | 8 | 2 | 12 |
f i f_i f i |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
我们先用动态规划的方式来求解。我们将按照结束时间单调递增的相互不冲突的活动集合定义为
S
i
j
S_{ij}
S
i
j
,其中第一个活动的开始时间不早于
f
i
−
1
f_{i-1}
f
i
−
1
,最后一个活动的结束时间不晚于
s
j
+
1
s_{j+1}
s
j
+
1
,也就是说
S
i
j
S_{ij}
S
i
j
中的每一个活动的开始时间都大于前一个活动的结束时间,对于一个活动集合
A
i
j
A_{ij}
A
i
j
,我们都可以求出其中相互兼容的活动集合
S
i
j
S_{ij}
S
i
j
。
在
A
i
j
A_{ij}
A
i
j
中,我们可以发现,对于某个活动
a
k
a_k
a
k
,存在
A
i
j
=
A
i
k
∪
a
k
∪
A
k
j
A_{ij}=A_{ik}\cup a_{k}\cup A_{kj}
A
i
j
=
A
i
k
∪
a
k
∪
A
k
j
,同样的,对于
A
i
j
A_{ij}
A
i
j
中相互兼容的活动
S
i
j
S_{ij}
S
i
j
,也存在
S
i
j
=
S
i
k
∪
a
k
∪
S
k
j
S_{ij}=S_{ik}\cup a_{k}\cup S_{kj}
S
i
j
=
S
i
k
∪
a
k
∪
S
k
j
,那么,我们就可以根据集合中每个
a
k
a_k
a
k
,将
A
i
j
A_{ij}
A
i
j
拆分成两个子集合,再分别求出子集合的最大值进行合并,比较每个不同的
a
k
a_k
a
k
拆分的结果,并取最大值即可。在这里定义
A
i
j
A_{ij}
A
i
j
中最大的相互兼容的活动集合的个数为
c
i
j
c_{ij}
c
i
j
,可以得到递归式:
c
i
j
=
{
0
,
A
i
j
为
空
max
i
<
k
<
j
(
c
i
j
k
+
c
k
j
+
1
)
A
i
j
不
为
空
c_{ij} = \begin{cases} 0, & A_{ij}为空 \\ \max_{i<k<j}({c_{ijk+c_{kj}+1}}) & A_{ij}不为空 \\ \end{cases}
c
i
j
=
{
0
,
max
i
<
k
<
j
(
c
i
j
k
+
c
k
j
+
1
)
A
i
j
为
空
A
i
j
不
为
空
我们再用贪心算法来解一下这个问题。在这里,我们进行一系列的选择,每一次都选择能够和之前的选择相兼容的,并且结束时间最早的活动,我们认为这样能够给接下来的选择更大的空,最后得到一个最优解为
a
1
,
a
4
,
a
8
,
a
1
1
{a_1,a_4,a_8,a_11}
a
1
,
a
4
,
a
8
,
a
1
1
,其伪代码如下:
S=
ϕ
S = \phi
S
=
ϕ
将A中的活动按照结束时间从小到大排列
for x in S
if x的开始时间大于S最后一个元素的结束时间
S=
S
∪
{
x
}
S = S\cup \{ x\}
S
=
S
∪
{
x
}
return S
为什么可以进行这样的选择呢?我们定义
a
m
a_m
a
m
为
A
i
j
A_{ij}
A
i
j
中结束时间最早的活动,那么
a
m
a_m
a
m
将会在
A
i
j
A_{ij}
A
i
j
的某一个最大兼容活动子集中被使用,证明如下:
设
Si
j
S_{ij}
S
i
j
为
Ai
j
A_{ij}
A
i
j
的一个最大兼容活动子集,
ak
a_k
a
k
为
Si
j
S_{ij}
S
i
j
的第一个活动,若
ak
≠
a
m
a_k\ne a_m
a
k
=
a
m
,那么我们构造子集
Si
j
′
=
(
S
i
j
−
{
a
k
}
∪
{
a
m
}
)
S’_{ij}=(S_{ij}-\{ a_k\} \cup \{ a_m\} )
S
i
j
′
=
(
S
i
j
−
{
a
k
}
∪
{
a
m
}
)
。其中,我们很容易得出
fm
<
=
d
k
f_m <= d_k
f
m
<
=
d
k
,所以
am
a_m
a
m
和
Si
j
S_{ij}
S
i
j
中除了
ak
a_k
a
k
以外的其他活动都是相互兼容的,即
si
j
′
s’_{ij}
s
i
j
′
也是
Ai
j
A_{ij}
A
i
j
的一个最大兼容活动子集,即
am
a_m
a
m
在
Ai
j
A_{ij}
A
i
j
的的某一个最大兼容活动子集中被使用。
在活动选择问题中,我们既可以用动态规划解,也可以用贪心策略解,但是我们也很容易比较得出这两种方法中贪心的复杂度更小。
贪心策略和动态规划的比较
贪心和动态规划都利用的最优子结构的性质,但是动态规划是自底向上的方法,其对每一个问题都是先对子问题求解再做选择,从而得到最优解;而贪心算法则不同,他是自顶向下的方法,对于每一个问题,他都是先做出选择,在对子问题求最优解。
0-1背包问题:有
n
n
n
个物品,对于第
i
i
i
个物品,它的价格为
v
i
v_i
v
i
,重量为
w
i
w_i
w
i
,每个物品都是不可分割的完整物品,而有一个背包可以容纳总重为
W
W
W
,问背包能够装下的最大价格为多少。
部分背包问题:再0-1背包中,每一个物品都可以拿取部分,而不是只能整个整个的拿。
这两个背包问题我们都可以使用动态规划来求解,而对于部分背包问题,我们也可以使用贪心策略来求解:优先选择性价比
v
i
/
w
i
v_i/ w_i
v
i
/
w
i
的物品。而对于0-1背包问题却不能使用贪心策略来求解,因为按照贪心策略做出选择后会再背包下留下无法利用的空间而在成浪费,所以应对某个物品放进去和不放进去的结果进行比较。
拟阵
对于很多的贪心问题,都可以使用拟阵来解决。
拟阵为一个序对
M=
(
S
,
l
)
M=(S,l)
M
=
(
S
,
l
)
,其满足以下条件:
SS
S
为一个又穷的非空集合。
ll
l
为
SS
S
的一个非空子集族,成为
SS
S
的独立子集,其具有遗传性:若存在
B∈
l
B\in l
B
∈
l
且
A⊆
B
A\subseteq B
A
⊆
B
, 则
A∈
l
A \in l
A
∈
l
。- 拟阵具有交换性:如果
A∈
l
,
b
∈
l
A\in l, b\in l
A
∈
l
,
b
∈
l
,且
∣A
∣
<
∣
B
∣
|A|<|B|
∣
A
∣
<
∣
B
∣
,则存在
x∈
B
−
A
x\in B-A
x
∈
B
−
A
,使得
A∪
{
x
}
∈
l
A \cup \{ x\} \in l
A
∪
{
x
}
∈
l
。
在这里,我们用有向图来举例子:图
G
=
(
V
,
E
)
G=(V,E)
G
=
(
V
,
E
)
存在拟阵
M
G
=
(
S
G
,
l
G
)
M_G=(S_G,l_G)
M
G
=
(
S
G
,
l
G
)
,其中,
S
G
=
E
S_G=E
S
G
=
E
,即为G的边集,
l
G
l_G
l
G
为
E
E
E
中使得其构成森林的子集形成的子集族。
我们可以很快得出,因为森林的子集依然是森林,所以其具有遗传性。对于交换性,其证明如下:
我们设两个森林
GA
G_A
G
A
和
GB
G_B
G
B
,且
∣G
A
∣
<
∣
G
B
∣
|G_A|<|G_B|
∣
G
A
∣
<
∣
G
B
∣
,所以可以得出
GA
G_A
G
A
中的树多于
GB
G_B
G
B
中的树,那么就存在
GB
G_B
G
B
中的某棵树的某一条边,它两个顶点属于
GA
G_A
G
A
中的两棵不同的树,即将两棵
GA
G_A
G
A
中的树连接成了一棵树(而没有形成环),从而可以证明其交换性质。自此,可以得出每一个无向图都存在对应的拟阵。
拟阵的最大独立子集
我们成拟阵
M
=
(
S
,
l
)
M=(S,l)
M
=
(
S
,
l
)
中,对于独立子集
A
∈
l
A\in l
A
∈
l
,存在
A
A
A
之外的元素
x
x
x
使得
A
∪
{
x
}
∈
l
A\cup \{ x\} \in l
A
∪
{
x
}
∈
l
,则成
x
x
x
为
A
A
A
的一个扩张。
对于独立子集
A
∈
l
A\in l
A
∈
l
,若其不存在任何扩张,则称其为最大独立子集。一个拟阵的最大独立子集的大小都是相同的。
加权拟阵
我们将拟阵的元素
x
x
x
加上权值
w
(
x
)
w(x)
w
(
x
)
,将独立子集的权值定义为它包含的所有元素的权值之和,即
w
(
A
)
=
∑
x
∈
A
w
(
x
)
w(A)=\sum_{x \in A} w(x)
w
(
A
)
=
∑
x
∈
A
w
(
x
)
。这样,我们就可以很容易的使用贪心策略来求拟阵的最大独立子集,其伪代码如下:
A=
ϕ
A = \phi
A
=
ϕ
将S中的元素按照权值从大到小排列
for x in S
if
A∪
{
x
}
∈
l
A\cup \{ x\} \in l
A
∪
{
x
}
∈
l
A=
A
∪
{
x
}
A = A\cup \{ x\}
A
=
A
∪
{
x
}
return A
而对于其他各种贪心问题,我们都可以将其转化为求拟阵的最大独立子集,下面以求带权无向连通图的最小生成树为例:
设无向连通图的各个边的权值为
w
(
e
)
w(e)
w
(
e
)
,将其转换为拟阵之后,拟阵的元素与图的边一一对应。我们设拟阵的元素的权值为
w
′
(
e
)
=
w
0
(
e
)
−
w
(
e
)
w'(e)=w_0(e)-w(e)
w
′
(
e
)
=
w
0
(
e
)
−
w
(
e
)
,其中
w
0
(
e
)
w_0(e)
w
0
(
e
)
大于图所有边的权值。这样,拟阵权值最大的元素就对应着图权值最小的边,这样求得拟阵的最大独立子集就与图的最小生成树相对应了。