关于对贪心算法的理解

文章介绍了贪心算法的基本概念和特征,并通过会议安排、股票抛售和路灯点亮三个问题展示了如何运用贪心策略寻找局部最优解。在会议问题中,关键策略是选择最早结束的会议;股票问题中,预知未来价格可提前抛售;路灯问题中,最少的灯照亮最多的居民点。每个问题的贪心策略不同,体现了贪心算法的灵活性和多样性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.概念理解

1.何为贪心算法?

贪心算法(greedy algorithm [8] ,又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,算法得到的是在某种意义上的局部最优解 。

2.贪心算法的特征

  • 1、有一个以最优方式来解决的问题。为了构造问题的解决方案,有一个候选的对象的集合:比如不同面值的硬币 。

  • 2、随着算法的进行,将积累起其他两个集合:一个包含已经被考虑过并被选出的候选对象,另一个包含已经被考虑过但被丢弃的候选对象 。

  • 3、有一个函数来检查一个候选对象的集合是否提供了问题的解答。该函数不考虑此时的解决方法是否最优 [3] 。

  • 4、还有一个函数检查是否一个候选对象的集合是可行的,即是否可能往该集合上添加更多的候选对象以获得一个解。和上一个函数一样,此时不考虑解决方法的最优性 。

  • 5、选择函数可以指出哪一个剩余的候选对象最有希望构成问题的解 。

  • 6、最后,目标函数给出解的值


二.思路解析及结合具体题目进行贪心策略的选择sad

1.思路总述:

贪心算法是所有算法中最灵动的算法,对于贪心题目而言,我们解决问题的关键在于能够找到其正确的贪策略,但是说句实在话,对于不同的题目和应用场景,几乎每道题目都有属于自己的解题思路,我们能做的在很大程度上只能说是总结;与此同时,贪心算法同样也是最符合自然智慧的算法,贪心算法的起源来自于生活场景,因此,根据不同生活场景进行部署不同的贪心策略,同样有助于我们解决问题。

接下来,我们根据不同的题目进行不同贪心策略的部署:

1.会议问题

大家都知道,老板总是很忙,每天有很多会等着老板参加,但老板又没有时间参加所有的会议,所以需要秘书来合理安排。
某一天,老板对秘书说:“你安排一下,我希望今天尽可能多参加几场会议”。秘书看了一下如下希望老板参加的会议,她应该如何安排,才能使老板参加会议的场次最多? [[8,10],[9,11],[10,15],[11,14],[13,16],[14,17],[15,17],[17,18],[18,20],[16,19]] 列表里的每一个子列表都表示一场会议比如第一场会议[8,10],表示这场会议开始时间是8点,结束时间是10点。

我们试着分析一一下这个问题:如果想要合理安排会议以至于在一天中安排的会议场数最多,我们必须要找到一个标准来决定会议安排的时间先后问题,而这便是这个问题的贪心策略的着手点,那标准是会议开始的越早就先安排这场会议吗?(反例:如果一场会议从早上开到晚上结束呢?,这样只能安排一场会议了)这个策略显然不合理,与此相反,恰好是哪场会议结束的最早,我们先安排哪场会议(因为一天的时间是一定的,如果先安排早结束的会议,意味着我们有更多的时间去安排后面的会议,而这便是这个题目贪心策略的关键点

基于这个思路,我们进行code:

public class ConferenceForGreedy {
    public static void main(String[] args) {

    }
    public int conferenceGreedy(Conference[]programs){
        if(programs==null||programs.length==0){
            return  0;
        }
        int result=0;

        int timeLine=0;
        //对数组进行排序
        Arrays.sort(programs,new sorts());
        //对数组进行遍历放入
        for (int i = 0; i < programs.length; i++) {
            if(programs[i].start>=timeLine){
                //符合条件就对会议的数量进行增加
                result++;
                //不断去更迭timeLine
                timeLine=programs[i].end;
            }
        }
        return result;
    }
//重写比较器,使结束时间早的会议排在数组的前面
   public static class sorts implements Comparator<Conference> {
       @Override
       public int compare(Conference o1, Conference o2) {
           return o1.end-o2.end;
       }
   }
}
2.股票抛售问题
给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。返回 你能获得的 最大 利润 。

我们通过上面的实例能够发现:我们能够预知未来(知道未来几天股票价格的跌落),如果在这样的情况下,那我们的贪心策略就很容易进行抉择了:第一天买入股票,如果预知到后面某一天股票价格跌落,就在前一天将股票进行抛售;如果后面的日子股票价格没有跌落,那我们就继续等待(赚更多的钱)

我们在这种思路下来code:

class Solution {
    public int maxProfit(int[] prices) {
        //思路:在当前情况下做出做优决定:如果股票在明天降价或者不变,我们就在今天卖出,如果价格上升,保留
        //检查数组的有效性
        if(prices.length==1){
            return 0;
        }
        int profits=0;
        //-1状态表示当前手中没有股票
        int tacket=-1;
        for(int i=0;i<prices.length-1;++i){
            if(tacket==-1){
                //买入
                tacket=prices[i];
            }
            if(prices[i+1]>prices[i]){
                //继续等待
                continue;
            }
            else{
                //进行抛售
                profits+=prices[i]-tacket;
                tacket=-1;
            }
        }
        //检查最后一天,不是0说明最后一天股票价格上涨
        if(tacket!=-1){
            profits+=prices[prices.length-1]-tacket;
        }
        return profits;
    }
}
3.路灯问题
给定一个字符串str,只由‘X’和‘.’两中国字符构成。
‘X’表示墙,不能放灯,也不需要点亮,‘.’表示居民点,可以放灯,需要点亮。
如果灯放在i位置,可以让i-1,i和i+1三个位置被点亮,返回如果点亮str中所需要点亮的位置,至少需要几盏灯
例如: X…X…X…X. 需要至少5盏灯
思路概述:这道题的关键点用一句话可以概括为用最少的灯点亮全部的居民点,我们按照不同的情况进行不同的策略部署:

在思路的指引下:我们直接进入code:

package greedy;

/**
 * @author tongchen
 * @create 2023-03-09 9:31
 */
public class LightForGreedy {
    public static void main(String[] args) {

    }
    public int PlacesForGreedy(char[]chars){
        int index=0;//遍历变量
        int lights=0;
        while(index<chars.length){
            //当前位置为x则直接跳过
            if(chars[index]=='X'){
                index++;
            }
            else {
                //因为要考虑当前位置+1的内容,所以我们必须判断当前位置是不是在数组的最后
                //在index index+1 和index+2 的区间内,必定放一个灯
                lights++;
                if(chars[index+1]==chars.length){
                    //如果是最后一个元素,直接返回
                    break;
                }
                if(chars[index+1]=='X') {
                    //下一个位置为X,跳跃到下下个位置
                    index+=2;
                }
                else {
                    //这种情况则为下一个位置为.  不论index+2的位置是.还是X,直接跳跃到index+3的位置
                    index+=3;
                }
            }
        }
        //返回放置路灯的数量
        return  lights;
    }
}

通过这三个题目,我们能够总结一点:不同的贪心题目具有不同的应用场景,贪心策略也随之有所变化,

除了找到正确的贪心策略之外,我们仍然可以采用“试”的方法求得最优解:

我们拿会议问题和路灯问题来给出代码实例:

  1. 会议问题

package greedy;

/**
 * @author tongchen
 * @create 2023-03-09 8:09
 */
public class ConferenceForViolence {
    public static void main(String[] args) {

    }
    //创建不断递归的主方法
    public  int process(Conference[]programs,int done,int timeLine){
        //出口条件:判断所有的事务是否都已完成
        if( 0==programs.length){
            return 0;
        }
        //继承上面做的事务数量
        int max=done;
        //循环:处理当前事务,并判断下面的事务(以当前事务为第一个事务,不断进行暴力枚举)
        for(int i=0;i<programs.length;++i){
            if(programs[i].start>=timeLine){
                //没有需要处理的,直接跨入下一个
                Conference[] conferences = copyButExpect(programs, i);
            max=Math.max(max,  process(conferences, done+1, programs[i].end));
            }
        }
        return max;
    }
    //创建获取新数组的方法
    public Conference[] copyButExpect(Conference[]programs,int i){
        //创建新的数组
        Conference[] arr=new Conference[programs.length-1];
        int count=0;
        //定义计数器
        for(int k=0;k< programs.length;++i){
            if(k!=i){
                arr[count++]=programs[k];
            }
        }
        return arr;
    }

}
  1. 路灯问题

package greedy;

import java.util.HashSet;

/**
 * @author tongchen
 * @create 2023-03-09 9:01
 * 思路:循环处理灯:判断当前位置为X的时候不做选择,当前位置是.的时候选择放入和不放入
 */
public class LightsForViolence {
    public static void main(String[] args) {

    }
    public int process(char[]places, int index, HashSet<Integer>lights){
        //当index走到最后的时候,从头到尾依次判断这次的放入灯的策略是否正确
        if(index==places.length){
            //遍历检查有效性
            for (int i = 0; i < places.length; i++) {
                if(places[i]!='X'){
                    if(lights.contains(i-1)&&lights.contains(i)&&lights.contains(i+1)){
                        //这种情况不合理
                        return  Integer.MAX_VALUE;
                    }
                }
            }
            return lights.size();
        }else {
            //没有走到最后
            int no=process(places, index+1, lights);
            int yes=Integer.MAX_VALUE;

            if(places[index]=='.'){
                lights.add(index);
                 yes=process(places, index+1, lights);
            }


            return Math.min(yes,no);
        }
    }

}

这三个题目只是贪心问题中的冰山一角:

另外我还做了其他的一些关于贪心问题的题目,并总结了贪心策略,希望能给大家带来一些启发。

链接如下:

三.总结概括

我们通过上面的例子更能清楚的感受到不同题目所采用的的贪心算法的多样性和灵活性,在一定程度上很难在其中找到固定的解法,但是我们仍能从其中找到一些普适的规律:很多贪心题目都能在进行特定的排序或者将其放入按照某些属性进行排序的大跟堆和小根堆中,往往有可能能够找到正确的贪心策略

<think>好的,我现在要帮助用户理解贪心算法的概念和用法,以及它的原理和应用实例。首先,我需要回顾一下用户提供的引用内容,确保准确引用相关信息。用户给的四个引用都涉及贪心算法的基本原理、优缺点以及应用案例。 首先,贪心算法的定义是什么?根据引用[4],贪心算法是一种在每一步选择中都采取当前优决策的策略,不考虑未来后果。它适用于可以通过局部优达到全局优的问题。需要明确两个关键性质:贪心选择性质和优子结构,引用[1]提到这点。 接下来,用户可能需要了解贪心算法的基本思想。引用[4]指出,贪心算法需要建立选择标准,做出当前优选择,然后更新问题为子问题,并继续应用策略。这个过程应该分步骤解释,例如找零问题,引用[2]和引用[4]都提到了这个例子,说明如何用贪心算法解决问题。 然后,应用实例部分需要具体案例。用户提供的引用中有哈夫曼编码、活动选择问题、小生成树等。例如,引用[2]提到哈夫曼编码通过每次合并频率低的节点来构建优编码树;活动选择问题可以用贪心策略选择早结束的活动,引用[4]可能有相关内容。同时,引用[3]中的路径问题说明了贪心算法可能的局限性,即局部优不一定全局优,这时候需要指出贪心算法的适用场景。 另外,要强调贪心算法的优缺点。引用[2]提到优点包括简单高效、在特定问题中能得到优解、适合在线问题。缺点则是适用范围有限,不保证所有问题的优解,如引用[3]中的例子。需要结合这些点来回答用户。 用户的问题还包括原理和应用实例,所以结构上应该分原理和实例两部分。原理部分需要解释基本思想和两个关键性质;应用部分列举几个经典例子,每个例子说明问题、策略和结果。同时,要提到优缺点,帮助用户全面理解。 需要确保回答结构清晰,使用中文,数学表达式正确。例如,在哈夫曼编码中可能需要用公式表示频率,但用户提供的引用中没有具体数学公式,所以可能不需要。但如果有例子涉及数学,比如找零问题中的金额计算,可以用行内公式表示,例如总金额$T$,面额$d_i$等。 后,生成相关问题,可能需要根据用户提供的示例问题,考虑贪心算法的其他应用、如何判断问题是否适用贪心、与其他算法(如动态规划)的比较等。例如,用户之前的示例问题包括快速排序的应用场景,这里可以类似生成关于贪心算法适用性的问题。 总结思考步骤: 1. 定义贪心算法及其核心思想。 2. 解释两个关键性质:贪心选择和优子结构。 3. 分步骤说明算法流程。 4. 应用实例:找零、活动选择、哈夫曼编码、小生成树。 5. 优缺点分析。 6. 注意事项:适用场景判断。 7. 生成相关问题。 需要检查引用是否正确,例如引用[3]中的路径问题说明贪心可能失败,这点在注意事项中提到。引用[1]和[4]强调性质,引用[2]和[4]提供应用案例。确保每个引用在回答中适当位置被引用,比如在讲优点时引用[2],在讲局限性时引用[3]。</think>### 贪心算法原理及应用实例详解 #### 一、贪心算法的基本原理 贪心算法是一种**通过局部优选择逐步构建全局优解**的算法策略。其核心思想是:**每一步选择当前状态下优的决策,而不考虑后续步骤的影响**。这一特性使得它高效且易于实现,但同时也限定了其适用场景[^1][^4]。 ##### 关键性质 1. **贪心选择性质** 每一步的局部优选择必须能导出全局优解。例如在找零问题中,优先使用大面额货币的策略能保证总硬币数少[^2][^4]。 2. **优子结构** 问题的优解包含其子问题的优解。例如在哈夫曼编码中,合并频率低的节点后,剩余节点的优编码结构仍然成立[^1][^4]。 ##### 算法步骤 1. 建立贪心选择标准(如大面额、早结束时间) 2. 按标准做出当前优选择 3. 将问题简化为更小的子问题 4. 重复上述步骤直至问题解决 #### 二、经典应用实例 1. **找零问题** - **问题**:用少的硬币凑出金额$T$ - **策略**:优先使用面额大的硬币 - **示例**:若硬币面额为$[25,10,5,1]$,金额$T=41$,则选择过程为$25+10+5+1$[^4] 2. **活动选择问题** - **问题**:从多个时间重叠的活动中选出多不冲突的活动 - **策略**:每次选择**早结束**的活动 - **示例**:活动按结束时间排序后,依次选择不冲突的早结束活动[^4] 3. **哈夫曼编码** - **问题**:构造字符的优前缀编码以小化文件总长度 - **策略**:每次合并**频率低**的两棵子树 - **数学表达**:若字符频率为$f_1,f_2,...,f_n$,总文件长度$L=\sum f_i \cdot depth(i)$[^2] 4. **小生成树(Prim算法)** - **问题**:在带权连通图中选择边集构成树且总权重小 - **策略**:从任意顶点出发,每次选择连接已选顶点和未选顶点的**小边**[^4] #### 三、优缺点分析 | 优点 | 缺点 | |------|------| | 实现简单,时间复杂度低(如找零问题$O(n)$)[^2] | 不保证全局优(如图1的路径问题)[^3] | | 对在线问题响应及时 | 适用范围受限(需满足贪心选择性质) | | 在特定问题中严格优(如哈夫曼编码) | 难以处理后效性问题 | #### 四、注意事项 1. **适用性验证**:必须严格验证问题是否满足贪心选择性质和优子结构 2. **对比其他算法**:与动态规划的区别在于不需要考虑子问题解的组合 3. **典型失败案例**:图1中贪心路径$7→12→6→9$(实际优路径$7→3→1→99$)[^3] ```python # 活动选择问题示例代码 def select_activities(activities): activities.sort(key=lambda x: x[1]) # 按结束时间排序 selected = [activities[0]] for act in activities[1:]: if act[0] >= selected[-1][1]: selected.append(act) return selected ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘子干测开

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值