贪婪是一种算法范式,它一点一点地构建解决方案,总是选择下一个提供最明显和最直接好处的部分。贪婪算法用于优化问题。
如果优化问题具有以下属性,则可以使用贪婪解决该问题:
在每一步中,我们都可以做出一个目前看起来最好的选择,我们得到了完整问题的最佳解决方案。
如果贪婪算法可以解决一个问题,那么它通常会成为解决该问题的最佳方法,因为贪婪算法通常比其他技术(如动态规划)更有效。但贪婪算法并不总是适用的。例如,部分背包问题可以使用Greedy解决,但0-1背包不能使用Greedy解决。
以下是一些标准的贪婪算法
- Kruskal最小生成树(MST):
在Kruskal的算法中,我们通过逐个拾取边缘来创建MST。贪婪选择是在目前构造的MST中选取不引起循环的最小权重边 - Prim的最小生成树:
在Prim的算法中,我们也通过逐个拾取边缘来创建MST。我们维护两套:已经包括在MST中的顶点的集合和尚未包括的顶点的集合。贪婪选择是选择连接两个集合的最小权重边 - Dijkstra的最短路径:
Dijkstra的算法与Prim的算法非常相似。最短路径树是边接边建立的。我们维护两套:一组已经包括在树中的顶点和一组尚未包括的顶点。贪婪选择是选择连接两个集合的边,并且该边位于从源到包含尚未包含的折点的集合的最小权重路径上 - 霍夫曼编码:
霍夫曼编码是一种无损压缩技术。它为不同的字符分配可变长度的位代码。贪婪选择是将最小位长的代码分配给最频繁的字符。
贪婪算法有时也被用来获得硬优化问题的近似。例如,旅行商问题是一个NP-Hard问题。这个问题的一个贪婪的选择是在每一步都选择离当前城市最近的未访问的城市。这些解决方案并不总是产生最佳的最优解决方案,但可以用于获得近似最优解决方案。
在这里,让我们看到一个这样的问题,可以解决使用贪婪算法。
问题:
您将获得n个活动及其开始和结束时间。选择一个人可以执行的最大活动数,假设一个人一次只能处理一个活动。
示例:
输入:start[] = {10,12,20},finish[] = {20,25,30}
输出:0 2
说明:一个人最多只能做两件事。该可执行的最大活动集是 {0,2} [这些是start[]和finish[]中的索引]
输入:start[] = {1,3,0,5,8,5},finish[] = {2,4,6,7,9,9};
输出:0 1 3 4
说明:一个人最多只能做四种活动。该可执行的最大活动集是 {0,1,3,4} [这些是start[]和finish[]中的索引
方法:
要解决这个问题,请遵循以下思路:
贪婪的选择是总是选择下一个活动,其完成时间在剩余活动中是最短的,并且开始时间大于或等于先前选择的活动的完成时间。我们可以根据活动的完成时间对活动进行排序,这样我们总是把下一个活动视为最短完成时间的活动
按照给出的步骤解决问题:
- 根据活动的完成时间对活动进行排序
- 从排序数组中选择第一个活动并打印它
- 对排序数组中的剩余活动执行以下操作
如果此活动的开始时间大于或等于先前选定活动的完成时间,则选择此活动并打印它
注意:在实现中,假设活动已经根据它们的完成时间排序。
下面是上述方法的python实现:
# The following implementation assumes that the activities
# are already sorted according to their finish time
# Prints a maximum set of activities that can be done
# by a single person, one at a time
def printMaxActivities(s, f):
n = len(f)
print("Following activities are selected")
# The first activity is always selected
i = 0
print(i, end=' ')
# Consider rest of the activities
for j in range(1, n):
# If this activity has start time greater than
# or equal to the finish time of previously
# selected activity, then select it
if s[j] >= f[i]:
print(j, end=' ')
i = j
# Driver code
if __name__ == '__main__':
s = [1, 3, 0, 5, 8, 5]
f = [2, 4, 6, 7, 9, 9]
# Function call
printMaxActivities(s, f)
输出:
Following activities are selected
0 1 3 4
时间复杂度:O(N)
空间复杂度:O(1)
贪婪选择如何对根据完成时间排序的活动起作用?
假设给定的活动集合为S = {1,2,3,… n},并且活动按完成时间排序。贪婪的选择是总是选择活动1。为什么活动1总是提供最佳解决方案之一?
我们可以证明,如果有另一个解决方案B的第一个活动不是1,那么也有一个解决方案A的大小与活动1相同。设B选择的第一个活动为k,则总是存在A = {B - {k}} U {1}。
注:B中的活动是独立的,并且k在所有活动中具有最小的完成时间。因为k不是1,所以finish(k)>= finish(1))
当给定的活动没有排序时如何实现?
我们为活动创建一个结构/类。我们按完成时间对所有活动进行排序。一旦我们对活动进行了排序,我们就应用相同的算法。
下图是上述方法的说明:
以下是上述方法的python实现:
''' Python program for activity selection problem
when input activities may not be sorted.'''
def MaxActivities(arr, n):
selected = []
# Sort jobs according to finish time
Activity.sort(key=lambda x: x[1])
# The first activity always gets selected
i = 0
selected.append(arr[i])
for j in range(1, n):
'''If this activity has start time greater than or
equal to the finish time of previously selected
activity, then select it'''
if arr[j][0] >= arr[i][1]:
selected.append(arr[j])
i = j
return selected
# Driver code
if __name__ == '__main__':
Activity = [[5, 9], [1, 2], [3, 4], [0, 6], [5, 7], [8, 9]]
n = len(Activity)
# Function call
selected = MaxActivities(Activity, n)
print("Following activities are selected :")
print(selected[0], end = "");
for i in range (1, len(selected)):
print(",", end = " ")
print(selected[i], end = "")
输出:
Following activities are selected :
(1, 2), (3, 4), (5, 7), (8, 9)
时间复杂度:O(N log N),如果输入活动可能没排序。排序时是O(N)
空间复杂度:O(1)
使用优先级队列的活动选择问题
按照给出的步骤解决问题:
- 创建一个优先级队列(Min-Heap)并将活动推入其中。
- 将优先级队列的顶部推入答案向量,并将变量start设置为第一个活动的开始时间,将变量end设置为活动的完成时间
- 当优先级不为空时,执行以下操作:
取优先级队列的最高位置进行检查
如果此活动的开始时间大于或等于最后选择的活动的完成时间,则将此活动推入答案向量
否则忽略它 - 打印存储在答案向量中的所选活动
以下是上述方法的python实现:
# when input activities may not be sorted.
from heapq import heappop, heappush
# Function to select activites
def SelectActivities(s, f):
ans = []
p = []
# Pushing elements in the list
for i, j in zip(s, f):
heappush(p, (j, i))
it = heappop(p)
start = it[1]
end = it[0]
ans.append(it)
# Sorting process
while p:
it = heappop(p)
if it[1] >= end:
start = it[1]
end = it[0]
ans.append(it)
print("Following Activities should be selected.\n")
for f, s in ans:
print(f"Activity started at {s} and ends at {f}")
# Driver code
if __name__ == "__main__":
s = [1, 3, 0, 5, 8, 5]
finish = [2, 4, 6, 7, 9, 9]
# Function call
SelectActivities(s, finish)
输出:
Following Activities should be selected.
Activity started at: 1 and ends at 2
Activity started at: 3 and ends at 4
Activity started at: 5 and ends at 7
Activity started at: 8 and ends at 9
时间复杂度:O(N * log N)
空间复杂度:O(N)