Problema dello zaino frazionario: algoritmo Greedy con esempio

โšก Riepilogo intelligente

Il problema dello zaino frazionario utilizza un algoritmo greedy che ordina i pacchi in base al rapporto valore-peso e preleva gli oggetti in quell'ordine, consentendo a frazioni di oggetti di riempire la capacitร  rimanente per una soluzione ottimale garantita.

  • ๐Ÿ’ก Strategia avida: Ad ogni passo vengono effettuate scelte ottimali a livello locale, nella speranza di raggiungere un ottimo globale per il problema nel suo complesso.
  • ๏ธ Rapporto valore/peso: I pacchetti vengono ordinati in ordine decrescente in base al costo unitario V[i] / W[i] prima che inizi la selezione.
  • ๐Ÿ“ฆ Regola delle frazioni: Una porzione parziale del pacchetto successivo riempie l'eventuale capacitร  rimanente, garantendo una soluzione ottimale per la variante frazionaria.
  • ๏ธ Complessitร : O(n log n) con quick sort o merge sort, dominato dalla fase di ordinamento piuttosto che dal ciclo di selezione.
  • ๐Ÿšซ Limitazione: La stessa regola avida non funziona con il gioco dello zaino 0/1, dove gli oggetti non possono essere divisi, quindi si ricorre alla programmazione dinamica.
  • ๐Ÿš€ Usi: Il caricamento delle merci, l'allocazione del portafoglio, la condivisione della larghezza di banda cloud e la pianificazione delle risorse AI si basano tutti sull'algoritmo Fractional Knapsack.

Problema dello zaino frazionario: algoritmo greedy

Cos'รจ la strategia golosa?

Algoritmi avidi Ad ogni passo, scelgono l'opzione locale migliore nella speranza che una catena di ottimi locali produca una soluzione globalmente ottimale. Come la programmazione dinamica, si concentrano su problemi di ottimizzazione, ma non tornano mai indietro per riconsiderare le decisioni prese in precedenza.

Gli algoritmi greedy sono generalmente semplici da scrivere, veloci (spesso con complessitร  temporale lineare o quadratica), facili da debuggare e leggeri in termini di memoria. Il compromesso รจ che il risultato non รจ sempre ottimale, quindi la strategia funziona solo per problemi che presentano una struttura dimostrata essere sicura per gli algoritmi greedy.

Le strategie greedy risolvono l'ottimizzazione combinatoria costruendo una soluzione A un componente Ai alla volta. Ad ogni passaggio si sceglie Ai in modo ottimale in base ai vincoli correnti e si riduce il problema a un sottoproblema piรน piccolo.

Perchรฉ un metodo greedy sia corretto, devono valere due proprietร :

  1. Proprietร  di scelta avida: Un ottimo locale ad ogni passo conduce a un ottimo globale. La scelta dipende dalle decisioni passate, non da quelle future.
  2. Sottostruttura ottimale: La soluzione ottimale dell'intero problema contiene le soluzioni ottimali dei suoi sottoproblemi.

Un algoritmo greedy ha cinque componenti:

  1. Un insieme di candidati a partire dai quali vengono costruite le soluzioni.
  2. Una funzione di selezione che sceglie il miglior candidato successivo.
  3. Una funzione di fattibilitร  che verifica se un candidato puรฒ estendere l'attuale soluzione parziale.
  4. Una funzione obiettivo che valuta una soluzione completa o parziale.
  5. Una funzione di valutazione che segnala quando la soluzione รจ completa.

L'idea dell'avido

Greedy One ordina i pacchetti esclusivamente in base al valore:

  • Ordina i pacchi in ordine di valore non crescente.
  • Percorri l'elenco ordinato e aggiungi ogni pacco allo zaino se la capacitร  rimanente lo consente.

Questa regola non fornisce sempre la risposta ottimale. Controesempio:

  • Parametri: n = 3, M = 19.
  • Pacchetti: {i = 1; W = 14; V = 20}, {i = 2; W = 6; V = 16}, {i = 3; W = 10; V = 8} โ€” valore elevato ma anche peso elevato.
  • Greedy One sceglie il pacchetto 1 con un valore totale di 20, mentre la scelta ottimale (pacchetto 2, pacchetto 3) raggiunge 24.

L'idea di Greedy Two

Greedy Two ordina i pacchi solo in base al peso:

  • Ordina i pacchi in ordine di peso non decrescente.
  • Percorri l'elenco ordinato e aggiungi ogni pacco allo zaino se la capacitร  rimanente lo consente.

Anche questa regola non รจ ottimale. Controesempio:

  • Parametri: n = 3, M = 11.
  • Confezioni: {i = 1; W = 5; V = 10}, {i = 2; W = 6; V = 16}, {i = 3; W = 10; V = 28} โ€” peso ridotto ma valore basso.
  • Greedy sceglie due opzioni (pacchetto 1, pacchetto 2) per un valore totale di 26, mentre la scelta ottimale (pacchetto 3) raggiunge 28.

L'idea del Tre Avido

Greedy Three risolve entrambi i problemi combinando valore e peso in un'unica chiave di classificazione. รˆ il metodo standard per il problema dello zaino frazionario.

  • Calcola il costo unitario V[i] / W[i] per ogni confezione.
  • Ordina i pacchi in ordine decrescente di costo unitario.
  • Esamina l'elenco ordinato e aggiungi ogni pacco se lo spazio rimanente lo consente.

Greedy Three ordina per costo unitario

Avido Tre ordinamenti in base al costo unitario V[i] / W[i]

Idea: Calcola il rapporto valore-peso V[i] / W[i] per ogni pacco, ordinalo in ordine decrescente e prendi prima il rapporto piรน grande disponibile finchรฉ lo zaino non รจ pieno.

Per il vero Frazionale variante, quando il pacchetto successivo non puรฒ entrare intero, prendi una frazione che riempia esattamente la capacitร  rimanente. Questa regola aggiuntiva รจ ciรฒ che rende Greedy Three dimostrabilmente ottimale su Fractional Knapsack.

Selezione del pacchetto Greedy Three

Passaggi dell'algoritmo

Per la variante branch-and-bound 0/1, l'elenco ordinato dei costi unitari genera un albero di ricerca:

  • Passo 1: Il nodo radice rappresenta uno zaino vuoto. Valore totale = 0. Limite superiore = M ร— costo unitario massimo.
  • Passo 2: Dirama la radice in base al numero di copie del pacchetto con il rapporto piรน elevato che possono essere contenute. Per ogni figlio, ricalcola TotalValue, la capacitร  rimanente M e UpperBound.
  • Passo 3: Espandere prima il figlio con il limite superiore piรน grande, nella speranza di trovare rapidamente una soluzione robusta.
  • Passo 4: Elimina tutti i nodi il cui limite superiore non รจ migliore della migliore soluzione completa corrente.
  • Passo 5: Quando ogni nodo viene espanso o potato, la migliore soluzione completa corrente รจ ottimale.

Pseudocodice per l'algoritmo greedy Fractional Knapsack puro:

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

Complessitร  dell'algoritmo:

  • Utilizzando un semplice ordinamento (selezione o bolla): O(n2).
  • Utilizzando quick sort o merge sort: O(n log n), dominato dalla fase di ordinamento.

Java Code per Greedy Three

Definire il KnapsackPackage classe con peso, valore e costo derivato (il rapporto V/W utilizzato per la classificazione):

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

Quindi crea la funzione che 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);
}

Funzione zainoGreProc() in Java

Funzione zainoGreProc() in Java

Spiegazione del codice:

  1. Avvolgi ogni input in un KnapsackPackage quindi il criterio di ordinamento (rapporto V/W) รจ precalcolato.
  2. Ordina in ordine decrescente di costo.
  3. Prendi ogni confezione intera se ci sta.
  4. Prendi una parte della confezione successiva per riempire lo spazio rimanente.
  5. Interrompere l'operazione non appena la capacitร  residua raggiunge lo zero.

Nota di correzione: l'originale Java ciclo avanzato i solo quando un pacco non entrava, il che causava il prelievo ripetuto dello stesso pacco. La versione sopra riportata avanza di un pacco per iterazione e aggiunge una fase di riempimento frazionario, corrispondente alla vera regola dello zaino frazionario.

Java driver che esegue l'algoritmo su un esempio pratico:

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 per Greedy Three

Innanzitutto definiamo il KnapsackPackage classe. Il __lt__ Questo metodo permette di ordinarlo direttamente in base al costo:

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

Quindi implementa la routine dello zaino frazionario:

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)

Funzione zainoGreProc() in Python

Funzione zainoGreProc() in Python

Nota di correzione: l'originale Python classe definita vuota __init__ senza corpo, il che solleva IndentationErrorLa versione sopra riportata rimuove il costruttore vuoto perchรฉ non รจ necessario.

Driver che esegue l'algoritmo sul primo esempio:

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 per Greedy Three

Definire il KnapsackPackage classe:

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

Implementare Greedy Three con una fase di riempimento frazionario:

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

Funzione ZainoGreProc() in C#

Funzione ZainoGreProc() in C#

Controesempio: Tre avidi su 0/1 zaino

Greedy Three รจ ottimale per la variante Frazionaria, ma nello zaino 0/1 (dove gli oggetti non possono essere divisi) puรฒ essere battuto. Controesempio:

  • Parametri: n = 3, M = 10.
  • Pacchetti: {i = 1; W = 7; V = 9; costo = 9/7}, {i = 2; W = 6; V = 6; costo = 1}, {i = 3; W = 4; V = 4; costo = 1}.
  • Greedy Three sceglie il pacchetto 1 per un valore totale di 9, mentre la scelta ottimale 0/1 (pacchetto 2, pacchetto 3) raggiunge 10.

La lezione: usa Greedy Three solo quando sono ammesse le frazioni. Per la variante 0/1, usa Programmazione dinamica anzichรฉ.

Applicazioni dello zaino frazionato

  • Operazioni di carico in cui merci liquide, in polvere o sfuse possono essere suddivise in base al peso.
  • Allocazione del portafoglio tra opzioni di investimento che accettano finanziamenti parziali.
  • Condivisione della larghezza di banda nel cloud, dove i flussi possono consumare solo una frazione di un collegamento.
  • Pianificazione della CPU secondo un modello a slice di tempo condiviso con carichi di lavoro divisibili.
  • Allocazione delle risorse per l'IA, dove un processo di addestramento puรฒ utilizzare solo una frazione di una GPU.

DOMANDE FREQUENTI

Il problema dello zaino frazionario richiede di riempire uno zaino di capacitร  M con oggetti che possono essere divisi. Ogni oggetto ha un peso e un valore; l'obiettivo รจ massimizzare il valore totale rispettando la capacitร .

Ordinare in base al rapporto valore/peso e prendere prima l'articolo con il rapporto piรน alto รจ dimostrabilmente ottimale perchรฉ qualsiasi scambio con un articolo dal rapporto inferiore riduce il valore totale per unitร  di capacitร . Le frazioni permettono all'ultimo articolo di riempire esattamente lo spazio rimanente.

Il problema dello zaino frazionario permette di prendere una porzione di qualsiasi oggetto e viene risolto tramite un algoritmo di ordinamento greedy basato sul valore e sul peso. 0/1 Zaino richiede elementi interi e necessita di programmazione dinamica per una risposta ottimale.

L'ordinamento in base al rapporto valore-peso domina il tempo di esecuzione. Con quick sort o merge sort l'algoritmo ha una complessitร  temporale di O(n log n). Selection sort o bubble sort la porta a O(n quadrato). Il ciclo di selezione greedy stesso ha una complessitร  temporale di O(n).

Senza frazioni, la scelta avida puรฒ lasciare capacitร  inutilizzata che uno scambio piรน intelligente potrebbe riempire. Il caso classico (W = 7, 6, 4; V = 9, 6, 4; M = 10) sceglie il valore 9 mentre la risposta ottimale 0/1 raggiunge 10.

Carico di merci sfuse, allocazione del portafoglio, condivisione della larghezza di banda cloud, pianificazione delle celle temporali della CPU e allocazione delle risorse di intelligenza artificiale su carichi di lavoro divisibili. Qualsiasi situazione in cui gli articoli possono essere suddivisi in base al peso รจ candidata.

Gli agenti di apprendimento per rinforzo raggruppano le attivitร  cloud entro i limiti della GPU o della memoria e i modelli di apprendimento automatico prevedono buoni ordinamenti branch-and-bound. Nella variante frazionaria, l'approccio greedy rimane ottimale, quindi l'IA si concentra principalmente sul caso 0/1.

Sรฌ. GitHub Copilot genera la struttura per l'ordinamento valore/peso, il ciclo greedy e il passaggio di riempimento frazionario in Java, Python, o C#, e genera test unitari che verificano che l'algoritmo raggiunga l'ottimo noto su set di input classici.

Riassumi questo post con: