Existe uma grelha de dimensão N x M . Algumas células são ilhas indicadas por '0' e as outras são água . Cada célula de água tem um número que denota o custo de uma ponte feita nessa célula. Você tem que encontrar o custo mínimo pelo qual todas as ilhas podem ser conectadas. Uma célula é conectada a outra célula se compartilhar uma aresta ou um vértice.
Qual algoritmo pode ser usado para resolver este problema? O que pode ser usado como uma abordagem de força bruta se os valores de N, M são muito pequenos, digamos NxM <= 100?
Exemplo : Na imagem fornecida, as células verdes indicam ilhas, as células azuis indicam água e as células azuis claras indicam as células nas quais uma ponte deve ser feita. Assim, para a imagem a seguir, a resposta será 17 .
Inicialmente pensei em marcar todas as ilhas como nós e conectar cada par de ilhas por uma ponte mais curta. Então, o problema poderia ser reduzido à árvore de abrangência mínima, mas nesta abordagem eu perdi o caso em que as arestas estão sobrepostas. Por exemplo , na imagem a seguir, a distância mais curta entre duas ilhas é 7 (marcada em amarelo), portanto, usando as Árvores de Extensão Mínima, a resposta seria 14 , mas a resposta deveria ser 11 (marcada em azul claro).
fonte
Respostas:
Para abordar este problema, eu usaria uma estrutura de programação inteira e definiria três conjuntos de variáveis de decisão:
Para os custos de construção da ponte c_ij , o valor objetivo a minimizar é
sum_ij c_ij * x_ij
. Precisamos adicionar as seguintes restrições ao modelo:y_ijbcn <= x_ij
para cada local de água (i, j). Além disso,y_ijbc1
deve ser igual a 0 se (i, j) não faz fronteira com a ilha b. Finalmente, para n> 1,y_ijbcn
só pode ser usado se um local de água vizinho foi usado na etapa n-1. DefinindoN(i, j)
ser os quadrados de água vizinhos (i, j), isso é equivalente ay_ijbcn <= sum_{(l, m) in N(i, j)} y_lmbc(n-1)
.I(c)
como os locais que fazem fronteira com a ilha c, isso pode ser feito coml_bc <= sum_{(i, j) in I(c), n} y_ijbcn
.sum_{b in S} sum_{c in S'} l_bc >= 1
,.Para uma instância de problema com K ilhas, W quadrados de água e comprimento de caminho máximo especificado N, este é um modelo de programação inteira mista com
O(K^2WN)
variáveis eO(K^2WN + 2^K)
restrições. Obviamente, isso se tornará intratável à medida que o tamanho do problema se tornar grande, mas pode ser resolvido para os tamanhos de seu interesse. Para ter uma noção da escalabilidade, vou implementá-la em python usando o pacote pulp. Vamos primeiro começar com o mapa menor de 7 x 9 com 3 ilhas na parte inferior da questão:Isso leva 1,4 segundos para ser executado usando o solucionador padrão do pacote de celulose (o solucionador CBC) e produz a solução correta:
Em seguida, considere o problema completo no topo da questão, que é uma grade 13 x 14 com 7 ilhas:
Os solucionadores de MIP geralmente obtêm boas soluções com relativa rapidez e, então, gastam muito tempo tentando provar a otimização da solução. Usando o mesmo código de solucionador acima, o programa não é concluído em 30 minutos. No entanto, você pode fornecer um tempo limite para o solucionador para obter uma solução aproximada:
Isso resulta em uma solução com valor objetivo 17:
Para melhorar a qualidade das soluções que você obtém, você pode usar um solucionador MIP comercial (gratuito se você estiver em uma instituição acadêmica e provavelmente não será gratuito caso você esteja). Por exemplo, aqui está o desempenho do Gurobi 6.0.4, novamente com um limite de tempo de 2 minutos (embora no log da solução tenhamos lido que o solucionador encontrou a melhor solução atual em 7 segundos):
Isso realmente encontra uma solução de valor objetivo 16, melhor do que o OP foi capaz de encontrar manualmente!
fonte
Uma abordagem de força bruta, em pseudocódigo:
Em C ++, isso pode ser escrito como
// 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); }
Depois de fazer uma primeira chamada (presumo que você está transformando seus mapas 2d em arrays 1d para facilitar a cópia),
bestCost
conterá o custo da melhor resposta ebest
conterá o padrão de pontes que o produz. No entanto, isso é extremamente lento.Otimizações:
bestCost
, porque não faz sentido continuar procurando depois. Se não puder melhorar, não continue cavando.Um algoritmo de pesquisa geral como A * permite uma pesquisa muito mais rápida, embora encontrar melhores heurísticas não seja uma tarefa simples. Para uma abordagem mais específica do problema, usar os resultados existentes nas árvores Steiner , conforme sugerido por @Gassa, é o caminho a percorrer. Observe, entretanto, que o problema de construir árvores de Steiner em grades ortogonais é NP-Completo, de acordo com este artigo de Garey e Johnson .
Se "bom o suficiente" é o suficiente, um algoritmo genético provavelmente pode encontrar soluções aceitáveis rapidamente, desde que você adicione algumas heurísticas importantes quanto ao posicionamento preferido da ponte.
fonte
2^(13*14) ~ 6.1299822e+54
iterações. Se assumirmos que você pode fazer um milhão de iterações por segundo, isso levaria apenas ... ~ 194380460000000000000000000000000000000000` anos. Essas otimizações são muito necessárias.Este problema é uma variante da árvore de Steiner chamada árvore de Steiner ponderada por nós , especializada em uma determinada classe de grafos. De forma compacta, a árvore de Steiner com peso de nó é, dado um grafo não direcionado com peso de nó onde alguns nós são terminais, encontrar o conjunto mais barato de nós, incluindo todos os terminais que induzem um subgrafo conectado. Infelizmente, não consigo encontrar nenhum solucionador em algumas pesquisas superficiais.
Para formular como um programa inteiro, faça uma variável 0-1 para cada nó não terminal, então para todos os subconjuntos de nós não terminais cuja remoção do gráfico inicial desconecta dois terminais, requer que a soma das variáveis no subconjunto esteja em pelo menos 1. Isso induz muitas restrições, então você terá que aplicá-las preguiçosamente, usando um algoritmo eficiente para conectividade de nó (fluxo máximo, basicamente) para detectar uma restrição violada ao máximo. Desculpem a falta de detalhes, mas vai ser difícil implementar, mesmo se você já estiver familiarizado com a programação de inteiros.
fonte
Dado que esse problema ocorre em uma grade e você tem parâmetros bem definidos, eu abordaria o problema com a eliminação sistemática do espaço do problema por meio da criação de uma árvore geradora mínima. Fazendo isso, faz sentido para mim abordar esse problema com o algoritmo de Prim.
Infelizmente, agora você se depara com o problema de abstrair a grade para criar um conjunto de nós e arestas ... logo, o verdadeiro problema deste post é como converter minha grade nxm em {V} e {E}?
Este processo de conversão é, à primeira vista, provavelmente NP-Difícil devido ao grande número de combinações possíveis (suponha que todos os custos hidroviários sejam idênticos). Para lidar com instâncias onde os caminhos se sobrepõem, você deve considerar fazer uma ilha virtual.
Quando isso for feito, execute o Algoritmo de Prim e você deve chegar à solução ótima.
Não acredito que a Programação Dinâmica possa ser executada de forma eficaz aqui porque não existe um princípio observável de otimização. Se encontrarmos o custo mínimo entre duas ilhas, isso não significa necessariamente que podemos encontrar o custo mínimo entre essas duas e a terceira ilha, ou outro subconjunto de ilhas que será (por minha definição encontrar o MST via Prim) conectado.
Se você deseja que o código (pseudo ou outro) converta sua grade em um conjunto de {V} e {E}, envie-me uma mensagem privada e irei ver como unir uma implementação.
fonte