Jest ruszt o rozmiarach N x m . Niektóre komórki to wyspy oznaczone „0”, a inne to woda . Każda komórka wodna ma na sobie liczbę oznaczającą koszt mostu wykonanego w tej komórce. Musisz znaleźć minimalny koszt, za jaki wszystkie wyspy mogą być połączone. Komórka jest połączona z inną komórką, jeśli ma wspólną krawędź lub wierzchołek.
Jakiego algorytmu można użyć do rozwiązania tego problemu? Czego można użyć jako metody brutalnej siły, jeśli wartości N, M są bardzo małe, powiedzmy NxM <= 100?
Przykład : Na podanym obrazku zielone komórki oznaczają wyspy, niebieskie pola - wodę, a jasnoniebieskie komórki - komórki, na których ma być wykonany most. Zatem dla poniższego obrazu odpowiedź będzie wynosić 17 .
Początkowo myślałem o oznaczeniu wszystkich wysp jako węzłów i połączeniu każdej pary wysp najkrótszym mostem. Wtedy problem mógłby zostać zredukowany do Minimalnego drzewa rozpinającego, ale w tym podejściu przegapiłem przypadek, w którym krawędzie zachodzą na siebie. Na przykład na poniższym obrazku najkrótsza odległość między dowolnymi dwiema wyspami wynosi 7 (zaznaczona na żółto), więc przy użyciu minimalnych drzew rozpinających odpowiedź będzie wynosić 14 , ale odpowiedź powinna wynosić 11 (zaznaczona na jasnoniebiesko).
źródło
Odpowiedzi:
Aby podejść do tego problemu, użyłbym struktury programowania liczb całkowitych i zdefiniowałbym trzy zestawy zmiennych decyzyjnych:
W przypadku kosztów budowy mostów c_ij , docelową wartością do zminimalizowania jest
sum_ij c_ij * x_ij
. Do modelu musimy dodać następujące ograniczenia:y_ijbcn <= x_ij
dla każdego miejsca wodnego (i, j). Ponadtoy_ijbc1
musi wynosić 0, jeśli (i, j) nie graniczy z wyspą b. Wreszcie, dla n> 1,y_ijbcn
można użyć tylko wtedy, gdy w kroku n-1 użyto sąsiedniej lokalizacji wodnej. DefiniowanieN(i, j)
jako kwadraty wody sąsiednie (i, j), jest równoważne zy_ijbcn <= sum_{(l, m) in N(i, j)} y_lmbc(n-1)
.I(c)
lokalizacje graniczące z wyspą c, można to osiągnąć za pomocąl_bc <= sum_{(i, j) in I(c), n} y_ijbcn
.sum_{b in S} sum_{c in S'} l_bc >= 1
.W przypadku problemu z wyspami K, kwadratami wody W i określoną maksymalną długością ścieżki N, jest to model programowania z mieszanymi liczbami całkowitymi ze
O(K^2WN)
zmiennymi iO(K^2WN + 2^K)
ograniczeniami. Oczywiście stanie się to nie do rozwiązania, gdy rozmiar problemu stanie się duży, ale może być rozwiązany w przypadku rozmiarów, na których Ci zależy. Aby uzyskać poczucie skalowalności, zaimplementuję to w Pythonie przy użyciu pakietu pulp. Zacznijmy najpierw od mniejszej mapy 7 x 9 z 3 wyspami u dołu pytania:Trwa to 1,4 sekundy do uruchomienia przy użyciu domyślnego solwera z pakietu pulp (solver CBC) i wyświetla poprawne rozwiązanie:
Następnie rozważ pełny problem u góry pytania, czyli siatkę 13 x 14 z 7 wyspami:
Osoby rozwiązujące MIP często uzyskują dobre rozwiązania stosunkowo szybko, a następnie spędzają dużo czasu, próbując udowodnić optymalność rozwiązania. Używając tego samego kodu solwera, co powyżej, program nie kończy się w ciągu 30 minut. Możesz jednak podać czas rozwiązującemu, aby uzyskać przybliżone rozwiązanie:
Daje to rozwiązanie o obiektywnej wartości 17:
Aby poprawić jakość otrzymywanych rozwiązań, możesz skorzystać z komercyjnego solwera MIP (jest on bezpłatny, jeśli jesteś w instytucji akademickiej i prawdopodobnie nie jest bezpłatny). Na przykład, oto wydajność Gurobi 6.0.4, ponownie z 2-minutowym limitem czasu (chociaż z dziennika rozwiązań czytamy, że solver znalazł aktualnie najlepsze rozwiązanie w ciągu 7 sekund):
W rzeczywistości znajduje to rozwiązanie o wartości celu 16, lepsze niż to, które PO był w stanie znaleźć ręcznie!
źródło
Podejście siłowe, w pseudokodzie:
W C ++ można to zapisać jako
// map = linearized map; map[x*n + y] is the equivalent of map2d[y][x] // nm = n*m // bridged = true if bridge there, false if not. Also linearized // nBridged = depth of recursion (= current bridge being considered) // cost = total cost of bridges in 'bridged' // best, bestCost = best answer so far. Initialized to "horrible" void findBestBridges(char map[], int nm, bool bridged[], int nBridged, int cost, bool best[], int &bestCost) { if (nBridged == nm) { if (connected(map, nm, bridged) && cost < bestCost) { memcpy(best, bridged, nBridged); bestCost = best; } return; } if (map[nBridged] != 0) { // try with a bridge there bridged[nBridged] = true; cost += map[nBridged]; // see how it turns out findBestBridges(map, nm, bridged, nBridged+1, cost, best, bestCost); // remove bridge for further recursion bridged[nBridged] = false; cost -= map[nBridged]; } // and try without a bridge there findBestBridges(map, nm, bridged, nBridged+1, cost, best, bestCost); }
Po wykonaniu pierwszego połączenia (zakładam, że przekształcasz swoje mapy 2d w tablice 1d w celu ułatwienia kopiowania),
bestCost
będzie zawierał koszt najlepszej odpowiedzi ibest
będzie zawierał wzór mostów, który ją daje. Jest to jednak niezwykle powolne.Optymalizacje:
bestCost
, ponieważ nie ma sensu szukać później. Jeśli nie może być lepiej, nie kop dalej.Ogólny algorytm wyszukiwania, taki jak A *, umożliwia znacznie szybsze wyszukiwanie, chociaż znalezienie lepszej heurystyki nie jest prostym zadaniem. Aby uzyskać bardziej specyficzne podejście, należy wykorzystać istniejące wyniki na drzewach Steinera , zgodnie z sugestią @Gassa. Należy jednak pamiętać, że zgodnie z artykułem Gareya i Johnsona problem budowania drzew Steinera na siatkach ortogonalnych jest NP-Complete .
Jeśli wystarczy „wystarczająco dobry”, algorytm genetyczny prawdopodobnie może szybko znaleźć akceptowalne rozwiązania, o ile dodasz kilka kluczowych heurystyk dotyczących preferowanego umieszczania mostów.
źródło
2^(13*14) ~ 6.1299822e+54
iteracje. Jeśli założymy, że możesz wykonać milion iteracji na sekundę, zajmie to tylko ... ~ 1943804600000000000000000000000000000000 lat. Te optymalizacje są bardzo potrzebne.Ten problem jest wariantem drzewa Steinera zwanego drzewem Steinera ważonym węzłami , specjalizującym się w określonej klasie grafów. W skrócie, drzewo Steinera ważone węzłem, biorąc pod uwagę niekierunkowy wykres ważony węzłem, w którym niektóre węzły są terminalami, znajduje najtańszy zestaw węzłów, w tym wszystkie terminale, które indukują podłączony podgraf. Niestety, w niektórych pobieżnych poszukiwaniach nie mogę znaleźć żadnego rozwiązania.
Aby sformułować jako program całkowity, utwórz zmienną 0-1 dla każdego węzła nieterminalnego, a następnie dla wszystkich podzbiorów węzłów nieterminalnych, których usunięcie z grafu początkowego rozłącza dwa terminale, wymagaj, aby suma zmiennych w podzbiorze była równa najmniej 1. To wywołuje zbyt wiele ograniczeń, więc będziesz musiał wymuszać je leniwie, używając wydajnego algorytmu łączenia węzłów (w zasadzie maksymalny przepływ), aby wykryć maksymalnie naruszone ograniczenie. Przepraszamy za brak szczegółów, ale będzie to trudne do wdrożenia, nawet jeśli znasz już programowanie liczb całkowitych.
źródło
Biorąc pod uwagę, że problem ten występuje w siatce i masz dobrze zdefiniowane parametry, podszedłbym do problemu z systematyczną eliminacją przestrzeni problemowej poprzez utworzenie minimalnego drzewa rozpinającego. Robiąc to, ma dla mnie sens, jeśli podejdziesz do tego problemu za pomocą algorytmu Prima.
Niestety, teraz napotkasz problem wyodrębnienia siatki w celu utworzenia zestawu węzłów i krawędzi… ergo prawdziwym problemem tego postu jest to, jak przekonwertować moją siatkę nxm na {V} i {E}?
Ten proces konwersji jest na pierwszy rzut oka prawdopodobnie NP-trudny ze względu na ogromną liczbę możliwych kombinacji (załóżmy, że wszystkie koszty dróg wodnych są identyczne). Aby poradzić sobie z przypadkami nakładania się ścieżek, należy rozważyć utworzenie wirtualnej wyspy.
Gdy to zrobisz, uruchom algorytm Prim i powinieneś znaleźć optymalne rozwiązanie.
Nie wierzę, że programowanie dynamiczne można tutaj skutecznie uruchomić, ponieważ nie ma zauważalnej zasady optymalności. Jeśli znajdziemy minimalny koszt między dwiema wyspami, nie musi to oznaczać, że możemy znaleźć minimalny koszt między tymi dwiema a trzecią wyspą lub inny podzbiór wysp, który będzie (zgodnie z moją definicją, aby znaleźć MST przez Prim) połączony.
Jeśli chcesz, aby kod (pseudo lub inny) przekształcił twoją siatkę w zestaw {V} i {E}, wyślij mi prywatną wiadomość, a ja zajmę się łączeniem razem implementacji.
źródło