Problema de mochila fraccional: Algoritmo codicioso con ejemplo

โšก Resumen inteligente

El problema de la mochila fraccionaria utiliza un algoritmo voraz que ordena los paquetes segรบn su relaciรณn valor-peso y toma los artรญculos en ese orden, lo que permite que fracciones de los artรญculos llenen la capacidad restante para garantizar una soluciรณn รณptima.

  • ???? Estrategia codiciosa: En cada paso se toman decisiones รณptimas locales con la esperanza de alcanzar un รณptimo global para el problema en su conjunto.
  • ๐Ÿ‡ง๐Ÿ‡ท Relaciรณn valor/peso: Los paquetes se ordenan en orden descendente de costo unitario V[i] / W[i] antes de que comience la selecciรณn.
  • ๐Ÿ“ฆ Regla fraccionaria: Una porciรณn parcial del siguiente paquete llena la capacidad restante, garantizando una soluciรณn รณptima para la variante fraccionaria.
  • ๐Ÿ‡ง๐Ÿ‡ท Complejidad: O(n log n) con ordenaciรณn rรกpida o ordenaciรณn por fusiรณn, dominada por el paso de ordenaciรณn en lugar del bucle de selecciรณn.
  • ๐Ÿšซ Limitaciรณn: La misma regla voraz falla en el problema de la mochila 0/1, donde los elementos no se pueden dividir, por lo que se utiliza la programaciรณn dinรกmica en su lugar.
  • ๐Ÿš€ Usos: La carga de mercancรญas, la asignaciรณn de cartera, el uso compartido del ancho de banda en la nube y la programaciรณn de recursos de IA dependen del modelo de la mochila fraccionaria.

Problema de la mochila fraccionaria: algoritmo voraz

ยฟQuรฉ es la estrategia codiciosa?

Algoritmos codiciosos En cada paso, eligen la mejor opciรณn local con la esperanza de que una cadena de รณptimos locales produzca una soluciรณn รณptima global. Al igual que la programaciรณn dinรกmica, se centran en problemas de optimizaciรณn, pero nunca revisan decisiones anteriores.

Los algoritmos voraces suelen ser sencillos de escribir, rรกpidos (a menudo con tiempo lineal o cuadrรกtico), fรกciles de depurar y consumen poca memoria. La desventaja es que el resultado no siempre es รณptimo, por lo que esta estrategia solo funciona para problemas con una estructura que ha demostrado ser segura para algoritmos voraces.

Las estrategias voraces resuelven la optimizaciรณn combinatoria construyendo una soluciรณn A, un componente Ai a la vez. En cada paso, se elige Ai de forma รณptima bajo las restricciones actuales y se reduce el problema a un subproblema mรกs pequeรฑo.

Para que un mรฉtodo voraz sea correcto, deben cumplirse dos propiedades:

  1. Propiedad de elecciรณn codiciosa: Un รณptimo local en cada paso conduce a un รณptimo global. La elecciรณn depende de decisiones pasadas, pero no de decisiones futuras.
  2. Subestructura รณptima: La soluciรณn รณptima del problema completo contiene las soluciones รณptimas de sus subproblemas.

Un algoritmo codicioso tiene cinco componentes:

  1. Un conjunto de candidatos a partir del cual se construyen soluciones.
  2. Una funciรณn de selecciรณn que elige al mejor candidato para el siguiente paso.
  3. Una funciรณn de viabilidad que comprueba si un candidato puede ampliar la soluciรณn parcial actual.
  4. Una funciรณn objetivo que valora una soluciรณn completa o parcial.
  5. Una funciรณn de evaluaciรณn que indica cuรกndo se ha completado la soluciรณn.

La idea del codicioso

Greedy One clasifica los paquetes รบnicamente por su valor:

  • Ordena los paquetes en orden descendente de valor.
  • Recorre la lista ordenada y aรฑade cada paquete a la mochila si la capacidad restante lo permite.

Esta regla no siempre da la respuesta รณptima. Contraejemplo:

  • Parรกmetros: n = 3, M = 19.
  • Paquetes: {i = 1; W = 14; V = 20}, {i = 2; W = 6; V = 16}, {i = 3; W = 10; V = 8} โ€” alto valor pero tambiรฉn alto peso.
  • El usuario Greedy One elige el paquete 1 con un valor total de 20, mientras que la elecciรณn รณptima (paquete 2, paquete 3) alcanza los 24.

La idea de los dos codiciosos

Greedy Two clasifica los paquetes รบnicamente por peso:

  • Clasifique los paquetes en orden no decreciente de peso.
  • Recorre la lista ordenada y aรฑade cada paquete a la mochila si la capacidad restante lo permite.

Esta regla tampoco es รณptima. Contraejemplo:

  • Parรกmetros: n = 3, M = 11.
  • Paquetes: {i = 1; W = 5; V = 10}, {i = 2; W = 6; V = 16}, {i = 3; W = 10; V = 28} โ€” peso ligero pero bajo valor.
  • El algoritmo Greedy Two elige (paquete 1, paquete 2) con un valor total de 26, mientras que la elecciรณn รณptima (paquete 3) alcanza 28.

La idea de los tres codiciosos

El algoritmo Greedy Three corrige ambos fallos combinando valor y peso en una รบnica clave de clasificaciรณn. Es el mรฉtodo estรกndar para el problema de la mochila fraccionaria.

  • Calcula el costo unitario V[i] / W[i] para cada paquete.
  • Clasifique los paquetes en orden descendente segรบn su costo unitario.
  • Recorre la lista ordenada y aรฑade cada paquete si la capacidad restante lo permite.

Greedy Three ordena por costo unitario

Tres algoritmos codiciosos se clasifican por costo unitario V[i] / W[i]

Idea: Calcula la relaciรณn valor-peso V[i] / W[i] para cada paquete, ordรฉnalos en orden descendente y toma primero la relaciรณn mรกs grande disponible hasta que la mochila estรฉ llena.

por la verdad Fraccionario En esta variante, cuando el siguiente paquete no cabe completo, se toma una fracciรณn que llena exactamente la capacidad restante. Esta regla adicional es lo que hace que el algoritmo Greedy Three sea demostrablemente รณptimo en el problema de la mochila fraccionaria.

Selecciรณn de paquetes Greedy Three

Pasos del algoritmo

Para la variante de ramificaciรณn y acotaciรณn 0/1, la lista de costos unitarios ordenada impulsa un รกrbol de bรบsqueda:

  • Paso 1: El nodo raรญz representa una mochila vacรญa. TotalValue = 0. UpperBound = M ร— coste unitario mรกximo.
  • Paso 2: Ramifique la raรญz segรบn cuรกntas copias del paquete de mayor proporciรณn puedan caber. Para cada hijo, vuelva a calcular TotalValue, la capacidad restante M y UpperBound.
  • Paso 3: Primero, expanda el elemento hijo con el lรญmite superior mรกs grande, con la esperanza de encontrar rรกpidamente una soluciรณn sรณlida.
  • Paso 4: Pode cualquier nodo cuyo lรญmite superior no sea mejor que la mejor soluciรณn completa actual.
  • Paso 5: Cuando cada nodo se expande o se poda, la mejor soluciรณn completa actual es la รณptima.

Pseudocรณdigo para el algoritmo voraz de la mochila fraccionaria pura:

Fractional Knapsack (Array W, Array V, int M)
1. for i <- 1 to size(V)
2.     cost[i] <- V[i] / W[i]
3. Sort-Descending(cost)
4. total <- 0
5. i <- 1
6. while (i <= size(V) and M > 0)
7.     if W[i] <= M
8.         M <- M - W[i]
9.         total <- total + V[i]
10.        i <- i + 1
11.    else
12.        total <- total + V[i] * (M / W[i])
13.        M <- 0

Complejidad del algoritmo:

  • Usando una ordenaciรณn simple (selecciรณn o burbuja): O(n2).
  • Usando ordenaciรณn rรกpida o ordenaciรณn por fusiรณn: O(n log n), dominado por el paso de ordenaciรณn.

Java Code para Greedy Three

Definir el KnapsackPackage Clase con peso, valor y costo derivado (la relaciรณn V/W utilizada para la clasificaciรณn):

public class KnapsackPackage {

    private double weight;
    private double value;
    private Double cost;

    public KnapsackPackage(double weight, double value) {
        super();
        this.weight = weight;
        this.value = value;
        this.cost = Double.valueOf(value / weight);
    }

    public double getWeight() { return weight; }

    public double getValue() { return value; }

    public Double getCost()  { return cost; }
}

Luego, crea la funciรณn que implementa Greedy Three:

public void knapsackGreProc(int W[], int V[], int M, int n) {
    KnapsackPackage[] packs = new KnapsackPackage[n];
    for (int i = 0; i < n; i++) {
        packs[i] = new KnapsackPackage(W[i], V[i]);
    }

    Arrays.sort(packs, new Comparator<KnapsackPackage>() {
        @Override
        public int compare(KnapsackPackage a, KnapsackPackage b) {
            return b.getCost().compareTo(a.getCost());
        }
    });

    double remain = M;
    double result = 0d;

    for (int i = 0; i < n && remain > 0; i++) {
        if (packs[i].getWeight() <= remain) {
            remain -= packs[i].getWeight();
            result += packs[i].getValue();
            System.out.println("Pack " + i + " - Weight " + packs[i].getWeight()
                             + " - Value " + packs[i].getValue());
        } else {
            double fraction = remain / packs[i].getWeight();
            result += packs[i].getValue() * fraction;
            System.out.println("Pack " + i + " - Fraction " + fraction
                             + " - Value " + packs[i].getValue() * fraction);
            remain = 0;
        }
    }

    System.out.println("Max Value:\t" + result);
}

Funciรณn mochilaGreProc() en Java

Funciรณn mochilaGreProc() en Java

Explicaciรณn del cรณdigo:

  1. Envuelva cada entrada en un KnapsackPackage Por lo tanto, la clave de ordenaciรณn (relaciรณn V/W) se calcula previamente.
  2. Ordenar en orden descendente de costo.
  3. Coge cada paquete entero si cabe.
  4. Toma una fracciรณn del siguiente paquete para llenar la capacidad restante.
  5. Detรฉngase en cuanto la capacidad restante llegue a cero.

Nota de correcciรณn: el original Java bucle avanzado i Solo cuando un paquete no cabรญa, lo que provocaba que se tomara el mismo paquete repetidamente. La versiรณn anterior avanza un paquete por iteraciรณn y aรฑade un paso de llenado fraccional, que coincide con la regla de la mochila fraccional.

Java Controlador que ejecuta el algoritmo en un ejemplo prรกctico:

public void run() {
    int W[] = new int[]{15, 10, 2, 4};
    int V[] = new int[]{30, 25, 2, 6};
    int M = 37;
    int n = V.length;
    knapsackGreProc(W, V, M, n);
}

Python3 Code para Greedy Three

Primero defina el KnapsackPackage clase. La __lt__ Este mรฉtodo permite ordenarlo directamente por coste:

class KnapsackPackage(object):
    """Knapsack Package Data Class"""

    def __init__(self, weight, value):
        self.weight = weight
        self.value  = value
        self.cost   = value / weight

    def __lt__(self, other):
        return self.cost < other.cost

A continuaciรณn, implementa la rutina de la mochila fraccionaria:

class FractionalKnapsack(object):

    def knapsackGreProc(self, W, V, M, n):
        packs = [KnapsackPackage(W[i], V[i]) for i in range(n)]
        packs.sort(reverse=True)

        remain = M
        result = 0

        for i in range(n):
            if remain == 0:
                break
            if packs[i].weight <= remain:
                remain -= packs[i].weight
                result += packs[i].value
                print("Pack", i, "- Weight", packs[i].weight, "- Value", packs[i].value)
            else:
                fraction = remain / packs[i].weight
                result += packs[i].value * fraction
                print("Pack", i, "- Fraction", fraction,
                      "- Value", packs[i].value * fraction)
                remain = 0

        print("Max Value:", result)

Funciรณn mochilaGreProc() en Python

Funciรณn mochilaGreProc() en Python

Nota de correcciรณn: el original Python La clase definiรณ un vacรญo __init__ sin cuerpo, lo cual eleva IndentationErrorLa versiรณn anterior elimina el constructor vacรญo porque no es necesario.

Controlador que ejecuta el algoritmo en el primer ejemplo:

if __name__ == "__main__":
    W = [15, 10, 2, 4]
    V = [30, 25, 2, 6]
    M = 37
    n = 4

    proc = FractionalKnapsack()
    proc.knapsackGreProc(W, V, M, n)

C# Code para Greedy Three

Definir el KnapsackPackage clase:

using System;

namespace KnapsackProblem
{
    public class KnapsackPackage
    {
        private double weight;
        private double value;
        private double cost;

        public KnapsackPackage(double weight, double value)
        {
            this.weight = weight;
            this.value  = value;
            this.cost   = value / weight;
        }

        public double Weight { get { return weight; } }
        public double Value  { get { return value; } }
        public double Cost   { get { return cost; } }
    }
}

Implementar Greedy Three con un paso de relleno fraccional:

public void KnapsackGreProc(int[] W, int[] V, int M, int n)
{
    KnapsackPackage[] packs = new KnapsackPackage[n];
    for (int k = 0; k < n; k++)
        packs[k] = new KnapsackPackage(W[k], V[k]);

    Array.Sort<KnapsackPackage>(packs,
        (a, b) => b.Cost.CompareTo(a.Cost));

    double remain = M;
    double result = 0d;

    for (int i = 0; i < n && remain > 0; i++)
    {
        if (packs[i].Weight <= remain)
        {
            remain -= packs[i].Weight;
            result += packs[i].Value;
            Console.WriteLine("Pack " + i + " - Weight " + packs[i].Weight
                            + " - Value " + packs[i].Value);
        }
        else
        {
            double fraction = remain / packs[i].Weight;
            result += packs[i].Value * fraction;
            Console.WriteLine("Pack " + i + " - Fraction " + fraction
                            + " - Value " + packs[i].Value * fraction);
            remain = 0;
        }
    }

    Console.WriteLine("Max Value:\t" + result);
}

Funciรณn KnapsackGreProc() en C#

Funciรณn KnapsackGreProc() en C#

Contraejemplo: Tres codiciosos en mochila 0/1

Greedy Three es รณptimo para la variante Fraccional, pero en la mochila 0/1 (donde los objetos no se pueden dividir) puede ser superado. Contraejemplo:

  • Parรกmetros: n = 3, M = 10.
  • Paquetes: {i = 1; W = 7; V = 9; coste = 9/7}, {i = 2; W = 6; V = 6; coste = 1}, {i = 3; W = 4; V = 4; coste = 1}.
  • El algoritmo Greedy Three elige el paquete 1 para un valor total de 9, mientras que la elecciรณn รณptima 0/1 (paquete 2, paquete 3) alcanza 10.

La lecciรณn: utilice Greedy Three solo cuando se permitan fracciones. Para la variante 0/1, utilice Programaciรณn dinรกmica .

Aplicaciones del modelo de la mochila fraccionada

  • Carga de mercancรญas donde los productos lรญquidos, en polvo o a granel se pueden dividir por peso.
  • Asignaciรณn de cartera entre opciones de inversiรณn que aceptan financiaciรณn parcial.
  • Comparticiรณn de ancho de banda en la nube, donde los flujos pueden consumir una fracciรณn de un enlace.
  • Planificaciรณn de la CPU bajo un modelo de divisiรณn de tiempo compartida con cargas de trabajo divisibles.
  • Asignaciรณn de recursos de IA donde una tarea de entrenamiento puede usar una fracciรณn de una GPU.

Preguntas Frecuentes

El problema de la mochila fraccionada consiste en llenar una mochila de capacidad M con artรญculos que pueden dividirse. Cada artรญculo tiene un peso y un valor; el objetivo es maximizar el valor total respetando la capacidad de la mochila.

Ordenar por relaciรณn valor-peso y tomar primero el de mayor relaciรณn es demostrablemente รณptimo, ya que cualquier cambio hacia un artรญculo de menor relaciรณn reduce el valor total por unidad de capacidad. Las fracciones permiten que el รบltimo artรญculo ocupe exactamente el espacio restante.

El problema de la mochila fraccionaria te permite tomar una porciรณn de cualquier artรญculo y se resuelve mediante una ordenaciรณn voraz por valor/peso. 0/1 Mochila Requiere elementos completos y necesita programaciรณn dinรกmica para obtener una respuesta รณptima.

La ordenaciรณn por relaciรณn valor-peso domina el tiempo de ejecuciรณn. Con la ordenaciรณn rรกpida o la ordenaciรณn por fusiรณn, el algoritmo se ejecuta en O(n log n). La ordenaciรณn por selecciรณn o la ordenaciรณn de burbuja lo elevan a O(n al cuadrado). El bucle de selecciรณn voraz en sรญ mismo es O(n).

Sin fracciones, la selecciรณn codiciosa puede dejar capacidad sin usar que un intercambio mรกs inteligente llenarรญa. El caso clรกsico (W = 7, 6, 4; V = 9, 6, 4; M = 10) elige el valor 9, mientras que la respuesta รณptima 0/1 llega a 10.

Carga de mercancรญas a granel, asignaciรณn de cartera, comparticiรณn de ancho de banda en la nube, programaciรณn de intervalos de tiempo de CPU y asignaciรณn de recursos de IA entre cargas de trabajo divisibles. Cualquier situaciรณn en la que los artรญculos puedan dividirse por peso es candidata.

Los agentes de aprendizaje por refuerzo optimizan las tareas en la nube, respetando los lรญmites de la GPU o la memoria, y los modelos de aprendizaje automรกtico predicen buenos ordenamientos de ramificaciรณn y acotaciรณn. En la variante fraccionaria, el algoritmo voraz sigue siendo รณptimo, por lo que la IA se centra principalmente en el caso 0/1.

Sรญ. GitHub Copilot genera la ordenaciรณn por valor/peso, el bucle voraz y el paso de relleno fraccional en Java, Python, o C#, y genera pruebas unitarias que verifican que el algoritmo alcanza el รณptimo conocido en conjuntos de entrada clรกsicos.

Resumir este post con: