贪心算法的本质是什么?

举报
Xxy_1008 发表于 2024/08/25 15:35:10 2024/08/25
【摘要】 贪心算法(Greedy Algorithm)是一种在每一步选择中都采取在当前状态下最优(或最有利)的选择的算法。贪心算法希望通过局部最优选择来达到全局最优解。虽然这种方法并不总是能保证找到最优解,但在很多实际问题和特定情况下,贪心算法可以提供高效且接近最优的解决方案。

经常听人谈起贪心算法,但是你真的了解什么是贪心算法吗?知道在什么时候使用谈心算法吗?If not,follow me.

贪心算法(Greedy Algorithm)是一种在每一步选择中都采取在当前状态下最优(或最有利)的选择的算法。贪心算法希望通过局部最优选择来达到全局最优解。虽然这种方法并不总是能保证找到最优解,但在很多实际问题和特定情况下,贪心算法可以提供高效且接近最优的解决方案。

贪心算法的特点

  1. 局部最优选择:每一步都选择当前状态下的最优解。
  2. 无回溯:一旦作出选择,不会回头修改之前的选择。
  3. 问题分解:将问题分解成一系列子问题,通过解决这些子问题来解决整体问题。

适用问题

贪心算法适用于具有贪心选择性质和最优子结构性质的问题。

  1. 贪心选择性质:通过局部最优选择可以构建全局最优解。
  2. 最优子结构性质:一个问题的最优解包含其子问题的最优解。

常见例子

1. 活动选择问题

假设有一组活动,每个活动都有一个开始时间和结束时间。目标是选择尽可能多的活动,使得这些活动互不重叠。贪心策略是每次选择结束时间最早且不与已选择的活动重叠的活动。

解释


def activity_selection(activities):
    # 按照结束时间排序
    activities.sort(key=lambda x: x[1])
    
    selected_activities = []
    last_end_time = 0
    
    for activity in activities:
        if activity[0] >= last_end_time:
            selected_activities.append(activity)
            last_end_time = activity[1]
    
    return selected_activities

# 活动的开始和结束时间
activities = [(1, 4), (3, 5), (0, 6), (5, 7), (3, 8), (5, 9), (6, 10), (8, 11), (8, 12), (2, 13), (12, 14)]
print(activity_selection(activities))  # 输出: [(1, 4), (5, 7), (8, 11), (12, 14)]

2. 最小生成树(Kruskal算法)

在一个加权无向图中,找到一个生成树,使得所有节点都连通且边的总权重最小。Kruskal算法是一种贪心算法,每次选择权重最小的边,前提是不会形成环。

解释


class DisjointSet:
    def __init__(self, vertices):
        self.parent = {v: v for v in vertices}
        self.rank = {v: 0 for v in vertices}
    
    def find(self, v):
        if self.parent[v] != v:
            self.parent[v] = self.find(self.parent[v])
        return self.parent[v]
    
    def union(self, v1, v2):
        root1 = self.find(v1)
        root2 = self.find(v2)
        
        if root1 != root2:
            if self.rank[root1] > self.rank[root2]:
                self.parent[root2] = root1
            else:
                self.parent[root1] = root2
                if self.rank[root1] == self.rank[root2]:
                    self.rank[root2] += 1

def kruskal(graph):
    edges = []
    for u in graph:
        for v, weight in graph[u]:
            edges.append((weight, u, v))
    
    edges.sort()
    
    vertices = set(graph.keys())
    ds = DisjointSet(vertices)
    mst = []
    
    for weight, u, v in edges:
        if ds.find(u) != ds.find(v):
            ds.union(u, v)
            mst.append((u, v, weight))
    
    return mst

graph = {
    'A': [('B', 1), ('C', 3)],
    'B': [('A', 1), ('C', 3), ('D', 6)],
    'C': [('A', 3), ('B', 3), ('D', 4)],
    'D': [('B', 6), ('C', 4)]
}

print(kruskal(graph))  # 输出: [('A', 'B', 1), ('A', 'C', 3), ('C', 'D', 4)]

优缺点

优点

  1. 简单直观:贪心算法的每一步选择都是基于当前最优,因此逻辑简单且容易实现。
  2. 高效:在许多情况下,贪心算法的时间复杂度较低,比其他复杂算法更高效。

缺点

  1. 不保证全局最优解:贪心算法并不总是能保证找到问题的最优解,特别是在一些复杂问题中。
  2. 问题依赖性强:贪心算法适用于具有特定性质的问题,不具备广泛的适用性。

总结

贪心算法是一种通过局部最优选择来尝试构建全局最优解的算法。它在解决某些特定类型的问题时非常有效,具有简单直观和高效的优点。然而,由于其不保证全局最优解的特性,在应用时需要特别注意问题的性质,确保其适用性。

上示例!

 救生艇[中等]

题目:

给定数组 people 。people[i]表示第 i 个人的体重 ,船的数量不限,每艘船可以承载的最大重量为 limit

每艘船最多可同时载两人,但条件是这些人的重量之和最多为 limit

返回 承载所有人所需的最小船数 。


示例 1:

输入:people = [1,2], limit = 3
输出:1
解释:1 艘船载 (1, 2)

示例 2:

输入:people = [3,2,2,1], limit = 3
输出:3
解释:3 艘船分别载 (1, 2), (2) 和 (3)

示例 3:

输入:people = [3,5,3,4], limit = 5
输出:4
解释:4 艘船分别载 (3), (3), (4), (5)


提示:

  • 1 <= people.length <= 5 * 10**4
  • 1 <= people[i] <= limit <= 3 * 10**4

 题目分析:

        一艘船最多上两个人,要使船只最少那就只能让每只船载人尽可能多,那么最多就是两个人,这里对people数组进行排序(假设从小到大排),然后定义双指针分别指头和尾。判断头+尾是否大于limit:

  • 大于的话则说明尾指针指向的最大值只能单独乘船,此时应单独分配一艘船给体重最重的人。从 people中去掉体重最重的人后,我们缩小了问题的规模,变成求解剩余 n−1 个人所需的最小船数,将其加一即为原问题的答案,此时尾指针向前走,船只加一;
  • 否则则说明 头 可以和 尾  一起乘船,那么头也能与其余任何人同乘一艘船,为了尽可能地利用船的承载重量,选择与体重最重的人同乘一艘船是最优的。从 people 中去掉体重最轻和体重最重的人后,就缩小了问题的规模,变成求解剩余 n−2 个人所需的最小船数,将其加一即为原问题的答案。此时头尾都往中间走一步,船只加1。以此类推,遍历一遍过去即可出答案。

代码实现:

class Solution:
    def numRescueBoats(self, people: List[int], limit: int) -> int:
        people.sort()
        n=len(people)
        if n==1: return 1
        res=0
        st,ed=0,n-1
        while st<=ed:
            if people[st]+people[ed]<=limit:
                res+=1
                st+=1
                ed-=1
            else:
                res+=1
                ed-=1
        return res

总结:

       代码的逻辑是基于贪心算法,每次尽可能多地安排乘客乘坐救生艇达到最少的船只数,最后返回res即为所需的最少船只数。具体步骤如下:

  1. 首先对乘客的重量列表进行排序,这样可以从小到大依次选择乘客。
  2. 使用双指针st和ed分别指向排序后的乘客列表的第一个和最后一个元素。
  3. 如果st指向的乘客和ed指向的乘客的重量之和不超过limit,则表示可以安排这两个乘客乘坐一艘救生艇,此时res加1,同时移动st和ed指针继续判断下一组乘客。
  4. 如果st指向的乘客和ed指向的乘客的重量之和超过limit,则只能让ed指向的乘客单独乘坐一艘救生艇,此时res加1,移动ed指针继续判断。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。