Задача о ранце. Генетический алгоритм - C#
Формулировка задачи:
Доброго времени суток, коллеги.
На днях выдалось относительно свободное время и я решил размять мозги. Выбор пал на задачу о ранце. Начать решил с поиска готовых решений. В нете нашёл простую реализацию решения методом перебора. Небольшой список предметов она решала относительно быстро, при увеличении время работы нелинейно возрастало: на 5 предметов решение находилось за 10 мс, 10 предметов - 4,5 секунды, 15 - задача крутилась 8 часов, после чего мне это надоело и я её вырубил.
Вооружившись википедией и тематическими статьями решил реализовать решение генетическим алгоритмом. Вроде сделал, вроде работает, но что-то мне оно не особо нравится:
1) Пришлось хардкодить создание начальной популяции отбрасывая особи, у которых суммарный вес предметов выше максимально допустимого. Знаю, что это допускается условием задачи, но, ИМХО, по идее алгоритм должен их принимать, как и других, просто отсеивая. А по факту в итоге зачастую выживали именно с этим генотипом.
2) Гвоздями прибито условие, по которому популяция должна делиться надвое без остатка.
3) Иногда выживают особи с не самым оптимальным набором предметов, или даже имеющие кучу свободного места.
Если подскажете, как можно довести до ума алгоритм буду благодарен.
Модель данных:
Алгоритм:
Исходники прилагаются (простое WPF приложение с одной кнопкой)
BP_Problem.zip
public class Item { public String Name { get; private set; } public int Weight { get; private set; } public double Price { get; private set; } public Item (String Name, int Weight, double Price) { this.Name = Name; this.Weight = Weight; this.Price = Price; } } public class BackPack { List<Item> Items = null; public int Weight { get; private set; } = 0; public double Price { get; private set; } public double Fitness { get; private set; } public double Probably { get; set; } public BackPack(int maxWeight, Item[] allItems, Random rnd) { int i; int iterations = 0; Items = new List<Item>(); while (Weight < maxWeight) { iterations++; if (iterations > 100) break; i = rnd.Next(0, allItems.Length); if (Items.Contains(allItems[i])) continue; if (Weight + allItems[i].Weight > maxWeight) continue; Items.Add(allItems[i]); Weight += Items.Last().Weight; } Price = Items.Sum(x => x.Price); Fitness = Math.Round(Price / Weight,3); } public Item[] getItems() { return Items.ToArray(); } }
public partial class MainWindow : Window { Random rnd = new Random(); List<Item> BaseItems = new List<Item>(); public List<BackPack> population; const int generationsCount = 1500; const int populationCount = 60; const int maxWeight = 15; public MainWindow() { InitializeComponent(); } private void Button_Click(object sender, RoutedEventArgs e) { InitItems(); CreatePopulation(); for (int i = 0; i < generationsCount; i++) { Selection(); Mutation(); } } /// <summary> /// Создаём набор предметов /// </summary> void InitItems() { BaseItems.Add(new Item("Книга", 1, 600)); BaseItems.Add(new Item("Бинокль", 2, 5000)); BaseItems.Add(new Item("Аптечка", 4, 1500)); BaseItems.Add(new Item("Ноутбук", 2, 40000)); BaseItems.Add(new Item("Котелок", 1, 500)); BaseItems.Add(new Item("Книга1", 3, 500)); BaseItems.Add(new Item("Бинокль1", 2, 7000)); BaseItems.Add(new Item("Аптечка1", 5, 500)); BaseItems.Add(new Item("Ноутбук1", 6, 60000)); BaseItems.Add(new Item("Котелок1", 2, 300)); BaseItems.Add(new Item("Книга2", 6, 650)); BaseItems.Add(new Item("Бинокль2", 3, 15000)); BaseItems.Add(new Item("Аптечка2", 1, 15000)); BaseItems.Add(new Item("Ноутбук2", 9, 440000)); BaseItems.Add(new Item("Котелок2", 2, 600)); BaseItems.Add(new Item("Книга3", 6, 200)); BaseItems.Add(new Item("Бинокль3", 4, 500)); BaseItems.Add(new Item("Аптечка3", 4, 150)); BaseItems.Add(new Item("Ноутбук3", 7, 45000)); BaseItems.Add(new Item("Котелок3", 1, 500)); BaseItems.Add(new Item("Книга4", 1, 400)); BaseItems.Add(new Item("Бинокль4", 2, 5500)); BaseItems.Add(new Item("Аптечка4", 2, 2000)); BaseItems.Add(new Item("Ноутбук4", 2, 32000)); BaseItems.Add(new Item("Котелок4", 2, 520)); BaseItems.Add(new Item("Книга5", 7, 640)); BaseItems.Add(new Item("Бинокль5", 6, 5400)); BaseItems.Add(new Item("Аптечка5", 3, 1600)); BaseItems.Add(new Item("Ноутбук5", 2, 21000)); BaseItems.Add(new Item("Котелок5", 2, 500)); } /// <summary> /// Создаём начальную популяцию /// </summary> void CreatePopulation() { population = new List<BackPack>(); for (int i = 0; i < populationCount; i++) population.Add(new BackPack(maxWeight, BaseItems.ToArray(), rnd)); } /// <summary> /// Выполняем селекцию /// </summary> void Selection() { double SumFitness = population.Sum(x => x.Fitness); foreach (BackPack bp in population) // для каждого набора расчитываем вероятность быть избранным bp.Probably = 100 * bp.Fitness / SumFitness; population = population.OrderByDescending(x => x.Probably).ToList(); List<BackPack> parents = new List<BackPack>(); while (parents.Count != populationCount / 2) // перебирая сортированный список наборов отбираем половину на роль родителей { for (int i = 0; i < population.Count; i++) { if (population[i].Weight > maxWeight) // отбрасываем нежиснеспособных особей continue; if ((rnd.Next(0, 100 + (int)population[i].Probably * 10)) < 50) continue; else { parents.Add(population[i]); population.RemoveAt(i); break; } } } population = new List<BackPack>(); population.AddRange(parents); BackPack dad, mum, son1, son2; List<Item> items; while(parents.Count > 0) // перебирая родителей отбираем случайным образом пары и создаём 2-х потомков { dad = parents[rnd.Next(parents.Count)]; parents.Remove(dad); mum = parents[rnd.Next(parents.Count)]; parents.Remove(mum); items = new List<Item>(); items.AddRange(dad.getItems()); items.AddRange(mum.getItems()); son1 = new BackPack(maxWeight, items.ToArray(), rnd); son1.Probably = 100 * son1.Fitness / SumFitness; son2 = new BackPack(maxWeight, items.ToArray(), rnd); son2.Probably = 100 * son2.Fitness / SumFitness; population.Add(son1); population.Add(son2); } } /// <summary> /// Случайная мутация /// </summary> void Mutation() { BackPack rBp = population[rnd.Next(0, population.Count)]; int index = rnd.Next(0, rBp.getItems().Length); Item newItem = BaseItems[rnd.Next(0, BaseItems.Count)]; rBp.getItems()[index] = newItem; } }
Решение задачи: «Задача о ранце. Генетический алгоритм»
textual
Листинг программы
public struct Backpack { public Backpack(ImmutableArray<Item> items) { Items = items; } public ImmutableArray<Item> Items { get; } public int TotalWorth => Items.Sum(x => x.Worth); }
ИИ поможет Вам:
- решить любую задачу по программированию
- объяснить код
- расставить комментарии в коде
- и т.д