Tört hátizsák probléma: Mohó algoritmus példával

Mi az a mohó stratégia?

A mohó algoritmusok olyanok, mint a dinamikus programozási algoritmusok, amelyeket gyakran az optimális problémák megoldására használnak (a probléma legjobb megoldásainak megtalálása egy adott kritérium szerint).

A mohó algoritmusok optimális helyi kijelöléseket valósítanak meg abban a reményben, hogy ezek a kijelölések a megoldandó probléma optimális globális megoldásához vezetnek. A mohó algoritmusokat gyakran nem túl nehéz beállítani, gyorsak (az időbonyolultság gyakran lineáris függvény vagy nagyon is másodrendű függvény). Ezenkívül ezeket a programokat nem nehéz hibakeresni, és kevesebb memóriát használnak. De az eredmények nem mindig jelentik az optimális megoldást.

A mohó stratégiákat gyakran alkalmazzák a kombinatorikus optimalizációs probléma megoldására egy A opció felépítésével. Az A opciót úgy állítják össze, hogy az A minden egyes Ai komponensét kijelölik, amíg az elkészül (elég n komponens). Minden Ai-hez optimálisan választja az Ai-t. Ily módon előfordulhat, hogy az utolsó lépésben nem kell mást választania, mint elfogadni az utolsó megmaradt értéket.

A mohó döntéseknek két kritikus összetevője van:

  1. A mohó kiválasztás módja. Kiválaszthatja, hogy jelenleg melyik megoldás a legjobb, majd megoldhatja az utolsó kiválasztás során felmerülő részproblémát. A mohó algoritmusok kiválasztása a korábbi kiválasztásoktól függhet. De ez nem függhet semmilyen jövőbeli kiválasztástól vagy részproblémák megoldásától. Az algoritmus úgy fejlődik, hogy ciklusban választja ki, ugyanakkor az adott problémát kisebb részproblémákká zsugorítja.
  2. Optimális alépítmény. Egy probléma optimális alstruktúráját akkor hajtja végre, ha ennek a feladatnak az optimális megoldása tartalmazza a részproblémák optimális megoldásait.

Egy mohó algoritmusnak öt összetevője van:

  1. Jelöltek halmaza, amelyekből megoldásokat lehet alkotni.
  2. Kiválasztási funkció a megoldáshoz a legjobb jelölt kiválasztásához.
  3. Egy megvalósítható függvényt használnak annak eldöntésére, hogy egy jelölt felhasználható-e megoldás felépítésére.
  4. Célfüggvény, amely egy megoldás vagy egy hiányos megoldás értékét rögzíti.
  5. Értékelő funkció, amely jelzi, ha teljes megoldást talál.

A Mohó Egy ötlete

Az első ötlettel a Greedy One következő lépései vannak:

  • Rendezés az értékek nem növekvő sorrendjében.
  • A megrendelt csomagokat viszont mérlegelje, a mérlegelési csomagot hátizsákba tegye, ha a hátizsák fennmaradó kapacitása elegendő annak befogadására (ami azt jelenti, hogy a hátizsákba helyezett csomagok össztömege és a figyelembe vett csomagok tömege nem haladja meg a a hátizsák kapacitása).

Ez a mohó algoritmus azonban nem mindig adja meg az optimális megoldást. Itt van egy ellenpélda:

  • A feladat paraméterei: n = 3; M = 19.
  • A csomagok: {i = 1; W[i] = 14; V[i] = 20}; {i = 2; W[i] = 6; V[i] = 16}; {i = 3; W[i] = 10; V[i] = 8} -> Nagy érték, de nagy súly is.
  • Az algoritmus az 1-es csomagot 20-as összértékű, míg a feladat optimális megoldását (2-es csomag, 3-as csomag) 24-es összértékkel választja ki.

A Mohó Kettő ötlete

A második ötlettel a Greedy Two következő lépései vannak:

  • Rendezés a súlyok nem csökkenő sorrendjében.
  • A megrendelt csomagokat viszont mérlegelje, a mérlegelési csomagot hátizsákba tegye, ha a hátizsák fennmaradó kapacitása elegendő annak befogadására (ami azt jelenti, hogy a hátizsákba helyezett csomagok össztömege és a figyelembe vett csomagok tömege nem haladja meg a a hátizsák kapacitása).

Ez a mohó algoritmus azonban nem mindig adja meg az optimális megoldást. Itt van egy ellenpélda:

  • A feladat paraméterei: n = 3; M = 11.
  • A csomagok: {i = 1; W[i] = 5; V[i] = 10}; {i = 2; W[i] = 6; V[i] = 16}; {i = 3; W[i] = 10; V[i] = 28} -> Könnyű, de az értéke is nagyon könnyű.
  • Az algoritmus (1. csomag, 2. csomag) 26-os összértéket választ, míg a feladat optimális megoldása (3. csomag) 28-as összértékkel.

A Mohó Három ötlete

A harmadik ötlettel a Greedy Three következő lépései vannak. Valójában ez a legszélesebb körben használt algoritmus.

  • A csomagokat az egységköltség értékének nem növekedése szerinti sorrendben rendezze A Mohó Három ötlete. Neked van:

A Mohó Három ötlete

  • A megrendelt csomagokat viszont mérlegelje, a mérlegelési csomagot hátizsákba tegye, ha a hátizsák fennmaradó kapacitása elegendő annak befogadására (ami azt jelenti, hogy a hátizsákba helyezett csomagok össztömege és a figyelembe vett csomagok tömege nem haladja meg a a hátizsák kapacitása).

Ötlet: A probléma mohó ötlete az, hogy kiszámítsuk a A Mohó Három ötlete mindegyik aránya A Mohó Három ötlete. Ezután rendezze ezeket az arányokat csökkenő sorrendben. A legmagasabbat fogja választani A Mohó Három ötlete csomag és a hátizsák kapacitása tartalmazhatja azt a csomagot (maradvány > wi). Minden alkalommal, amikor egy csomagot a hátizsákba tesznek, az a hátizsák kapacitását is csökkenti.

A csomagok kiválasztásának módja:

  • Tekintsük az egységköltségek tömbjét. Csomagokat a csökkenő egységköltségek szerint választja ki.

A Mohó Három ötlete

  • Tegyük fel, hogy talált egy részmegoldást: (x1,…, Xi).
  • A hátizsák értékét megkapjuk:

A Mohó Három ötlete

  • A hátizsákba helyezett csomagok súlyának megfelelően:

A Mohó Három ötlete

  • Ezért a hátizsák fennmaradó súlyhatára:

A Mohó Három ötlete

Az algoritmus lépései

Látod, ez a probléma a max. A csomagok listája az egységköltségek csökkenő sorrendjében van rendezve, hogy fontolóra vegye az elágazást.

  • 1 lépés: A csomópont gyökér a hátizsák kezdeti állapotát jelenti, ahol nem választott ki egyetlen csomagot sem.
  • TotalValue = 0.
  • A gyökércsomópont felső határa UpperBound = M * Maximális egységköltség.
  • 2 lépés: A csomópontgyökér lesz a gyermek csomópontok, amelyek megfelelnek a legnagyobb egységköltségű csomag kiválasztásának képességének. Minden csomópontnál újra kell számítani a paramétereket:
  • TotalValue = TotalValue (régi) + a kiválasztott csomagok száma * minden csomag értéke.
  • M = M (régi) – a kiválasztott csomagok száma * minden csomag súlya.
  • UpperBound = TotalValue + M (új) * A következőként figyelembe veendő csomag egységköltsége.
  • 3 lépés: Az utódcsomópontokban a nagyobb felső korláttal rendelkező csomópont elágazását részesíti előnyben. Ennek a csomópontnak a gyermekei megfelelnek a következő nagy egységköltségű csomag kiválasztásának képességének. Minden csomópontnál újra kell számítani a TotalValue, M, UpperBound paramétereket a 2. lépésben említett képlet szerint.
  • 4 lépés: Ismételje meg a 3. lépést azzal a megjegyzéssel: ha a felső határérték kisebb vagy egyenlő egy talált opció ideiglenes maximális költségével, akkor többé nem kell elágaznia az adott csomóponthoz.
  • 5 lépés: Ha minden csomópont elágazó vagy levágott, akkor a legdrágább megoldás az, amelyet keresni kell.

Az algoritmus pszeudo kódja:

Fractional Knapsack (Array W, Array V, int M)
1. for i <- 1 to size (V)
2. 	calculate cost[i] <- V[i] / W[i]
3. Sort-Descending (cost)
4. i ← 1
5. while (i <= size(V))
6. 	if  W[i] <= M 
7.		M ← M – W[i]
8.		total ← total + V[i];
9. 	if  W[i] > M
10. 		i ← i+1

Az algoritmus összetettsége:

  • Ha egy egyszerű rendezési algoritmust használunk (kijelölés, buborék…), akkor az egész probléma összetettsége O(n2).
  • Ha gyors rendezést vagy összevonási rendezést használ, akkor az egész probléma összetettsége O(nlogn).

Java a Greedy Three kódja

  • Először is meg kell határoznia a KnapsackPackage osztályt. Ennek az osztálynak a következő tulajdonságai vannak: minden csomag súlya, értéke és megfelelő költsége. Ennek az osztálynak az ingatlanköltsége a fő algoritmusban a rendezési feladathoz használatos. Az egyes költségek értéke a Torkos Három az egyes csomagok aránya.
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 = new Double(value / weight);
	}

	public double getWeight() {
		return weight;
	}

	public double getValue() {
		return value;
	}

	public Double getCost() {
		return cost;
	}

}
  • Ezután létrehoz egy függvényt a Greedy Three algoritmus végrehajtásához.
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 kPackA, KnapsackPackage kPackB) {
			return kPackB.getCost().compareTo(kPackA.getCost());
		}
	});
	
	int remain = M;	
double result = 0d;
	
	int i = 0;
	boolean stopProc = false;
	while (!stopProc) {
		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());
		}
		
		if (packs[i].getWeight() > remain) {
			i ++;
		}
		
		if (i == n) {
			stopProc = true;
		}
	}
	
	System.out.println("Max Value:\t" + result);
}
A knapsackGreProc() in Java
A knapsackGreProc() in Java

A kód magyarázata:

  1. Minden hátizsák-csomag súlyát és értékét inicializálja.
  2. Válogassa szét a hátizsákcsomagokat költség szerint, csökkenő sorrendben.
  3. Ha az i. csomagot választja.
  4. Ha kiválasztja a csomagok számát, az i elég.
  5. Állítsa le az összes csomag böngészésekor.

Ebben az oktatóanyagban két példa van. Íme a java kód a fenti program futtatásához, két példával:

public void run() {
	/*
	 * Pack and Weight - Value
	 */
//int W[] = new int[]{15, 10, 2, 4};
	int W[] = new int[]{12, 2, 1, 1, 4};
	
//int V[] = new int[]{30, 25, 2, 6};
	int V[] = new int[]{4, 2, 1, 2, 10};
	
	/*
	 * Max Weight
	 */
//int M = 37;
	int M = 15;
	int n = V.length;
	
	/*
	 * Run the algorithm
	 */
	knapsackGreProc(W, V, M, n);
}

Megvan a kimenet:

  • Első példa:
Pack 0 - Weight 10.0 - Value 25.0
Pack 0 - Weight 10.0 - Value 25.0
Pack 0 - Weight 10.0 - Value 25.0
Pack 2 - Weight 4.0 - Value 6.0
Pack 3 - Weight 2.0 - Value 2.0
Max Value:	83.0
  • Második példa:
Pack 0 - Weight 4.0 - Value 10.0
Pack 0 - Weight 4.0 - Value 10.0
Pack 0 - Weight 4.0 - Value 10.0
Pack 1 - Weight 1.0 - Value 2.0
Pack 1 - Weight 1.0 - Value 2.0
Pack 1 - Weight 1.0 - Value 2.0
Max Value:	36.0

Elemezze az első példát:

  • A feladat paraméterei: n = 4; M = 37.
  • A csomagok: {i = 1; W[i] = 15; V[i] = 30; Költség = 2.0}; {i = 2; W[i] = 10; V[i] = 25; Költség = 2.5}; {i = 3; W[i] = 2; V[i] = 4; Költség = 1.0}; {i = 4; W[i] = 4; V[i] = 6; Költség = 1.5}.
  • A csomagokat a fajlagos költségek értékének növekedése szerinti sorrendbe rendezi. Önnek van: {i = 2} -> {i = 1} -> {i = 4} -> {i = 3}.

Az első példa algoritmusának alkalmazásának lépései:

  • Határozza meg az x1, x2, x3, x4 az egyes kiválasztott csomagok számát, amelyek megfelelnek az {i = 2} csomagnak -> {i = 1} -> {i = 4} -> {i = 3}.
  • Az N csomópont gyökér azt az állapotot jelöli, hogy nem választott ki egyetlen csomagot sem. Akkor:
    • TotalValue = 0.
    • M = 37 (a javaslat szerint).
    • UpperBound = 37 * 2.5 = 92.5, ebből 37 M, 2.5 pedig az {i = 2} csomag egységköltsége.
  • Az {i = 2} csomaggal 4 lehetőséged van: válassz 3 csomagot {i = 2} (x1 = 3); válasszon 2 csomagot {i = 2} (x1 = 2); válasszon 1 csomagot {i = 2} (x1 = 1), és ne válassza ki az {i = 2} csomagot (x1 = 0). Ennek a 4 lehetőségnek megfelelően az N gyökércsomópontot leágazza 4 gyermekre: N[1], N[2], N[3] és N[4].
  • Az N1 gyermekcsomóponthoz a következőkre van szüksége:
    • TotalValue = 0 + 3 * 25 = 75, ahol 3 a kiválasztott {i = 2} csomag száma, és 25 az egyes csomagok {i = 2} értéke.
    • M = 37 – 3 * 10 = 7, ahol 37 a hátizsák kezdeti mennyisége, 3 a csomagok száma {i = 2}, 10 az egyes csomagok súlya {i = 2}.
    • UpperBound = 75 + 7 * 2 = 89, ahol 75 a TotalValue, 7 a hátizsák fennmaradó súlya és 2 a csomag egységköltsége {i = 1}.
  • Hasonlóképpen kiszámíthatja az N[2], N[3] és N[4] csomópontok paramétereit, amelyekben a felső határ 84, 79 és 74.
  • Az N[1], N[2], N[3] és N[4] csomópontok közül az N[1] csomópont rendelkezik a legnagyobb felső határral, így először az N[1] csomópontot kell elágazni abban a reményben, hogy lesz egy jó terv ebből az irányból.
  • Az N[1] csomópontból csak egy N[1-1] gyermekcsomópontja van, amely x2 = 0-nak felel meg (a hátizsák fennmaradó súlya miatt 7, míg az egyes csomagok súlya {i = 1} 15) . Az N[1-1] gomb paramétereinek meghatározása után az N[1-1] felső határa 85.5.
  • Folytatja az N[1-1] csomópont elágazását. Az N[1-1] csomópontnak 2 gyermeke van: N[1-1-1] és N[1-1-2], amelyek x3 = 1 és x3 = 0 értéknek felelnek meg. A két csomópont paramétereinek meghatározása után láthatja, hogy a Az N[1-1-1] felső határa 84, az N[1-1-2]é pedig 82, tehát folytatja az N[1-1-1] csomópont elágazását.
  • Az N[1-1-1] csomópontnak két gyermeke van, N[1-1-1-1] és N[1-1-1-2], amelyek megfelelnek x4 = 1 és x4 = 0. Ez két levélcsomópont (az opciót jelenti), mert minden csomóponthoz a csomagok száma lett kiválasztva. Amelyben az N[1-1-1-1] csomópont az x1 = 3, x2 = 0, x3 = 1 és x4 = 1 opciót jelöli 83 esetén, míg az N[1-1-1-2] csomópont az x1 opciót = 3, x2 = 0, x3 = 1 és x4 = 01 81-nél. Tehát az ideiglenes maximális érték itt 83.
  • Ha visszakanyarodunk az N[1-1-2] csomóponthoz, azt látjuk, hogy az N[1-1-2] felső határa 82 < 83, tehát levágja az N[1-1-2] csomópontot.
  • Visszatérve az N2 csomóponthoz, azt látja, hogy az N2 felső határa 84 > 83, tehát folytatja az N2 csomópont elágazását. Az N2 csomópontnak két gyermeke van: N[2-1] és N[2-2], amelyek x2 = 1 és x2 = 0. Az N[2-1] és N[2-2] paraméterek kiszámítása után látni fogjuk, hogy az N[2-1] felső határa 83, az N[2-2]-é pedig 75.25. Ezen értékek egyike sem nagyobb 83-nál, így mindkét csomópont le van vágva.
  • Végül az N3 és N4 csomópontokat is levágjuk.
  • Tehát a fa összes csomópontja elágazó vagy levágott, így a legjobb ideiglenes megoldás az, amelyet keresni kell. Ennek megfelelően ki kell választani 3 csomagot {i = 2}, 1 csomagot {i = 4} és egy csomagot {i = 3}, amelyek összértéke 83, össztömege 36.

A második példa azonos elemzésével megvan az eredmény: válassza ki a 4-es csomagot (3-szor) és az 5-ös csomagot (3-szor).

Python3 kód a Greedy Three-hez

  • Először is meg kell határoznia a KnapsackPackage osztályt.
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
  • Ezután létrehoz egy függvényt a Greedy Three algoritmus végrehajtásához.
class FractionalKnapsack(object):
    def __init__(self):
        
    def knapsackGreProc(self, W, V, M, n):
        packs = []
        for i in range(n): 
            packs.append(KnapsackPackage(W[i], V[i]))
            
        packs.sort(reverse = True)
        
        remain = M
        result = 0
        
        i = 0
        stopProc = False
        
        while (stopProc != True):
            if (packs[i].weight <= remain):
                remain -= packs[i].weight;
                result += packs[i].value;
                
                print("Pack ", i, " - Weight ", packs[i].weight, " - Value ", packs[i].value)
            
            if (packs[i].weight > remain):
                i += 1
                
            if (i == n):
                stopProc = True            
        
        print("Max Value:\t", result)   
A knapsackGreProc() in Python
A knapsackGreProc() in Python

A kód magyarázata:

  1. Minden hátizsák-csomag súlyát és értékét inicializálja.
  2. Válogassa szét a hátizsákcsomagokat költség szerint, csökkenő sorrendben.
  3. Ha az i. csomagot választja.
  4. Ha kiválasztja a csomagok számát, az i elég.
  5. Állítsa le az összes csomag böngészésekor.

Itt van Python3 kód a fenti program futtatásához az első példával:

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)

Megvan a kimenet:

Pack  0  - Weight  10  - Value  25
Pack  0  - Weight  10  - Value  25
Pack  0  - Weight  10  - Value  25
Pack  2  - Weight  4  - Value  6
Pack  3  - Weight  2  - Value  2
Max Value:	 83

C# kód a Greedy Three számára

  • Először is meg kell határoznia a KnapsackPackage osztályt.
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 = (double) value / weight;
        }

        public double Weight
        {
            get { return weight; }
        }

        public double Value
        {
            get { return value; }
        }

        public double Cost
        {
            get { return cost; }
        }
    }
}
  • Ezután létrehoz egy függvényt a Greedy Three algoritmus végrehajtásához.
using System;
namespace KnapsackProblem
{
    public class FractionalKnapsack
    {
        public FractionalKnapsack()
        {
        }

        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, new Comparison<KnapsackPackage>(
                 (kPackA, kPackB) => kPackB.Cost.CompareTo(kPackA.Cost)));

            double remain = M;
            double result = 0d;

            int i = 0;
            bool stopProc = false;

            while (!stopProc)
            {
                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);
                }

                if (packs[i].Weight > remain)
                {
                    i++;
                }

                if (i == n)
                {
                    stopProc = true;
                }
            }

            Console.WriteLine("Max Value:\t" + result);
        }        
    }
}
A KnapsackGreProc() függvény C#-ban
A KnapsackGreProc() függvény C#-ban

A kód magyarázata:

  1. Minden hátizsák-csomag súlyát és értékét inicializálja.
  2. Válogassa szét a hátizsákcsomagokat költség szerint, csökkenő sorrendben.
  3. Ha az i. csomagot választja.
  4. Ha kiválasztja a csomagok számát, az i elég.
  5. Állítsa le az összes csomag böngészésekor.

Itt található a C# kód a fenti program futtatásához az első példával:

public void run()
{
    /*
     * Pack and Weight - Value
     */
    int[] W = new int[]{15, 10, 2, 4};
    //int[] W = new int[] { 12, 2, 1, 1, 4 };

    int[] V = new int[]{30, 25, 2, 6};
    //int[] V = new int[] { 4, 2, 1, 2, 10 };

    /*
     * Max Weight
     */
    int M = 37;
    //int M = 15;
    int n = V.Length;

    /*
     * Run the algorithm
     */
    KnapsackGreProc(W, V, M, n);
}

Megvan a kimenet:

Pack 0 - Weight 10 - Value 25
Pack 0 - Weight 10 - Value 25
Pack 0 - Weight 10 - Value 25
Pack 2 - Weight 4 - Value 6
Pack 3 - Weight 2 - Value 2
Max Value:	83

Ellenpélda a Kapzsi Háromra

A Greedy Three algoritmusa gyorsan megoldódik, és bizonyos esetekben optimális is lehet. Egyes speciális esetekben azonban nem ad optimális megoldást. Itt van egy ellenpélda:

  • A feladat paraméterei: n = 3; M = 10.
  • A csomagok: {i = 1; W[i] = 7; V[i] = 9; Költség = 9/7}; {i = 2; W[i] = 6; V[i] = 6; Költség = 1}; {i = 3; W[i] = 4; V[i] = 4; Költség = 1}.
  • Az algoritmus (1. csomag) 9-es összértékkel választja ki, míg a feladat optimális megoldása (2. csomag, 3. csomag) 10-es összértékkel.

Itt van a java kód a fenti program futtatásához az ellenpéldával:

public void run() {
	/*
	 * Pack and Weight - Value
	 */
	int W[] = new int[]{7, 6, 4};
	
	int V[] = new int[]{9, 6, 4};
	
	/*
	 * Max Weight
	 */
	int M = 10;
	int n = V.length;
	
	/*
	 * Run the algorithm
	 */
	knapsackGreProc(W, V, M, n);
}

Itt van az eredmény:

Pack 0 - Weight 7.0 - Value 9.0
Max Value:	9.0

Ennyi a frakcionált hátizsák problémája.

Foglald össze ezt a bejegyzést a következőképpen: