Bir karo setinin sınırı için Verimli Algoritma


12

Bir harita oluşturan sonlu büyüklükte fayans ızgarası var. Haritanın içindeki karoların bazıları bölge olarak bilinen bir kümeye konur. Bu bölge birbirine bağlı, ancak şekli hakkında hiçbir şey bilinmiyor. Çoğu zaman oldukça düzenli bir damla olurdu, ancak bir yönde çok uzatılabilir ve potansiyel olarak delikleri bile olabilir. Bölgenin (dış) sınırını bulmakla ilgileniyorum.

Yani, bölgedeki fayanslardan birine dokunmadan tüm fayansların bir listesini istiyorum. Bunu bulmanın etkili bir yolu nedir?

Ekstra zorluk için, karolarımın altıgen olduğu, ancak bunun çok fazla fark yaratmadığından, her karonun hala bir x ve y koordinatıyla etiketlendiğinden ve bir karo verildiğinde, komşularını kolayca bulabileceğimden şüpheleniyorum. Aşağıda birkaç örnek verilmiştir: Siyah bölge, mavi ise bulmak istediğim sınır. Bölge ve sınır örneği Bu kendi başına zor bir problem değildir, Sözde python'da bunun için basit bir algoritma:

def find_border_of_territory(territory):
    border = []
    for tile in territory:
        for neighbor in tile.neighbors():
            if neighbor not in territory and neighbor not in border:
                border.add(neighbor)

Ancak bu yavaş ve daha iyi bir şey istiyorum. Bölge üzerinde bir O (n) döngü var, tüm komşular üzerinde başka bir döngü (kısa bir, ama yine de), ve sonra bir tanesi n boyutunda olan iki liste üzerinde üyelik kontrol etmek zorunda. Bu korkunç bir O (n ^ 2) ölçeklendirmesi verir. Üyelik kontrolü hızlı olmak için sınır ve bölge listeleri yerine kümeleri kullanarak bunu O (n) değerine düşürebilirim, ancak yine de harika değil. Bölgenin büyük, ancak sınırın basit bir alan vs çizgi ölçeklendirmesi nedeniyle küçük olduğu birçok durum olmasını bekliyorum. Örneğin, bölge 5 yarıçapı altılıksa, 91 büyüklüğündedir, ancak sınır yalnızca 36 büyüklüğündedir.

Herkes daha iyi bir şey önerebilir mi?

Düzenle:

Aşağıdaki soruları cevaplamak için. Bölgenin büyüklüğü yaklaşık 20 ila 100 arasında değişebilir. Bölgeyi oluşturan karolar kümesi bir nesnenin özniteliğidir ve tüm sınır döşemelerinden oluşan bir kümeye ihtiyaç duyan bu nesnedir.

Başlangıçta bölge bir blok olarak oluşturulur ve daha sonra çoğunlukla teker teker karo kazanır. Bu durumda, en hızlı yolun sadece bir sınır kümesini korumak ve sadece kazanılan karoda güncellemek olduğu doğrudur. Bazen bölgede büyük bir değişiklik olabilir - bu yüzden o zaman tam olarak yeniden hesaplanması gerekecektir.

Şimdi basit bir sınır bulma algoritması yapmanın en iyi çözüm olduğunu düşünüyorum. Bunun ortaya çıkardığı tek ek karmaşıklık, sınırın olması gereken her seferinde yeniden hesaplanmasını sağlamaktır, ancak bundan daha fazlasını değil. Bunun mevcut çerçevemde güvenilir bir şekilde yapılabileceğinden oldukça eminim.

Zamanlama gelince, mevcut kodumda bölgenin her bir karo kontrol etmek gerekir bazı rutinleri var. Her dönüşte değil, yaratılışta ve bazen de sonrasında. Bu, tam programın çok küçük bir parçası olmasına rağmen test kodumun çalışma süresinin% 50'sinden fazlasını alıyor. Bu nedenle tekrarları en aza indirmeye istekliydim. ANCAK, test kodu programın (doğal olarak) normal bir çalışma daha çok daha fazla nesne oluşturma içerir, bu yüzden bunun çok ilgili olmayabilir.


10
Şekil hakkında hiçbir şey bilinmiyorsa, bir O (N) algoritması makul görünüyor. Daha hızlı bir şey, bölgenin şekliyle ilgili bir şey biliyorsan işe yarayacak olan her öğeye bakmamayı gerektirir.
amitp

3
Muhtemelen bunu çok sık yapmanız gerekmez. Ayrıca n çok büyük değil, toplam karo sayısından çok daha az.
Trilarion

1
Bu alanlar nasıl oluşturulur / değiştirilir? Ve ne sıklıkta değişiyorlar? Karo-kiremit seçildiyse, gittikçe komşu listenizi oluşturabilirsiniz ve sık sık değişmedikçe bir dizi bölgeyi ve sınırlarını depolayabilir ve gittikçe onları ekleyebilir veya kaldırabilirsiniz (böylece hayır sürekli olarak yeniden hesaplamaları gerekir).
DaveMongoose

2
Önemli: Bu gerçek bir teşhis ve profilli performans sorunu mu? Küçük bir sorun setiyle (sadece birkaç yüz eleman, gerçekten?) Bu O (n ^ 2) veya O (n) bir sorun olması gerektiğini düşünmüyorum. Her karede çalıştırılmayacak bir sistemde erken optimizasyon gibi görünüyor.
Delioth

1
En fazla 6 komşu olduğu için basit algoritma O (n) 'dir.
Eric

Yanıtlar:


11

Bir algoritma bulmak genellikle algoritmayı kolaylaştıran bir veri yapısı ile yapılır.

Bu durumda, bölgeniz.

Bölge sınırsız ve sınırsız (O (1) karma) bir küme olmalıdır.

Bölgeye her öğe eklediğinizde, bitişik döşemeleri yineleyip kenarlık döşemesi olup olmadığına bakın; bu durumda, bir öğe döşemesi değilse, bir sınır döşemesidir.

Bölgeden bir öğe çıkardığınızda, bitişik döşemelerinin hala bölgede olduğundan emin olursunuz ve kendinizin bir sınır döşemesi olup olmayacağını görürsünüz. Hızlı olması için buna ihtiyacınız varsa, kenar döşemelerinin "bitişik sayımlarını" takip etmelerini sağlayın.

Bu, bir bölgeye veya bölgeye bir döşeme eklediğinizde veya kaldırdığınızda O (1) çalışmasını gerektirir. Sınırı ziyaret etmek O (sınır uzunluğu) alır. "Sınırın ne olduğunu" bilmek istediğiniz sürece, bölgeye öğe ekleyip / kaldırdığınızdan çok daha sık, bu kazanmalıdır.


9

Bölgenizin ortasındaki deliklerin kenarlarını da bulmanız gerekiyorsa, bölge sınırındaki çizgisel çizginiz yapabileceğimiz en iyisidir. İç kısımdaki herhangi bir karo potansiyel olarak saymamız gereken bir delik olabilir, bu nedenle tüm delikleri bulduğumuzdan emin olmak için bölgenin anahatlarıyla sınırlanan alandaki her karoya en az bir kez bakmamız gerekir.

Ancak sadece dış sınırı (iç delikleri değil) bulmakla ilgileniyorsanız, bunu biraz daha verimli bir şekilde yapabiliriz:

  1. Bölgenizi ayıran bir kenar bulun. Bunu şu şekilde yapabilirsiniz ...

    • (en az bir bölge kutucuğunu biliyorsanız ve haritanızda tam olarak bir tane bağlı bölge bloğunuz olduğunu biliyorsanız)

      ... bölgenizdeki keyfi bir kutucuktan başlayıp haritanızın en yakın kenarına doğru ilerliyoruz. Bunu yaparken, bölge döşemesinden bölge olmayan döşemeye geçiş yaptığınız son kenarı unutmayın. Haritanın kenarına çarptığınızda, bu hatırlanan kenar başlangıç ​​kenarınızdır.

      Bu tarama haritanın çapında doğrusaldır.

    • veya (bölge kutunuzdan herhangi birinin nerede olduğunu bilmiyorsanız veya haritanızda bağlantısı kesilmiş birden fazla bölge olabilir)

      ... haritanızın bir kenarından başlayarak, bir arazi döşemesine ulaşana kadar her satırı tarayın. Araziden araziye geçtiğiniz son kenar, başlangıç ​​kenarınızdır.

      Bu tarama , haritanın alanındaki en kötü lineer olabilir (çapında ikinci dereceden), ancak aramanızı sınırlamak için herhangi bir sınırınız varsa (örneğin, bölgenin neredeyse her zaman orta sıraları geçtiğini biliyorsunuz) bu en kötü- vaka davranışı.

  2. 1. adımda bulunan başlangıç ​​kenarınızdan başlayarak, başlangıç ​​kenarına dönene kadar dış taraftaki her arazi dışı döşemeyi sınır koleksiyonunuza ekleyerek arazinizin çevresini takip edin.

    Bu kenar takip eden adım, alanından ziyade arazi anahattınızın çevresinde doğrusaldır . Dezavantajı, kodun daha karmaşık olmasıdır, çünkü kenarın alabileceği her dönüş türünü hesaba katmanız ve girişlerde çift sayım sınır döşemelerinden kaçınmanız gerekir.

Örnekleriniz gerçek büyüklüğünüzü birkaç büyüklükte temsil ediyorsa, o zaman kendim saf alan araması için giderdim - bu kadar az sayıda karo üzerinde hala hızlı bir şekilde hızlı olacak ve yazmak oldukça basit olacak , anlayın ve koruyun (genellikle daha az hataya yol açar!)


7

Uyarı : Bir karonun sınırda olup olmadığı sadece ona ve komşularına bağlıdır.

Bu yüzden:

  • Bu sorguyu tembel olarak çalıştırmak kolaydır. Örneğin: Sınırı tüm haritada aramanıza gerek yoktur, yalnızca görünenlerde arama yapmanız gerekir.

  • Bu sorguyu paralel olarak çalıştırmak kolaydır. Aslında, bunu yapan bazı gölgelendirici kodunu görüntüleyebilirim. Ve bu görselleştirmeden başka bir şeye ihtiyacınız varsa, bir dokuya dönüşebilir ve kullanabilirsiniz.

  • Bir kutucuk değişirse, sınır yalnızca yerel olarak değişir, yani her şeyi tekrar hesaplamanız gerekmez.

Sınırı önceden de hesaplayabilirsiniz. Yani, altıgenleri dolduruyorsanız, bir kutunun o anda sınır olup olmadığına karar verebilirsiniz. Bu şu demek oluyor:

  • Izgarayı doldurmak için bir döngü kullanırsanız ve bu sınırın ne olduğuna karar vermek için kullandığınız döngü ile aynıdır.
  • Boş bir ızgarayla başlar ve değiştirmek için kutucukları seçerseniz, sınırı yerel olarak güncelleyebilirsiniz.

Sınır için bir liste kullanmayın. Gerçekten ihtiyacınız varsa bir set kullanın ( sınırın ne için olmasını istediğinizi bilmiyorum. ). Ancak, bir döşemenin sınırı olan veya döşemenin bir niteliği olmayan her şeyi yaparsanız, kontrol etmek için başka bir veri yapısına gitmeniz gerekmez.


2

Bölgenizi bir karo yukarı, sonra yukarı sağa, sonra aşağı sağa vb. Taşıyın. Daha sonra orijinal alanı kaldırın.

Altı kümenin tümünü birleştirmek O (n), sıralama O (n.log (n)), ayar farkı O (n) olmalıdır. Orijinal kutucuklar sıralı biçimde saklanmışsa, birleştirilmiş set O (n) olarak da sıralanabilir.

Her karoya en az bir kez erişmeniz gerektiğinden O (n) 'den daha az bir algoritma olduğunu düşünmüyorum.


1

Sadece bunun nasıl yapılacağı hakkında bir blog yazısı yazdım. Bu, @DMGregory'nin bir kenar hücresinden başlayıp çevre etrafında yürüyen ilk yöntemi kullanır. Python yerine C # 'dadır, ancak uyarlanması oldukça kolay olmalıdır.

https://dillonshook.com/hex-city-borders/


0

ORİJİNAL POST:

Bu siteye yorum yapamam, bu yüzden bir sahte kod algoritmasıyla cevap vermeye çalışacağım.

Her bölgenin sınırın bir parçası olan en fazla altı komşusu olduğunu biliyorsunuz. Bölgedeki her karo için, altı komşu karoyu potansiyel bir sınır listesine ekleyin. Ardından, bölgedeki her döşemeyi sınırdan çıkarın ve yalnızca kenarlık döşemelerine bırakıldınız. Her listeyi saklamak için sırasız bir küme kullanırsanız en iyi sonucu verir. Umarım yardımcı oldum.

DÜZENLEME Basit yinelemeden çok daha etkili yollar vardır. Aşağıdaki (şimdi silinmiş) cevabımda belirtmeye çalıştığım gibi, en iyi durumda O (1) ve en kötü durumda O (n) elde edebilirsiniz.

Bir bölgeye fayans ekleme O (1) - O (N):

Komşu olmaması durumunda, sadece yeni bir bölge yaratırsınız.

Bir komşu olması durumunda, yeni kutucuğu mevcut bölgeye eklersiniz.

5 veya 6 komşu olması durumunda, hepsinin bağlı olduğunu bilirsiniz, böylece yeni döşemeyi mevcut bölgeye eklersiniz. Bunların hepsi O (1) işlemidir ve yeni sınır bölgelerinin güncellenmesi de O (1) 'dir, çünkü bir kümenin diğeriyle basit bir birleşimidir.

2, 3 veya 4 komşu bölgede, 3 benzersiz bölgeyi birleştirmeniz gerekebilir. Bu, kombine bölge boyutunda O (N) 'dir.

Bir döşemenin O (1) - O (N) bölgesinden çıkarılması:

Sıfır komşu ile bölgeyi silin. O (1)

Bir komşu ile kiremitleri bölgeden çıkarın. O (1)

İki veya daha fazla komşu ile 3 adede kadar yeni bölge oluşturulabilir. Bu O (N).

Boş zamanımı son birkaç hafta boyunca basit bir altıgen tabanlı bölge oyunu olan bir gösteri programı geliştirerek geçirdim. Bölgeleri yan yana koyarak gelirinizi artırmaya çalışın. Kırmızı, Yeşil ve Mavi olmak üzere 3 oyuncu, sınırlı bir oyun alanına stratejik olarak fayans yerleştirerek en fazla geliri elde etmek için yarışır.

Oyunu buradan indirebilirsiniz (.7z formatında) hex.7z

Basit fare kontrolü LMB bir kutucuğu yerleştirir (sadece vurgulu vurgulanan yerlere yerleştirilebilir). Üstte puan, altta gelir. Etkili bir strateji geliştirip geliştiremeyeceğinize bakın.

Kod burada bulunabilir:

Kartal / EagleTest

Kaynak koddan oluşturmak için Eagle ve Allegro 5'e ihtiyacınız var. Her ikisi de cmake ile inşa edin. Hex oyunu şu anda CB projesi ile inşa ediyor.

Bu downvote'ları baş aşağı çevirin. :)


Her ne kadar dahil etmeden önce komşu karoları kontrol etmek sonunda hepsini kaldırmaktan biraz daha hızlı olmasına rağmen, OP'deki algoritmanın yaptığı şey budur.
ScienceSnake

Temelde aynı ama sadece bir kez onları daha verimli
çıkarırsanız

Cevabımı tamamen güncelledim ve aşağıdaki gereksiz cevabı sildim.
BugSquasher
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.