Greedy Algorithm (Several Conventional Examples)

Greedy Algorithm (Several Conventional Examples)

贪心算法,指在对问题进行求解的时候,总是做出当前看来是最好的选择。也就是说不从整体上最优上考虑,算法得到的结果是某种意义上的局部最优解


introduce

Problems that can be solved with a greedy algorithm have the following characteristics

  • 1.贪心选择的性质:一个问题的整体最优解可以通过一系列局部的最优解的选择达到。并且每一次的选择可以依赖于之前做出的选择,但是不依赖后面做出的选择。这就是贪心选择性质。对于一个具体的问题,要确定他是否具有贪心选择的性质,必须证明每一步所作的贪心选择最终导致问题的整体的最优解

  • 2.最优子结构性质:当一个恩问题的最优解包含其子问题的最优解的时候,此问题具有最优子结构性质。问题的最优子结构性质是该问题可用贪心法的求解所在。

Steps for usage

The basic steps of using the greedy algorithm:
   1> Establish a mathematical model to describe the problem.
   2> Divide the problem to be solved into several sub-problems.
   3> Solve each sub-problem to obtain the local optimal solution of the sub-problem.
   4> Combine the local optimal solution of the sub-problem into a solution of the original problem.

实际上在使用贪心算法的时候,待选

template

while(约束条件成立)
{
    选择当前最优解,记录并累计到最后的解中
        if(当前最优解使用完毕)
    	当前最优解=当前次优解;
}

Drawbacks of Greedy Algorithms

​The following defects

1.不能保证解是最佳的。因为贪心算法得到的是局部最优解,不是从整体开了整体最优解。

2.贪心算法只能确定某些问题的可行性范围

Defects 1 and 2 mean that the greedy algorithm has limitations. For example, if you change coins, if it is 8 yuan, there are 2 yuan and 5 yuan, then 5 yuan for one piece, 2 yuan for one piece, and 1 yuan left. Then it can’t be calculated. In fact, four 2 yuan can be used. This is an example

3.贪心算法一般用来解决最大或者最小解

The defect is that the greedy algorithm uses a certain strategy to select a value instead of traversing all the solutions. To traverse all the solutions of a certain problem, the backtracking method is often used

classic example

1. Activity selection problem

2. Coin change problem

3. Revisiting the knapsack problem

4. The problem of boat crossing the river

5. Interval coverage problem

common example

Activity Selection Questions

This problem can generally be solved by greedy algorithm and dynamic programming. We will use greedy algorithm to solve it below.
The form of the question is: to hold multiple activities in a certain place, and each activity will take a different time (different start and end times), how to arrange as many activities as possible? The reason why this problem can be solved with a greedy algorithm is that the choice of the next activity can only depend on the deadline of the previous activity, and it is not necessary to consider the overall situation

Activity 1 2 3 4 5 6 7 8 9 10 11
Starting time 1 3 0 5 3 5 6 8 8 2 12
End Time 4 5 6 7 8 9 10 11 12 13 14

When solving this problem, it is necessary to sort the end time, because the end time of the previous activity will affect the selection of the next activity, and the start time of the current activity will not affect the selection of the next activity , so just look at the end time. Well, use the end time of the previous activity to compare with the start time of the next activity, if the former is less than the latter, then the latter is the next activity, otherwise continue to compare the start time of the next activity

problem solving ideas

  1. 将所有活动按照结束时间进行排序
  2. 然后默认选取第一个活动,用变量endTime标注当前活动的结束时间,然后与后面活动的开始时间进行比较
  3. 如果前者结束时间小于等于后者活动开始时间,那么选择后者这个活动,反之,继续比较下下一个活动,再次进行判断
  4. 重复进行第三步,直到所有活动比较完成
import java.util.*;

/**
 * 活动选择:
 * 大概形式为:在某一个地方,有多个活动要进行,活动有开始时间和结束时间,我们该如何选择才能在这一天中这一个地点,举办最多的活动
 * 求解最多的活动个数为?
 */
class Play{
    
    
    int preTime;
    int endTime;//表示活动的开始时间和结束时间
}
public class 活动选择 {
    
    
    public static void main(String[] args) {
    
    
        Scanner scanner=new Scanner(System.in);
        //我们可以用容器来存储各个活动,然后进行贪心算法,最后得到结果
        //我们可以用链表来进行存储,排序
        LinkedList<Play> linkedList=new LinkedList();
        int n=scanner.nextInt();//表示活动的个数
        for (int i = 0; i <n ; i++) {
    
    
            //提案写每一个活动的开始时间和结束时间
            Play play=new Play();
            play.preTime=scanner.nextInt();
            play.endTime=scanner.nextInt();
            linkedList.add(play);//存储链表中,然后进行对于结束时间排序
        }
        Collections.sort(linkedList, new Comparator<Play>() {
    
    
            @Override
            public int compare(Play o1, Play o2) {
    
    
                return o1.endTime-o2.endTime;
            }
        });
        //排序完成
        //然后进行判别,从第一个活动开始
        int num=0;//计数
        Play Time=linkedList.get(0);
        for (int i = 0; i <linkedList.size() ; i++) {
    
    
            if(Time.endTime<=linkedList.get(i).preTime){
    
    
                Time=linkedList.get(i);
                num++;
            }
        }
        System.out.println(num);//得到
    }
}

coin change problem

The general description of the problem is: Suppose there are a, b, c, d, e, f banknotes of 1 yuan, 2 yuan, 5 yuan, 10 yuan, 20 yuan, 50 yuan, and 100 yuan respectively. Now to use these money to pay K yuan, at least how many banknotes should be used? This problem is somewhat similar to the previous problem. To solve this problem, there are generally two arrays, one represents the size of the denomination, and the other represents the number of coins of different denominations, and the array representing the denomination is ordered.

We are solving for the least number of coins, so we can use the greedy algorithm

1.理所当然先使用大额纸币,当大额纸币张数不够的时候,再使用后面较小面额的纸币,依次递减,直到表示完所有纸币

2.做题方法可以递归也可以迭代

import java.util.*;

public class 钱币找零问题 {
    
    
    public static void main(String[] args) {
    
    
        //题目:指定币值和相应的数量,用最少的数量去凑齐某金额
        //思路:利用贪心算法,我们优先选择面值大的纸币,依次类推,直到凑齐总金额
        Scanner scanner=new Scanner(System.in);
        int num=scanner.nextInt();//输入总金额
        greedy(num);
    }
    public static void greedy(int num){
    
    
        int[] values = {
    
     1, 2, 5, 10, 20, 50, 100 };
        //数量
        int[] counts = {
    
     3, 3, 2, 1, 1, 3, 3 };
        //获取需要各种面值多少张
        int[] result = getNumber(num, values, counts);
        System.out.println("各币值的数量:"+Arrays.toString(result));
    }
    public static int[] getNumber(int sum,int []values,int[]counts){
    
    
        int []result=new int[7];//表示有七种钱币
        //我们要找的是每一种钱币所需的票数
        int add=0;//表示需要的总钱数
        for (int i = values.length-1; i >=0 ; i--) {
    
    
            int num=(sum-add)/values[i];
            if(num>counts[i]){
    
    
                num=counts[i];
            }
            //add加上数值
            add=add+num*values[i];
            result[i]=num;
        }
        return result;
    }
    //或者这样迭代
    public static int[] get(int sum,int []values,int[]counts){
    
    
        int []result=new int[values.length];
        for (int i = values.length-1; i >=0 ; i++) {
    
    
            int num=Math.min(counts[i],sum/values[i]);
            result[i]=num;
            sum=sum-num*values[i];
        }
        return result;
    }
}

knapsack problem

Not 01 Backpack

常见题型为给定n种物品,一个背包,背包的容量是c,二米一个物品i的价值为vi,重量为wi,如何选择装入的物品使得背包的总价值最大?

The knapsack problem here refers to a part of the knapsack. It is not like the 01 knapsack problem where an item cannot be disassembled. The items here can be disassembled and a part of it can be put into the knapsack. From the perspective of the greedy algorithm, what we guarantee is that the total value of the backpack is the largest, that is, to ensure that the individual value of each item put in is as large as possible. (can be disassembled)

Proceed as follows

1.需要将物品按照单位重量价值进行排序。

2.将尽可能多的单位i重量价值最高的物品装入被阿波,若最大单位重量价值的物品全部装入背包后,背包华友多余容量,则选择单位重量价值次高的尽可能多的装入背包中。

3.如果最后一件物品无法装入,那就计算可以装入的比例,然后按照比例装入

import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.Scanner;

/**
 * 贪心的背包 ,有n个物品,一个背包,然后想使得这个背包的价值最大
 * 不是01背包不可拆分物品,这个是可以拆分的
 */
class beiBao{
    
    
    int values;
    int weight;//价值和重量
    double wValues;//单个物品的单位重量的价值
}
public class 背包 {
    
    

    public static void main(String[] args) {
    
    
        Scanner scanner=new Scanner(System.in);
        int n=scanner.nextInt();
        int weight=scanner.nextInt();//背包能承受的重量
        LinkedList<beiBao> linkedList=new LinkedList();
        for (int i = 0; i <n ; i++) {
    
    
            beiBao b=new beiBao();
            b.values=scanner.nextInt();
            b.weight=scanner.nextInt();
            b.wValues=b.values/b.weight;
            linkedList.add(b);
        }
      Collections.sort(linkedList, new Comparator<beiBao>() {
    
    
          @Override
          public int compare(beiBao o1, beiBao o2) {
    
    
              return (int)(o2.wValues-o1.wValues);
          }
      });
        for (int i = 0; i <n ; i++) {
    
    
            System.out.println(linkedList.get(i).wValues);
        }
        double allvalues=0;
        boolean[] flag=new boolean[n];
        for (int i = 0; i < flag.length; i++) {
    
    
            flag[i]=false;//表示没有放入背包中
        }
        for (int i = 0; i <n ; i++) {
    
    
            if(linkedList.get(i).weight<=weight){
    
    
                flag[i]=true;
                weight=weight-linkedList.get(i).weight;
                allvalues+=linkedList.get(i).values;
                System.out.println("重量为:"+linkedList.get(i).weight+"价格为:"+linkedList.get(i).values+"可以完全装入");
            }
        }
        for (int i = 0; i <n ; i++) {
    
    
            if(!flag[i]){
    
    
                double rate=(double)weight/linkedList.get(i).weight;
                allvalues=allvalues+rate*linkedList.get(i).values;
                weight-=linkedList.get(i).weight;
                System.out.println("重量为:"+linkedList.get(i).weight+"价值为:"+linkedList.get(i).values+"装入比例为:"+rate);
            }
        }
        System.out.println("总价值为:"+allvalues);
    }
}

boat crossing problem

The common description of this problem is: there are n people who need to cross the river, and there is only one boat, which can accommodate at most two people. The speed of the string is the speed of the slower of the two people. How long will it take at least to transport n people to the other side.

Assuming that the time spent by these people is stored in an array, arranged in ascending order, that is, from fast to slow, the time spent by each person is time[0], time[1]...time[n]

When the number of people>=4, there are two options for this problem:
1. The fastest and second fastest cross the river first, and then the fastest row the boat back; the second slowest and slowest cross the river, and then the second fastest Back, at this point we calculate the time spent in the following process

icon

The second way:

1. The fastest and slowest to cross the river, then the fastest to row the boat back, the fastest and the second slowest to cross the river, and then the fastest to come back

icon
[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-W2UHDjrr-1678500194755) (C:/Users/Hongyan/AppData/Roaming/Typora/typora-user-images/ image-20230311092120755.png)]

There are two ways to cross the river above. When using it, you can compare which one has the shorter time before using it.

If the number of people is less than 4

1. When the number of people is three, the fixed time at this time is time[0]+time[1]+time[2]

2. When the number of people is two, the fixed time at this time is time[1]

3. When the number of people is one, the fixed time is time[0]

/*
 * 有n个人需要过河,只有一艘船,最多能乘2人,船的运行速度为2人中较慢一人的速度,
 * 过去后还需一个人把船划回来,把n个人运到对岸,最少需要多久。
 */
public class River {
    
    
    public static void main(String[] args) {
    
    
    	int[] times={
    
    1,2,4,5,8};
    	int result=crossRiver(times);
    	System.out.println("所花时间为:"+result);
    }
    
    private static int crossRiver(int[] times){
    
    
    	/*n表示还未过河的人数,初始化为所有人*/
    	int n=times.length;
    	int result=0;
    	while(n>0){
    
    
    		if(n==1){
    
    
    			result=result+times[0];
    			break;
    		}else if(n==2){
    
    
    			result=result+times[0]+times[1];
    			break;
    		}else if(n==3){
    
    
    			result=result+times[0]+times[1]+times[2];
    			break;
    		}else{
    
    
    			/*在每次过河时,在两种方式上进行比较,选择耗时更少的那个*/
    			result=result+Math.min(times[1]+times[0]+times[n-1]+times[1],times[n-1]+times[0]+times[n-2]+times[0]);
    			/*无论采取哪种方式,最后的结果都是讲最慢的和次慢的运送过河,也就是数组的最后两位,所以此处可简单地将数组长度-2*/
    			n=n-2;
    		}
    	}
    	return result;
    }
}

area coverage

This problem describes: given an interval with a length of m, and given the starting point and end point (closed interval) of n line segments, find the least number of line segments that can completely cover the entire interval. The steps are as follows:

  1. Among all the intervals to be selected, the intervals whose starting point and end point are outside the required range are eliminated.
  2. Sort all intervals by origin
  3. The first point is selected by default, and then in the process of selecting points, the following principles need to be followed: the starting point of the new interval should be smaller than the end point of the current interval, and the end point of the new interval should be greater than the starting point of the current interval
  4. Repeat step 3 in a loop until the end point value of the current interval >= the expected end point value, and end the process of finding the interval
import java.util.*;
class quJian{
    
    
    int pre;
    int end;//表示区间的起点和终点
}
public class 区间覆盖 {
    
    
    public static void main(String[] args) {
    
    
        Scanner scanner=new Scanner(System.in);
        //加入区间 10个
        int n=scanner.nextInt();
        int m=scanner.nextInt();
        //假设所求区间范围为[n,m];
        LinkedList <quJian>linkedList=new LinkedList();
        for (int i = 0; i <7 ; i++) {
    
    
            quJian qj=new quJian();
            qj.pre=scanner.nextInt();
            qj.end=scanner.nextInt(); //1.删除起点和终点在所要求范围外的区间
            if(!(qj.pre<n&&qj.end<n||qj.pre>m&&qj.end>m)){
    
    
                linkedList.add(qj);
            }
        }
        //先进行排序
        Collections.sort(linkedList, new Comparator<quJian>() {
    
    
            @Override
            public int compare(quJian o1, quJian o2) {
    
    
                return o1.pre-o2.pre;//按照小区间的起点排下序,升序
            }
        });
       //然后进行选取
        boolean flag=false;
        int count=1;//计数
        System.out.println("起点:"+linkedList.get(0).pre+","+linkedList.get(0).end);
        int end=linkedList.get(0).end;
        for (int i = 1; i <linkedList.size() ; i++) {
    
    
            if(linkedList.get(i).pre<=end&&linkedList.get(i).end>end){
    
    
                end=linkedList.get(i).end;//更新end点
                for (int j = i+1; j <linkedList.size() ; j++) {
    
    
                    //然后从这后面找到最长的end
                    if(linkedList.get(j).end>end){
    
    
                        end=linkedList.get(j).end;
                        count++;//找到一个区间
                        System.out.println(linkedList.get(j).pre+" "+linkedList.get(j).end);
                        if(end>=m){
    
    
                            flag=true;
                        }
                    }

                }
            }
            if(flag){
    
    
                break;
            }
        }
        System.out.println(count);
    }
}

Guess you like

Origin blog.csdn.net/qq_63319459/article/details/129459640