Tüm adaları birbirine bağlamanın minimum maliyeti nedir?


84

N x M boyutunda bir ızgara vardır . Bazı hücreler '0' ile gösterilen adalardır ve diğerleri sudur . Her su hücresinin üzerinde, o hücreye yapılan köprünün maliyetini gösteren bir numara vardır. Tüm adaların bağlanabileceği minimum maliyeti bulmalısınız. Hücre, bir kenarı veya tepe noktasını paylaşıyorsa başka bir hücreye bağlanır.

Bu sorunu çözmek için hangi algoritma kullanılabilir? N, M değerleri çok küçükse, örneğin NxM <= 100 ise kaba kuvvet yaklaşımı olarak ne kullanılabilir?

Örnek : Verilen görüntüde yeşil hücreler adaları, mavi hücreler suyu ve açık mavi hücreler üzerinde köprü yapılması gereken hücreleri belirtir. Böylece aşağıdaki görüntü için cevap 17 olacaktır .

http://i.imgur.com/ClcboBy.png

Başlangıçta tüm adaları düğümler olarak işaretlemeyi ve her ada çiftini en kısa köprü ile bağlamayı düşündüm. Daha sonra sorun Minimum yayılma ağacına indirilebilir, ancak bu yaklaşımda kenarların üst üste geldiği durumu kaçırdım. Örneğin , aşağıdaki resimde, herhangi iki ada arasındaki en kısa mesafe 7'dir (sarı ile işaretlenmiştir), bu nedenle Minimum Kapsayan Ağaçlar kullanıldığında cevap 14 , ancak cevap 11 olmalıdır (açık mavi ile işaretlenmiş).

image2


Sorularınızda tarif ettiğiniz çözüm yaklaşımı doğru görünüyor. "Kenarların örtüştüğü durumu kaçırdım" derken neyi kastettiğinizi açıklar mısınız?
Asad Saeeduddin

@Asad: MST yaklaşımındaki sorunu açıklamak için bir resim ekledim.
Atul Vaibhav

" her iki adayı en kısa bir köprü ile birbirine bağlayın " - görebileceğiniz gibi, bu açıkça kötü bir yaklaşım.
Karoly Horvath

1
Lütfen şu anda kullandığınız kodu paylaşır mısınız? Bu, bir cevap bulmayı biraz daha kolaylaştıracak ve bize mevcut yaklaşımınızın tam olarak ne olduğunu da gösterecektir.
Esad Saeeduddin

7
Bu, Steiner ağaç probleminin bir çeşididir . Bazı bilgiler için Wikipedia bağlantısını takip edin. Kısacası, kesin çözüm belki polinom zamanda bulunamaz, ancak minimal bir yayılma ağacı o kadar da kötü olmayan bir yaklaşımdır.
Gassa

Yanıtlar:


67

Bu soruna yaklaşmak için, bir tamsayı programlama çerçevesi kullanırdım ve üç set karar değişkeni tanımlardım:

  • x_ij : Su konumunda (i, j) bir köprü kurup kurmayacağımızı gösteren ikili gösterge değişkeni.
  • y_ijbcn : Su konumunun (i, j) b adasını c adasına bağlayan n ^ inci konum olup olmadığına dair ikili bir gösterge.
  • l_bc : b ve c adalarının doğrudan bağlantılı olup olmadığına dair ikili gösterge değişkeni (diğer bir deyişle, yalnızca b'den c'ye köprü karelerinde yürüyebilirsiniz).

Köprü inşa maliyetleri c_ij için , en aza indirilecek amaç değeri sum_ij c_ij * x_ij. Modele aşağıdaki kısıtlamaları eklememiz gerekiyor:

  • Y_ijbcn değişkenlerinin geçerli olduğundan emin olmamız gerekir . Her zaman bir su meydanına ancak oraya bir köprü inşa edersek ulaşabiliriz, yani y_ijbcn <= x_ijher su konumu için (i, j). Ayrıca, y_ijbc1(i, j) ada b'yi sınırlamıyorsa, 0'a eşit olmalıdır. Son olarak, n> 1 için, y_ijbcnyalnızca adım n-1'de bir komşu su konumu kullanılmışsa kullanılabilir. Tanımlama N(i, j)komşu su kareler (i, j) için, bu eşdeğerdir y_ijbcn <= sum_{(l, m) in N(i, j)} y_lmbc(n-1).
  • L_bc değişkenlerinin yalnızca b ve c bağlantılıysa ayarlandığından emin olmalıyız . I(c)Ada c'yi çevreleyen yerler olarak tanımlarsak , bu yapılabilir l_bc <= sum_{(i, j) in I(c), n} y_ijbcn.
  • Tüm adaların doğrudan veya dolaylı olarak bağlantılı olmasını sağlamalıyız. Bu, aşağıdaki şekilde gerçekleştirilebilir: Adaların boş olmayan her uygun alt kümesi için, S'deki en az bir adanın S'nin tamamlayıcısı içinde S 'olarak adlandıracağımız en az bir adaya bağlı olmasını gerektirir. Kısıtlamalarda, bunu boş olmayan her S kümesi için <= K / 2 boyutunda bir sınırlama ekleyerek uygulayabiliriz (burada K, ada sayısıdır) sum_{b in S} sum_{c in S'} l_bc >= 1.

K adaları, W su kareleri ve belirtilen maksimum yol uzunluğu N ile ilgili bir sorun örneği için bu, O(K^2WN)değişkenler ve O(K^2WN + 2^K)kısıtlamalar içeren bir karma tamsayı programlama modelidir . Açıkçası bu, problem boyutu büyüdükçe çözülemez hale gelecektir, ancak ilgilendiğiniz boyutlar için çözülebilir olabilir. Ölçeklenebilirlik hakkında bir fikir edinmek için, onu pulp paketini kullanarak python'da gerçekleştireceğim. Öncelikle sorunun altında 3 ada bulunan daha küçük 7 x 9 haritayla başlayalım:

import itertools
import pulp
water = {(0, 2): 2.0, (0, 3): 1.0, (0, 4): 1.0, (0, 5): 1.0, (0, 6): 2.0,
         (1, 0): 2.0, (1, 1): 9.0, (1, 2): 1.0, (1, 3): 9.0, (1, 4): 9.0,
         (1, 5): 9.0, (1, 6): 1.0, (1, 7): 9.0, (1, 8): 2.0,
         (2, 0): 1.0, (2, 1): 9.0, (2, 2): 9.0, (2, 3): 1.0, (2, 4): 9.0,
         (2, 5): 1.0, (2, 6): 9.0, (2, 7): 9.0, (2, 8): 1.0,
         (3, 0): 9.0, (3, 1): 1.0, (3, 2): 9.0, (3, 3): 9.0, (3, 4): 5.0,
         (3, 5): 9.0, (3, 6): 9.0, (3, 7): 1.0, (3, 8): 9.0,
         (4, 0): 9.0, (4, 1): 9.0, (4, 2): 1.0, (4, 3): 9.0, (4, 4): 1.0,
         (4, 5): 9.0, (4, 6): 1.0, (4, 7): 9.0, (4, 8): 9.0,
         (5, 0): 9.0, (5, 1): 9.0, (5, 2): 9.0, (5, 3): 2.0, (5, 4): 1.0,
         (5, 5): 2.0, (5, 6): 9.0, (5, 7): 9.0, (5, 8): 9.0,
         (6, 0): 9.0, (6, 1): 9.0, (6, 2): 9.0, (6, 6): 9.0, (6, 7): 9.0,
         (6, 8): 9.0}
islands = {0: [(0, 0), (0, 1)], 1: [(0, 7), (0, 8)], 2: [(6, 3), (6, 4), (6, 5)]}
N = 6

# Island borders
iborders = {}
for k in islands:
    iborders[k] = {}
    for i, j in islands[k]:
        for dx in [-1, 0, 1]:
            for dy in [-1, 0, 1]:
                if (i+dx, j+dy) in water:
                    iborders[k][(i+dx, j+dy)] = True

# Create models with specified variables
x = pulp.LpVariable.dicts("x", water.keys(), lowBound=0, upBound=1, cat=pulp.LpInteger)
pairs = [(b, c) for b in islands for c in islands if b < c]
yvals = []
for i, j in water:
    for b, c in pairs:
        for n in range(N):
            yvals.append((i, j, b, c, n))

y = pulp.LpVariable.dicts("y", yvals, lowBound=0, upBound=1)
l = pulp.LpVariable.dicts("l", pairs, lowBound=0, upBound=1)
mod = pulp.LpProblem("Islands", pulp.LpMinimize)

# Objective
mod += sum([water[k] * x[k] for k in water])

# Valid y
for k in yvals:
    i, j, b, c, n = k
    mod += y[k] <= x[(i, j)]
    if n == 0 and not (i, j) in iborders[b]:
        mod += y[k] == 0
    elif n > 0:
        mod += y[k] <= sum([y[(i+dx, j+dy, b, c, n-1)] for dx in [-1, 0, 1] for dy in [-1, 0, 1] if (i+dx, j+dy) in water])

# Valid l
for b, c in pairs:
    mod += l[(b, c)] <= sum([y[(i, j, B, C, n)] for i, j, B, C, n in yvals if (i, j) in iborders[c] and B==b and C==c])

# All islands connected (directly or indirectly)
ikeys = islands.keys()
for size in range(1, len(ikeys)/2+1):
    for S in itertools.combinations(ikeys, size):
        thisSubset = {m: True for m in S}
        Sprime = [m for m in ikeys if not m in thisSubset]
        mod += sum([l[(min(b, c), max(b, c))] for b in S for c in Sprime]) >= 1

# Solve and output
mod.solve()
for row in range(min([m[0] for m in water]), max([m[0] for m in water])+1):
    for col in range(min([m[1] for m in water]), max([m[1] for m in water])+1):
        if (row, col) in water:
            if x[(row, col)].value() > 0.999:
                print "B",
            else:
                print "-",
        else:
            print "I",
    print ""

Bu, kağıt hamuru paketinden (CBC çözücü) varsayılan çözücüyü kullanarak çalıştırmak 1,4 saniye sürer ve doğru çözümü verir:

I I - - - - - I I 
- - B - - - B - - 
- - - B - B - - - 
- - - - B - - - - 
- - - - B - - - - 
- - - - B - - - - 
- - - I I I - - - 

Daha sonra, sorunun üst kısmındaki 7 adalı 13 x 14 ızgara olan sorunun tamamını düşünün:

water = {(i, j): 1.0 for i in range(13) for j in range(14)}
islands = {0: [(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)],
           1: [(9, 0), (9, 1), (10, 0), (10, 1), (10, 2), (11, 0), (11, 1),
               (11, 2), (12, 0)],
           2: [(0, 7), (0, 8), (1, 7), (1, 8), (2, 7)],
           3: [(7, 7), (8, 6), (8, 7), (8, 8), (9, 7)],
           4: [(0, 11), (0, 12), (0, 13), (1, 12)],
           5: [(4, 10), (4, 11), (5, 10), (5, 11)],
           6: [(11, 8), (11, 9), (11, 13), (12, 8), (12, 9), (12, 10), (12, 11),
               (12, 12), (12, 13)]}
for k in islands:
    for i, j in islands[k]:
        del water[(i, j)]

for i, j in [(10, 7), (10, 8), (10, 9), (10, 10), (10, 11), (10, 12),
             (11, 7), (12, 7)]:
    water[(i, j)] = 20.0

N = 7

MIP çözücüler genellikle iyi çözümleri nispeten hızlı bir şekilde elde ederler ve ardından çözümün optimalliğini kanıtlamaya çalışmak için çok fazla zaman harcarlar. Yukarıdaki ile aynı çözücü kodunu kullanarak, program 30 dakika içinde tamamlanmaz. Ancak, yaklaşık bir çözüm elde etmek için çözücüye bir zaman aşımı süresi sağlayabilirsiniz:

mod.solve(pulp.solvers.PULP_CBC_CMD(maxSeconds=120))

Bu, hedef değeri 17 olan bir çözüm sağlar:

I I - - - - - I I - - I I I 
I I - - - - - I I - - - I - 
I I - - - - - I - B - B - - 
- - B - - - B - - - B - - - 
- - - B - B - - - - I I - - 
- - - - B - - - - - I I - - 
- - - - - B - - - - - B - - 
- - - - - B - I - - - - B - 
- - - - B - I I I - - B - - 
I I - B - - - I - - - - B - 
I I I - - - - - - - - - - B 
I I I - - - - - I I - - - I 
I - - - - - - - I I I I I I 

Elde ettiğiniz çözümlerin kalitesini artırmak için ticari bir MIP çözücü kullanabilirsiniz (akademik bir kurumdaysanız bu ücretsizdir ve aksi takdirde büyük olasılıkla özgür değilsiniz). Örneğin, yine 2 dakikalık bir zaman sınırı ile Gurobi 6.0.4'ün performansı burada (çözüm günlüğünden çözücünün mevcut en iyi çözümü 7 saniye içinde bulduğunu okuyoruz):

mod.solve(pulp.solvers.GUROBI(timeLimit=120))

Bu aslında, OP'nin elle bulabileceğinden daha iyi olan 16 objektif değerinde bir çözüm bulur!

I I - - - - - I I - - I I I 
I I - - - - - I I - - - I - 
I I - - - - - I - B - B - - 
- - B - - - - - - - B - - - 
- - - B - - - - - - I I - - 
- - - - B - - - - - I I - - 
- - - - - B - - B B - - - - 
- - - - - B - I - - B - - - 
- - - - B - I I I - - B - - 
I I - B - - - I - - - - B - 
I I I - - - - - - - - - - B 
I I I - - - - - I I - - - I 
I - - - - - - - I I I I I I 

Y_ijbcn formülasyonu yerine, akışa dayalı bir formülasyon denerdim (bir ada çifti ve kare bitişiklikten oluşan her demet için değişken; lavaboda fazlalık 1 ve kaynakta -1 ile koruma kısıtlamaları; bağlı toplam akış içi satın alıp almadığına göre bir karede).
David Eisenstat

1
@DavidEisenstat öneri için teşekkürler - Ben sadece denedim ve maalesef bu sorun örnekleri için epeyce daha yavaş çözdü.
josliber

8
Bu tam olarak ben ödül başladı aradığı şeyi. Tanımlaması bu kadar önemsiz bir problemin MIP çözücülere nasıl bu kadar zor anlar yaşattığı beni şaşırtıyor. Aşağıdakilerin doğru olup olmadığını merak ediyordum: İki adayı birbirine bağlayan bir yol, bir hücreden (i, j) geçmek zorunda olduğu ek kısıtlamaya sahip en kısa yoldur. Örneğin, Gurobi'nin çözümündeki sol üst ve ortadaki adalar, hücreden geçmek için kısıtlanan bir SP ile bağlantılıdır (6, 5). Bunun doğru olup olmadığından emin değilim, ama bir noktada ona bakacağım. Cevap için teşekkürler!
Ioannis

@Ioannis ilginç soru - Varsayımınızın doğru olup olmadığından emin değilim ama bana oldukça makul görünüyor. (İ, j) hücresini, bu adalardan gelen köprülerin diğer adalara daha fazla bağlanmak için gitmesi gereken yer olarak düşünebilirsiniz ve daha sonra bu koordinasyon noktasına ulaşmak için adayı bağlamak için mümkün olan en ucuz köprüleri inşa etmek isteyebilirsiniz çift.
josliber

5

Sözde kodla kaba kuvvet yaklaşımı:

start with a horrible "best" answer
given an nxm map,
    try all 2^(n*m) combinations of bridge/no-bridge for each cell
        if the result is connected, and better than previous best, store it

return best

C ++ 'da bu şu şekilde yazılabilir:

İlk aramayı yaptıktan sonra (2d haritalarınızı kopyalama kolaylığı için 1d dizilere dönüştürdüğünüzü varsayıyorum), bestCosten iyi cevabın maliyetini bestiçerecek ve bunu sağlayan köprülerin modelini içerecektir. Ancak bu son derece yavaştır.

Optimizasyonlar:

  • Bir "köprü sınırı" kullanarak ve maksimum köprü sayısını artırmak için algoritmayı çalıştırarak, tüm ağacı keşfetmeden minimum yanıtlar bulabilirsiniz. 1-köprü cevabını bulmak, eğer varsa, O (2 ^ nm) yerine O (nm) olacaktır - büyük bir gelişme.
  • Aramayı (özyinelemeyi durdurarak; buna "budama" da denir) aştıktan sonra kaçınabilirsiniz bestCost, çünkü daha sonra bakmaya devam etmenin bir anlamı yoktur. Daha iyi olamazsa, kazmaya devam etmeyin.
  • "Kötü" adaylara bakmadan önce "iyi" adaylara bakarsanız, yukarıdaki budama daha iyi sonuç verir (olduğu gibi, hücrelerin tümü soldan sağa, yukarıdan aşağıya sırayla bakılır). Bağlantısız birkaç bileşene yakın olan hücreleri, olmayan hücrelere göre daha yüksek öncelikli olarak düşünmek iyi bir buluşsal yöntem olacaktır. Ancak, buluşsal yöntemler eklediğinizde, aramanız A * ' ya benzemeye başlar (ve sizin de bir tür öncelik kuyruğuna ihtiyacınız vardır).
  • Hiçbir yerde çift köprü ve köprülerden kaçınılmalıdır. Kaldırıldığında ada ağının bağlantısını kesmeyen herhangi bir köprü gereksizdir.

A * gibi genel bir arama algoritması çok daha hızlı aramaya izin verir, ancak daha iyi buluşsal yöntemler bulmak basit bir iş değildir. Daha probleme özgü bir yaklaşım için, @Gassa'nın önerdiği gibi Steiner ağaçlarında mevcut sonuçları kullanmak , gidilecek yoldur. Bununla birlikte, Garey ve Johnson tarafından yazılan bu makaleye göre, Steiner ağaçlarını dikey ızgaralar üzerine inşa etme sorununun NP-Complete olduğunu unutmayın .

"Yeterince iyi" yeterliyse, tercih edilen köprü yerleşimine ilişkin birkaç anahtar buluşsal yöntem eklediğiniz sürece, genetik bir algoritma muhtemelen kabul edilebilir çözümleri hızlı bir şekilde bulabilir.


"2 ^ (n * m) kombinasyonunun tümünü dene" uh, 2^(13*14) ~ 6.1299822e+54yinelemeler. Saniyede bir milyon yineleme yapabileceğinizi varsayarsak, bu sadece ... ~ 194380460000000000000000000000000000000000` yıl sürer. Bu optimizasyonlar çok gerekli.
Mooing Duck

OP , "N, M değerleri çok küçükse, NxM <= 100 diyelim, kaba kuvvet yaklaşımı" istedi. Diyelim ki, 20 köprü yeterli ve kullandığınız tek optimizasyon yukarıdaki köprü sınırlayıcıdır, optimum çözüm varsayımsal bilgisayarınızın kapsama alanı içinde olan O (2 ^ 20) 'de bulunacaktır.
tucuxi

Geri izleme algoritmalarının çoğu, siz budama, yinelemeli derinleştirme vb. Ekleyene kadar son derece verimsizdir. Bu onların işe yaramaz olduğu anlamına gelmez. Örneğin, satranç motorları bu algoritmalarla rutin olarak büyükustaları yener (kabul edilir - agresif budamak için kitaptaki her numarayı kullanırlar)
tucuxi

3

Bu problem, belirli bir grafik sınıfında uzmanlaşmış, düğüm ağırlıklı Steiner ağacı adı verilen bir Steiner ağacının bir çeşididir . Kısaca, düğüm ağırlıklı Steiner ağacına, bazı düğümlerin terminal olduğu düğüm ağırlıklı yönlendirilmemiş bir grafik verilir, bağlı bir alt grafiği indükleyen tüm terminaller dahil en ucuz düğüm kümesini bulur. Maalesef, bazı üstünkörü aramalarda herhangi bir çözücü bulamıyorum.

Bir tamsayı programı olarak formüle etmek için, her terminal olmayan düğüm için bir 0-1 değişken yapın, ardından başlangıç ​​grafiğinden çıkarılması iki terminalin bağlantısını kesen tüm terminal olmayan düğümlerin alt kümeleri için, alt kümedeki değişkenlerin toplamının şu değerde olmasını gerektirir: En az 1. Bu, çok fazla kısıtlamaya neden olur, bu nedenle, maksimum düzeyde ihlal edilen bir kısıtlamayı tespit etmek için düğüm bağlantısı için verimli bir algoritma (temelde maksimum akış) kullanarak bunları tembel olarak zorlamanız gerekir. Ayrıntıların eksikliğinden dolayı özür dilerim, ancak tamsayı programlamaya zaten aşina olsanız bile bunu uygulamak zor olacak.


-1

Bu problemin bir ızgarada meydana geldiği ve iyi tanımlanmış parametrelere sahip olduğunuz göz önüne alındığında, problem alanını minimum kapsama ağacı oluşturarak sistematik olarak ortadan kaldırarak probleme yaklaşırdım. Bunu yaparken, bu soruna Prim Algoritması ile yaklaşmanız bana mantıklı geliyor.

Ne yazık ki, şimdi bir dizi düğüm ve kenar oluşturmak için ızgarayı soyutlama sorunuyla karşılaşıyorsunuz ... ergo bu yazının asıl sorunu nxm ızgaramı {V} ve {E} ' ye nasıl dönüştürebilirim?

Bu dönüştürme süreci, bir bakışta, olası kombinasyon sayısının çokluğu nedeniyle muhtemelen NP-Zor'dur (tüm suyolu maliyetlerinin aynı olduğunu varsayın). Yolların çakıştığı durumları işlemek için sanal bir ada oluşturmayı düşünmelisiniz .

Bu yapıldığında, Prim Algoritmasını çalıştırın ve en uygun çözüme ulaşmalısınız.

Dinamik Programlamanın burada etkili bir şekilde çalıştırılabileceğine inanmıyorum çünkü gözlemlenebilir bir optimallik ilkesi yok. İki ada arasında minimum maliyeti bulursak, bu, bu iki ve üçüncü ada arasındaki minimum maliyeti veya adaların başka bir alt kümesini bulabileceğimiz anlamına gelmez (benim tanımıma göre MST'yi Prim yoluyla bulmak) bağlı.

Şebekenizi bir {V} ve {E} kümesine dönüştürmek için kod (sözde veya başka türlü) istiyorsanız, lütfen bana özel bir mesaj gönderin, ben de bir uygulamayı birbirine eklemeye bakacağım.


Tüm su maliyetleri aynı değildir (örneklere bakın). Prim'in bu "sanal düğümleri" oluşturma fikri olmadığı için, aşağıdakileri yapan bir algoritma düşünmelisiniz: Steiner ağaçları (sanal düğümlerinize "Steiner noktaları" denir).
tucuxi

@tucuxi: Tüm su yolu maliyetlerinin aynı olabileceğinden bahsetmek , en kötü durumun analizi için gerekli çünkü bu, arama alanını maksimum potansiyeline şişiren koşul. Bu yüzden bu konuyu açtım. Prim ile ilgili olarak, bu problem için Prim'i uygulamaktan sorumlu programcının, Prim'in sanal düğümler yaratmadığını fark ettiğini ve bunu uygulama seviyesinde ele aldığını varsayıyorum. Henüz Steiner ağaçlarını görmedim (hala üniversite öğrencisi), bu yüzden öğrenecek yeni materyaller için teşekkürler!
karnesJ.R
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.