Algoritmo codicioso (varios ejemplos convencionales)

Algoritmo codicioso (varios ejemplos convencionales)

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


introducir

Los problemas que se pueden resolver con un algoritmo voraz tienen las siguientes características

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

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

Pasos para el uso

Los pasos básicos para usar el algoritmo voraz:
   1> Establecer un modelo matemático para describir el problema.
   2> Dividir el problema a resolver en varios subproblemas.
   3> Resolver cada subproblema para obtener la solución óptima local del subproblema.
   4> Combinar la solución óptima local del subproblema en una solución del problema original.

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

plantilla

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

Inconvenientes de los algoritmos codiciosos

Los siguientes defectos

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

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

Los defectos 1 y 2 significan que el algoritmo codicioso tiene limitaciones. Por ejemplo, si cambia monedas, si son 8 yuanes, hay 2 yuanes y 5 yuanes, luego 5 yuanes por una pieza, 2 yuanes por una pieza y 1 yuan izquierda. Entonces no se puede calcular. De hecho, se pueden usar cuatro 2 yuanes. Este es un ejemplo

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

El defecto es que el algoritmo codicioso utiliza una determinada estrategia para seleccionar un valor en lugar de recorrer todas las soluciones. Para recorrer todas las soluciones de un determinado problema, a menudo se utiliza el método de retroceso.

ejemplo clásico

1. Problema de selección de actividades

2. Problema de cambio de moneda

3. Revisando el problema de la mochila

4. El problema del barco cruzando el río

5. Problema de cobertura de intervalo

ejemplo común

Preguntas de selección de actividades

Este problema generalmente se puede resolver mediante el algoritmo voraz y la programación dinámica.Usaremos el algoritmo voraz para resolverlo a continuación.
La forma de la pregunta es: para realizar múltiples actividades en un lugar determinado, y cada actividad tendrá un tiempo diferente (diferentes horas de inicio y finalización), ¿cómo organizar tantas actividades como sea posible? La razón por la que este problema se puede resolver con un algoritmo codicioso es que la elección de la siguiente actividad solo puede depender de la fecha límite de la actividad anterior y no es necesario considerar la situación general.

Actividad 1 2 3 4 5 6 7 8 9 10 11
Tiempo de empezar 1 3 0 5 3 5 6 8 8 2 12
Hora de finalización 4 5 6 7 8 9 10 11 12 13 14

Al resolver este problema, es necesario ordenar la hora de finalización, porque la hora de finalización de la actividad anterior afectará la selección de la siguiente actividad, y la hora de inicio de la actividad actual no afectará la selección de la siguiente actividad, por lo que solo mire la hora de finalización. Bueno, use la hora de finalización de la actividad anterior para compararla con la hora de inicio de la siguiente actividad, si la primera es menor que la última, entonces la última es la siguiente actividad, de lo contrario, continúe comparando la hora de inicio de la siguiente actividad

ideas para resolver problemas

  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);//得到
    }
}

problema de cambio de moneda

La descripción general del problema es: supongamos que hay billetes a, b, c, d, e, f de 1 yuan, 2 yuan, 5 yuan, 10 yuan, 20 yuan, 50 yuan y 100 yuan respectivamente. Ahora, para usar este dinero para pagar K yuanes, ¿cuántos billetes se deben usar al menos? Este problema es algo similar al problema anterior, para resolver este problema generalmente hay dos matrices, una representa el tamaño de la denominación y la otra representa el número de monedas de diferentes denominaciones, y se ordena la matriz que representa la denominación.

Estamos resolviendo la menor cantidad de monedas, por lo que podemos usar el algoritmo codicioso

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;
    }
}

problema de mochila

No 01 Mochila

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

El problema de la mochila aquí se refiere a una parte de la mochila. No es como el problema de la mochila 01 donde un artículo no se puede desarmar. Los artículos aquí se pueden desarmar y una parte se puede poner en la mochila. Desde la perspectiva del algoritmo codicioso, lo que garantizamos es que el valor total de la mochila sea el mayor, es decir, que el valor individual de cada artículo puesto sea el mayor posible. (se puede desarmar)

Proceder de la siguiente

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);
    }
}

problema de cruce de barcos

La descripción común de este problema es: hay n personas que necesitan cruzar el río, y solo hay un bote, que puede acomodar como máximo a dos personas.La velocidad de la cuerda es la velocidad de la más lenta de las dos personas. ¿Cuánto tiempo tomará al menos transportar a n personas al otro lado?

Suponiendo que el tiempo empleado por estas personas se almacena en una matriz, dispuesta en orden ascendente, es decir, de rápido a lento, el tiempo empleado por cada persona es tiempo[0], tiempo[1]...tiempo[n]

Cuando el número de personas>=4, hay dos opciones para este problema:
1. El más rápido y el segundo más rápido cruzan el río primero, y luego el más rápido rema el bote de regreso, el segundo más lento y el más lento cruzan el río, y luego el segundo más rápido Atrás, en este punto calculamos el tiempo empleado en el siguiente proceso

icono

La segunda manera:

1. El más rápido y el más lento para cruzar el río, luego el más rápido para remar el bote de regreso, el más rápido y el segundo más lento para cruzar el río, y luego el más rápido para regresar

icono
[Falló la transferencia de la imagen del enlace externo, el sitio de origen puede tener un mecanismo anti-leeching, se recomienda guardar la imagen y cargarla directamente (img-W2UHDjrr-1678500194755) (C:/Users/Hongyan/AppData/Roaming/Typora/ typora-user-images/ imagen-20230311092120755.png)]

Hay dos formas de cruzar el río arriba, al usarlo, puedes comparar cuál tiene menos tiempo antes de usarlo.

Si el número de personas es inferior a 4

1. Cuando el número de personas es tres, el tiempo fijo en este momento es tiempo[0]+tiempo[1]+tiempo[2]

2. Cuando el número de personas es dos, el tiempo fijo en este momento es el tiempo[1]

3. Cuando el número de personas es uno, el tiempo fijo es tiempo[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;
    }
}

cobertura de área

Este problema describe: dado un intervalo con una longitud de m, y dado el punto inicial y el punto final (intervalo cerrado) de n segmentos de línea, encuentre el menor número de segmentos de línea que pueden cubrir completamente todo el intervalo. Los pasos son los siguientes :

  1. Entre todos los intervalos a seleccionar, se eliminan los intervalos cuyo punto de inicio y punto final se encuentran fuera del rango requerido.
  2. Ordenar todos los intervalos por origen
  3. El primer punto se selecciona de manera predeterminada y luego, en el proceso de selección de puntos, se deben seguir los siguientes principios: el punto inicial del nuevo intervalo debe ser más pequeño que el punto final del intervalo actual y el punto final del intervalo actual. el nuevo intervalo debe ser mayor que el punto de inicio del intervalo actual
  4. Repita el paso 3 en un ciclo hasta que el valor del punto final del intervalo actual sea >= el valor del punto final esperado y finalice el proceso de búsqueda del intervalo.
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);
    }
}

Supongo que te gusta

Origin blog.csdn.net/qq_63319459/article/details/129459640
Recomendado
Clasificación