About the understanding of greedy algorithm

1. Conceptual understanding

1. What is a greedy algorithm?

Greedy algorithm (greedy algorithm [8], also known as greedy algorithm) means that when solving a problem , always make the best choice at present. That is to say, without considering the overall optimality, the algorithm obtains a local optimal solution in a certain sense.

2. Characteristics of Greedy Algorithm

  • 1. There is a problem to solve in an optimal way. In order to construct a solution to the problem, there is a collection of candidate objects: for example coins of different denominations.

  • 2. As the algorithm progresses, two other sets will be accumulated: one contains candidates that have been considered and selected, and the other contains candidates that have been considered but discarded.

  • 3. Have a function to check whether a set of candidate objects provides the answer to the question. This function does not consider whether the solution at this time is optimal [3].

  • 4. There is also a function to check whether a set of candidate objects is feasible, that is, whether it is possible to add more candidate objects to the set to obtain a solution. As in the previous function, the optimality of the solution is not considered at this time.

  • 5. The selection function can indicate which of the remaining candidates is the most promising solution to the problem.

  • 6. Finally, the objective function gives the value of the solution


2. Analysis of ideas and selection of greedy strategies combined with specific topics sad

1. Summary of ideas:

Greedy algorithm is the most flexible algorithm among all algorithms. For greedy problems, the key to solving problems is to find the correct greedy strategy. But to be honest, for different problems and application scenarios, almost every problem has With our own ideas for solving problems, what we can do is to a large extent only a summary ; at the same time, the greedy algorithm is also the algorithm that is most in line with natural wisdom . The origin of the greedy algorithm comes from life scenes. Therefore, according to Deploying different greedy strategies in different life scenarios also helps us solve problems.

Next, we deploy different greedy strategies according to different topics:

1. Meeting problems

As we all know, the boss is always very busy, and there are many meetings waiting for the boss to attend every day, but the boss does not have time to attend all the meetings, so the secretary is needed to make reasonable arrangements.
One day, the boss said to the secretary: "You arrange it, I hope to attend as many meetings as possible today." The secretary took a look at the following meetings that she hopes the boss will attend. How should she arrange so that the boss can attend the most meetings? [[8,10],[9,11],[10,15],[11,14],[13,16],[14,17],[15,17],[17,18],[ 18,20],[16,19]] Each sub-list in the list represents a meeting such as the first meeting [8,10], which means that the start time of this meeting is 8 o'clock and the end time is 10 o'clock.

Let's try to analyze this problem: If we want to arrange meetings reasonably so that the maximum number of meeting venues are arranged in a day, we must find a standard to determine the timing of meeting arrangements, and this is the essence of this problem. The starting point of the greedy strategy , is the standard that the earlier the meeting starts, the earlier the meeting is scheduled? (Counterexample: What if a meeting starts in the morning and ends in the evening?, so that only one meeting can be arranged) This strategy is obviously unreasonable. On the contrary, it happens to be the earliest meeting to end, which meeting we arrange first ( Because the time of the day is fixed, if we arrange the meeting that ends early, it means that we have more time to arrange the meeting later, and this is the key point of the greedy strategy of this topic )

Based on this idea, we 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. The stock selling problem
gives you an integer array prices, where prices[i] represents the price of a certain stock on the i-th day.
On each day, you can decide whether to buy and/or sell stocks. You can hold no more than one share of stock at any one time. You can also buy first and then sell on the same day. Return the maximum profit you can make.

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

我们在这种思路下来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);
        }
    }

}

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

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

链接如下:

三.总结概括

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

Guess you like

Origin blog.csdn.net/m0_65431718/article/details/129511477
Recommended