Das Knapsack-Problem

Das Knapsack-Problem ist ein bekanntes Optimierungsproblem, bei dem es darum geht, eine Menge von Gegenständen so in einen Rucksack zu packen, dass der Gesamtwert maximiert wird, ohne das Gewichtslimit des Rucksacks zu überschreiten.

unfertig!

Problemstellung

  • Gegeben: Eine Menge von Gegenständen, jeder mit einem Gewicht und einem Wert.
  • Ziel: Wähle die Gegenstände so aus, dass der Gesamtwert maximiert wird, während das Gesamtgewicht das Limit nicht überschreitet.

Lösungsansätze

  1. Dynamische Programmierung
  2. Greedy-Algorithmus
  3. Brute-Force-Methode
  4. Backtracking
  5. Genetische Algorithmen und andere Heuristiken

Dynamische Programmierung

  • Teilt das Problem in kleinere Unterprobleme auf.
  • Speichert Ergebnisse von Unterproblemen, um doppelte Berechnungen zu vermeiden.
  • Effizient für das 0/1 Knapsack-Problem.

Greedy-Algorithmus

  • Priorisiert Gegenstände basierend auf einem Kriterium (z.B. Wert/Gewicht-Verhältnis).
  • Einfach und schnell, aber nicht immer optimal.
def greedy_knapsack(values, weights, capacity):
    # Berechne das Wert-Gewicht-Verhältnis für jeden Gegenstand
    ratio = [(v / w, w) for v, w in zip(values, weights)]
    # Sortiere die Gegenstände basierend auf dem Verhältnis absteigend
    ratio.sort(reverse=True)
    
    total_value = 0
    for r, w in ratio:
        if capacity >= w:
            total_value += r * w
            capacity -= w
        else:
            total_value += r * capacity
            break
    return total_value

values = [60, 100, 120]
weights = [10, 20, 30]
capacity = 50
print(greedy_knapsack(values, weights, capacity))

Brute-Force-Methode

  • Probiert alle möglichen Kombinationen von Gegenständen aus.
  • Garantiert eine optimale Lösung, ist aber ineffizient für große Problemstellungen.
from itertools import combinations

def brute_force_knapsack(values, weights, capacity):
    n = len(values)
    best_value = 0
    for r in range(n + 1):
        for combo in combinations(range(n), r):
            current_weight = sum(weights[i] for i in combo)
            current_value = sum(values[i] for i in combo)
            if current_weight <= capacity and current_value > best_value:
                best_value = current_value
    return best_value

values = [60, 100, 120]
weights = [10, 20, 30]
capacity = 50
print(brute_force_knapsack(values, weights, capacity))

Backtracking

  • Ähnlich wie Brute-Force, aber verwirft unversprechende Pfade frühzeitig.
  • Kann effizienter als reines Brute-Force sein, bleibt jedoch zeitaufwendig.
def knapsack_backtracking(values, weights, capacity, n, 
                          current_value=0, best_value=0):
    if n == 0 or capacity == 0:
        return max(current_value, best_value)
    if weights[n-1] > capacity:
        return knapsack_backtracking(values, weights, 
                                     capacity, n-1, 
                                     current_value, best_value)
    else:
        tmp1 = knapsack_backtracking(values, weights, 
                                     capacity-weights[n-1], n-1, 
                                     current_value+values[n-1], best_value)
        tmp2 = knapsack_backtracking(values, weights, 
                                     capacity, n-1, 
                                     current_value, best_value)
        best_value = max(tmp1, tmp2)
    return best_value

values = [60, 100, 120]
weights = [10, 20, 30]
capacity = 50
n = len(values)
print(knapsack_backtracking(values, weights, capacity, n))

Genetische Algorithmen

  • Inspiriert von natürlichen Prozessen wie Evolution und natürlicher Selektion.
  • Iterative Verbesserung einer Menge von Lösungen.
  • Gut für komplexe Probleme, wo eine näherungsweise Lösung ausreichend ist.
import random

def fitness(individual, values, weights, capacity):
    weight = sum(w * ind for w, ind in zip(weights, individual))
    value = sum(v * ind for v, ind in zip(values, individual))
    if weight > capacity:
        return 0
    else:
        return value

def evolve(population, values, weights, capacity, mutation_rate=0.01):
    # Selektion
    ranked = sorted([(fitness(individual, values, 
                              weights, capacity), individual) 
                     for individual in population], reverse=True)
    population = [individual for _, individual in ranked[:len(ranked)//2]]
    
    # Kreuzung und Mutation
    offspring = []
    for _ in range(len(population)):
        parent1 = random.choice(population)
        parent2 = random.choice(population)
        child = [parent1[i] if random.random() < 0.5 else parent2[i] 
                 for i in range(len(parent1))]
        child = [gene if random.random() > mutation_rate else 1-gene for gene in child]
        offspring.append(child)
    return offspring + population

def genetic_knapsack(values, weights, capacity, generations=100, population_size=100):
    # Initialisiere die Population zufällig
    population = [[random.randint(0, 1) for _ in values] for _ in range(population_size)]
    for _ in range(generations):
        population = evolve(population, values, weights, capacity)
    best_individual = sorted([(fitness(individual, values, 
                                       weights, capacity), individual) 
                              for individual in population], reverse=True)[0][1]
    return sum(v * ind for v, ind in zip(values, best_individual))

values = [60, 100, 120]
weights = [10, 20, 30]
capacity = 50
print(genetic_knapsack(values, weights, capacity))

Zusammenfassung

Das Knapsack-Problem bietet einen Einblick in die Herausforderungen und Ansätze der algorithmischen Optimierung. Die Wahl des richtigen Ansatzes hängt von der spezifischen Problemstellung und den verfügbaren Ressourcen ab.

Das Knapsack-Problem

By Harald Haberstroh

Das Knapsack-Problem

  • 99