อัลกอริทึมที่มีประสิทธิภาพสำหรับขอบเขตของชุดของกระเบื้อง


12

ฉันมีตารางเรียงต่อกันของขนาด จำกัด ที่ทราบซึ่งก่อตัวเป็นแผนที่ แผ่นบางแผ่นที่อยู่ในแผนที่จะถูกวางลงในชุดที่รู้จักกันในชื่ออาณาเขต ดินแดนนี้เชื่อมต่อกัน แต่ไม่มีใครรู้เกี่ยวกับรูปร่างของมัน ส่วนใหญ่มันจะเป็นหยดปกติ แต่อาจยืดออกไปได้ในทิศทางเดียวและอาจมีรูได้ ฉันสนใจที่จะค้นหาชายแดน (ด้านนอก) ของอาณาเขต

นั่นคือฉันต้องการรายการของกระเบื้องทั้งหมดที่แตะหนึ่งในกระเบื้องในดินแดนโดยที่ตัวเองไม่ได้อยู่ในดินแดน วิธีที่มีประสิทธิภาพในการค้นหาสิ่งนี้คืออะไร?

สำหรับความยากเป็นพิเศษมันเกิดขึ้นที่ไทล์ของฉันเป็น hexes แต่ฉันคิดว่ามันไม่ได้สร้างความแตกต่างมากนักแต่ละไทล์ยังคงมีพิกัด x และ y เป็นจำนวนเต็มและถ้ามีไทล์ฉันสามารถหาเพื่อนบ้านได้อย่างง่ายดาย ด้านล่างเป็นตัวอย่างบางส่วน: สีดำคืออาณาเขตและเส้นขอบสีน้ำเงินที่ฉันต้องการค้นหา ตัวอย่างดินแดนและเส้นขอบ สิ่งนี้ในตัวของมันเองไม่ใช่ปัญหาที่ยากเลยอัลกอริธึมง่ายๆสำหรับสิ่งนี้ใน pseudo-python คือ:

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)

อย่างไรก็ตามมันช้าและฉันต้องการบางสิ่งที่ดีกว่า ฉันมี O (n) วนรอบอาณาเขตอีกวงหนึ่ง (อันสั้น แต่ก็ยัง) เหนือเพื่อนบ้านทั้งหมดจากนั้นฉันต้องตรวจสอบการเป็นสมาชิกของสองรายการซึ่งหนึ่งในนั้นคือขนาด n นั่นทำให้สเกลของ O (n ^ 2) แย่มาก ฉันสามารถลดให้เป็น O (n) ได้โดยใช้ชุดแทนที่จะเป็นรายการสำหรับเส้นขอบและอาณาเขตเพื่อให้สมาชิกตรวจสอบได้อย่างรวดเร็ว แต่ก็ยังไม่ยอดเยี่ยม ฉันคาดหวังว่าจะมีหลายกรณีที่พื้นที่มีขนาดใหญ่ แต่ชายแดนมีขนาดเล็กเนื่องจากพื้นที่เรียบง่ายเทียบกับการขยายขนาดของเส้น ตัวอย่างเช่นหากอาณาเขตเป็นเลขฐานสิบหกของรัศมี 5 จะมีขนาด 91 แต่เส้นขอบจะมีขนาดเท่ากับ 36

ทุกคนสามารถเสนอสิ่งที่ดีกว่าได้ไหม

แก้ไข:

เพื่อตอบคำถามบางข้อด้านล่าง ขอบเขตสามารถมีขนาดตั้งแต่ประมาณ 20 ถึง 100 หรือมากกว่านั้น ชุดของแผ่นกระเบื้องที่สร้างอาณาเขตเป็นคุณลักษณะของวัตถุและเป็นวัตถุที่ต้องการชุดของแผ่นกระเบื้องชายแดนทั้งหมด

เริ่มแรกอาณาเขตจะถูกสร้างเป็นบล็อกแล้วส่วนใหญ่จะได้รับไพ่ทีละส่วน ในกรณีนี้มันเป็นความจริงที่ว่าวิธีที่เร็วที่สุดคือการเก็บชุดของเส้นขอบไว้และอัปเดตเฉพาะบนไทล์ที่ได้รับมา ในบางครั้งอาจมีการเปลี่ยนแปลงครั้งใหญ่ในอาณาเขตดังนั้นจึงจำเป็นต้องคำนวณใหม่อย่างเต็มที่

ตอนนี้ฉันเห็นว่าการทำอัลกอริธึมการค้นหาเส้นขอบอย่างง่ายเป็นทางออกที่ดีที่สุด ความซับซ้อนเพิ่มเติมเพียงอย่างเดียวที่เพิ่มขึ้นนี้คือการตรวจสอบให้แน่ใจว่ามีการคำนวณเส้นขอบใหม่ทุกครั้งที่อาจจำเป็นต้องมี แต่ไม่มากไปกว่านั้น ฉันค่อนข้างมั่นใจว่าสิ่งนี้สามารถทำได้อย่างน่าเชื่อถือในกรอบปัจจุบันของฉัน

สำหรับเวลาในรหัสปัจจุบันของฉันฉันมีกิจวัตรบางอย่างที่ต้องตรวจสอบไทล์ของดินแดนทั้งหมด ไม่ใช่ทุกเทิร์น แต่เป็นการสร้างและบางครั้งหลังจากนั้น มันใช้เวลามากกว่า 50% ของเวลาทำงานในชุดทดสอบของฉันถึงแม้ว่ามันจะเป็นส่วนเล็ก ๆ ของโปรแกรมที่สมบูรณ์ ดังนั้นฉันจึงกระตือรือร้นที่จะลดจำนวนซ้ำ ๆ อย่างไรก็ตามรหัสทดสอบเกี่ยวข้องกับการสร้างวัตถุมากกว่าการรันโปรแกรมปกติ (ตามธรรมชาติ) ดังนั้นฉันจึงรู้ว่านี่อาจไม่เกี่ยวข้องกันมากนัก


10
ฉันคิดว่าหากไม่มีใครรู้เรื่องรูปร่างอัลกอริทึม O (N) นั้นสมเหตุสมผล อะไรที่เร็วกว่านั้นก็ไม่จำเป็นต้องมองทุกองค์ประกอบของดินแดนซึ่งจะใช้ได้ก็ต่อเมื่อคุณรู้เรื่องรูปร่างฉันคิดว่า
amitp

3
คุณอาจไม่จำเป็นต้องทำแบบนั้นบ่อยนัก n ก็ไม่ใหญ่มากน้อยกว่าจำนวนกระเบื้องทั้งหมด
Trilarion

1
พื้นที่เหล่านี้ถูกสร้าง / เปลี่ยนแปลงอย่างไร และพวกเขาเปลี่ยนบ่อยแค่ไหน? หากพวกเขาได้รับการคัดเลือกแบบเรียงต่อกันจากนั้นคุณสามารถสร้างรายชื่อเพื่อนบ้านของคุณในขณะที่คุณไปและถ้าพวกเขาเปลี่ยนบ่อยคุณสามารถจัดเก็บอาร์เรย์ของดินแดนและขอบเขตของพวกเขาและเพิ่มหรือลบออกจากพวกเขาในขณะที่คุณไป จำเป็นต้องคำนวณใหม่อย่างต่อเนื่อง)
DaveMongoose

2
สำคัญ: นี่เป็นปัญหาด้านประสิทธิภาพที่ได้รับการวินิจฉัยและทำประวัติจริงหรือไม่? ด้วยชุดปัญหาที่เล็ก (แค่ไม่กี่ร้อยองค์ประกอบจริงเหรอ?) ฉันไม่คิดว่า O (n ^ 2) หรือ O (n) นี้ควรเป็นปัญหา เสียงเหมือนการปรับให้เหมาะสมก่อนกำหนดในระบบที่จะไม่ทำงานทุกเฟรม
Delioth

1
อัลกอริทึมง่าย ๆ คือ O (n) เนื่องจากมีเพื่อนบ้านไม่เกิน 6 คน
Eric

คำตอบ:


11

การค้นหาอัลกอริทึมมักจะทำได้ดีที่สุดกับโครงสร้างข้อมูลที่ทำให้อัลกอริทึมง่าย

ในกรณีนี้อาณาเขตของคุณ

ดินแดนควรเป็นชุดของเส้นขอบและองค์ประกอบ (O (1)) ที่ไม่มีการเรียงลำดับ

เมื่อใดก็ตามที่คุณเพิ่มองค์ประกอบลงในดินแดนคุณทำซ้ำบนกระเบื้องที่อยู่ติดกันและดูว่าพวกเขาควรจะเป็นกระเบื้องชายแดน ในกรณีนี้มันเป็นไทล์ชายแดนหากพวกเขาไม่ใช่ไทล์องค์ประกอบ

เมื่อใดก็ตามที่คุณลบองค์ประกอบออกจากพื้นที่คุณจะตรวจสอบให้แน่ใจว่าไทล์ที่อยู่ติดกันนั้นยังคงอยู่ในอาณาเขตนั้นและคุณจะเห็นว่าตัวเองควรจะกลายเป็นไทล์ชายแดนหรือไม่ หากคุณต้องการให้สิ่งนี้รวดเร็วให้วางไทล์ของเส้นขอบเพื่อติดตาม "จำนวนที่อยู่ติดกัน"

สิ่งนี้จะใช้ O (1) ทำงานทุกครั้งที่คุณเพิ่มหรือลบไทล์ไปยังหรือออกจากพื้นที่ การเยี่ยมชมชายแดนนั้นต้องใช้ O (ความยาวของเส้นขอบ) ตราบใดที่คุณต้องการรู้ว่า "เส้นขอบคืออะไร" บ่อยครั้งกว่าที่คุณเพิ่ม / ลบองค์ประกอบออกจากดินแดนสิ่งนี้ควรชนะ


9

หากคุณต้องการหาขอบของรูที่อยู่ตรงกลางอาณาเขตของคุณเช่นกันเส้นตรงของคุณในพื้นที่ของขอบเขตนั้นเป็นวิธีที่ดีที่สุดที่เราสามารถทำได้ กระเบื้องใด ๆ ในการตกแต่งภายในอาจเป็นรูที่เราต้องนับดังนั้นเราจำเป็นต้องดูทุกกระเบื้องในพื้นที่ที่ล้อมรอบด้วยโครงร่างของอาณาเขตอย่างน้อยหนึ่งครั้งเพื่อให้แน่ใจว่าเราได้พบหลุมทั้งหมดแล้ว

แต่หากคุณกังวลเกี่ยวกับการค้นหาเส้นขอบด้านนอก (ไม่ใช่รูภายใน) เราสามารถทำได้อย่างมีประสิทธิภาพมากขึ้น:

  1. ค้นหาหนึ่งขอบที่แยกดินแดนของคุณ คุณสามารถทำได้โดย ...

    • (หากคุณรู้จักไทล์ดินแดนอย่างน้อยหนึ่งกระเบื้องและรู้ว่าคุณมีดินแดนที่เชื่อมต่อกันหนึ่งอันบนแผนที่ของคุณ)

      ... เริ่มต้นที่ไทล์โดยพลการในอาณาเขตของคุณและเคลื่อนไปทางขอบที่ใกล้ที่สุดของแผนที่ เมื่อคุณทำเช่นนั้นให้จดจำขอบสุดท้ายที่คุณเปลี่ยนจากไทล์อาณาเขตเป็นไทล์ที่ไม่ใช่อาณาเขต เมื่อคุณไปถึงขอบของแผนที่ขอบที่จำได้นี้คือขอบเริ่มต้นของคุณ

      การสแกนนี้เป็นเส้นตรงในเส้นผ่าศูนย์กลางของแผนที่

    • หรือ (หากคุณไม่ทราบว่าไทล์ดินแดนใดของคุณอยู่ล่วงหน้าหรือแผนที่ของคุณอาจมีหลาย ๆ พื้นที่ที่ไม่ได้เชื่อมต่อ)

      ... เริ่มต้นที่ขอบแผนที่ของคุณสแกนไปตามแต่ละแถวจนกว่าคุณจะชนกระเบื้องภูมิประเทศ ขอบสุดท้ายที่คุณข้ามจากภูมิประเทศที่ไม่ใช่ภูมิประเทศคือขอบเริ่มต้นของคุณ

      การสแกนนี้อาจเป็นเชิงเส้นที่เลวร้ายที่สุดในพื้นที่ของแผนที่ (กำลังสองในเส้นผ่าศูนย์กลาง) แต่ถ้าคุณมีขอบเขตใด ๆ ที่จะ จำกัด การค้นหาของคุณ (พูดว่าคุณรู้ว่าอาณาเขตมักจะข้ามแถวกลาง) คุณสามารถปรับปรุงที่เลวร้ายที่สุดนี้ได้ พฤติกรรมของผู้ป่วย

  2. เริ่มต้นที่ขอบเริ่มต้นของคุณที่พบในขั้นตอนที่ 1 ตามขอบรอบภูมิประเทศของคุณเพิ่มไทล์ที่ไม่ใช่ภูมิประเทศด้านนอกลงในคอลเล็กชันเส้นขอบของคุณจนกว่าคุณจะกลับไปที่ขอบเริ่มต้น

    ขั้นตอนต่อไปนี้เป็นเส้นขอบในขอบเขตของภูมิประเทศของคุณแทนที่จะเป็นพื้นที่ ข้อเสียคือรหัสมีความซับซ้อนมากขึ้นเพราะคุณจำเป็นต้องคำนึงถึงการเลี้ยวแต่ละประเภทที่สามารถทำได้

หากตัวอย่างของคุณเป็นตัวแทนของขนาดข้อมูลจริงของคุณภายในไม่กี่คำสั่งดังนั้นฉันจะไปหาพื้นที่ที่ไร้เดียงสา - มันจะยังคงรวดเร็วอย่างเห็นได้ชัดบนกระเบื้องจำนวนเล็กน้อยและง่ายต่อการเขียน เข้าใจและบำรุงรักษา (โดยทั่วไปจะนำไปสู่ข้อบกพร่องน้อยลง!)


7

แจ้งให้ทราบล่วงหน้า : ไม่ว่าจะเป็นกระเบื้องอยู่ในขอบเขตเพียงขึ้นอยู่กับมันและเพื่อนบ้าน

เนื่องจากว่า:

  • มันง่ายที่จะเรียกใช้แบบสอบถามนี้อย่างเกียจคร้าน ตัวอย่างเช่น: คุณไม่จำเป็นต้องค้นหาขอบเขตบนแผนที่ทั้งหมดเฉพาะสิ่งที่มองเห็นได้

  • มันง่ายต่อการเรียกใช้แบบสอบถามนี้ในแบบคู่ขนาน ที่จริงแล้วฉันสามารถอิมเมจโค้ด shader ได้ และถ้าคุณต้องการมันสำหรับการสร้างภาพข้อมูลอย่างอื่นคุณสามารถสร้างพื้นผิวและใช้มันได้

  • หากไทล์เปลี่ยนขอบเขตจะเปลี่ยนเฉพาะที่ซึ่งหมายความว่าคุณไม่จำเป็นต้องคำนวณทั้งหมดอีกครั้ง

คุณสามารถคำนวณขอบเขตล่วงหน้าได้ นั่นคือถ้าคุณเติมเลขฐานสิบหกคุณสามารถตัดสินใจได้ว่าไทล์จะมีขอบเขตในขณะนั้นหรือไม่ นั่นหมายความว่า:

  • หากคุณใช้การวนซ้ำเพื่อเติมกริดและมันก็เหมือนกับที่คุณใช้ในการตัดสินใจว่าขอบเขตคืออะไร
  • หากคุณเริ่มต้นด้วยกริดที่ว่างเปล่าและเลือกไทล์เพื่อเปลี่ยนคุณสามารถอัปเดตขอบเขตภายในเครื่องได้

ห้ามใช้รายการสำหรับขอบเขต ใช้ชุดถ้าคุณมีจริง ๆ ( ฉันไม่รู้ว่าคุณต้องการขอบเขตสำหรับ ) อย่างไรก็ตามหากคุณทำสิ่งใดก็ตามที่เรียงกันเป็นเส้นแบ่งเขตหรือไม่ใส่คุณลักษณะของช่องว่างคุณไม่จำเป็นต้องไปที่โครงสร้างข้อมูลอื่นเพื่อตรวจสอบ


2

ย้ายพื้นที่ของคุณขึ้นหนึ่งไทล์จากนั้นขึ้นไปทางขวาจากนั้นลงไปทางขวา ฯลฯ จากนั้นนำพื้นที่เดิมออก

การรวมทั้งหกชุดควรเป็น O (n), การเรียงลำดับ O (n.log (n)), ชุดผลต่าง O (n) หากกระเบื้องดั้งเดิมถูกจัดเก็บในรูปแบบที่เรียงลำดับชุดที่ผสานสามารถเรียงลำดับได้ใน O (n) เช่นกัน

ฉันไม่คิดว่ามีอัลกอริทึมที่มีค่าน้อยกว่า O (n) เนื่องจากคุณต้องเข้าถึงไทล์แต่ละอันอย่างน้อยหนึ่งครั้ง


1

ฉันเพิ่งเขียนโพสต์บล็อกเกี่ยวกับวิธีการทำเช่นนี้ วิธีนี้ใช้วิธีแรกที่ @DMGregory กล่าวถึงโดยเริ่มจากเซลล์ขอบแล้วเดินไปรอบ ๆ ขอบเขต มันอยู่ใน C # แทนที่จะเป็น Python แต่ควรง่ายต่อการปรับตัว

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


0

โพสต์ต้นฉบับ:

ฉันไม่สามารถแสดงความคิดเห็นในเว็บไซต์นี้ดังนั้นฉันจะพยายามตอบด้วยอัลกอริธึม pseudocode

คุณรู้ว่าทุก ๆ ดินแดนมีเพื่อนบ้านไม่เกินหกคนที่เป็นส่วนหนึ่งของขอบเขต สำหรับทุกไทล์ในดินแดนให้เพิ่มไทล์ไพ่ข้างเคียงทั้งหกในรายการเส้นขอบที่อาจเกิดขึ้น จากนั้นลบทุกไทล์ในอาณาเขตจากชายแดนและคุณเหลือเพียงไทล์ชายแดน มันจะทำงานได้ดีที่สุดถ้าคุณใช้ชุด unordered เพื่อจัดเก็บแต่ละรายการ หวังว่าฉันจะเป็นประโยชน์

แก้ไขมีวิธีที่มีประสิทธิภาพมากกว่าการทำซ้ำง่าย ๆ ขณะที่ฉันพยายามระบุคำตอบ (ลบแล้ว) ด้านล่างคุณสามารถบรรลุ O (1) ในกรณีที่ดีที่สุดและ O (n) ในกรณีที่แย่ที่สุด

การเพิ่มไทล์ลงในดินแดน O (1) - O (N):

ในกรณีที่ไม่มีเพื่อนบ้านคุณเพียงแค่สร้างอาณาเขตใหม่

ในกรณีของเพื่อนบ้านคนหนึ่งคุณเพิ่มไทล์ใหม่ไปยังดินแดนที่มีอยู่

ในกรณีของเพื่อนบ้าน 5 หรือ 6 คนคุณรู้ว่ามันเชื่อมต่อกันทั้งหมดดังนั้นคุณจึงเพิ่มไทล์ใหม่ไปยังดินแดนที่มีอยู่ เหล่านี้คือการดำเนินการ O (1) ทั้งหมดและการอัปเดตขอบเขตอาณาเขตใหม่คือ O (1) เช่นกันเนื่องจากเป็นการรวมที่เรียบง่ายของหนึ่งชุดเข้าด้วยกัน

ในกรณีของเขตพื้นที่ใกล้เคียง 2, 3 หรือ 4 คุณอาจต้องรวมพื้นที่ที่ไม่ซ้ำกันมากถึง 3 แห่ง นี่คือ O (N) ตามขนาดอาณาเขตรวม

การลบไทล์ออกจากดินแดน O (1) - O (N):

ด้วยศูนย์เพื่อนบ้านลบอาณาเขต O (1)

ด้วยเพื่อนบ้านหนึ่งคนถอดกระเบื้องออกจากดินแดน O (1)

ด้วยเพื่อนบ้านสองคนขึ้นไปอาจมีการสร้างพื้นที่ใหม่สูงสุด 3 เขต นี่คือ O (N)

ฉันใช้เวลาว่างในช่วงสองสามสัปดาห์ที่ผ่านมาเพื่อพัฒนาโปรแกรมสาธิตซึ่งเป็นเกมที่ใช้ฐานสิบหกแบบง่าย พยายามเพิ่มรายได้ของคุณโดยการวางอาณาเขตติดกัน ผู้เล่น 3 คนสีแดงสีเขียวและสีน้ำเงินแข่งขันกันเพื่อสร้างรายได้มากที่สุดโดยวางกลยุทธ์ลงบนพื้นที่ จำกัด ของเกม

คุณสามารถดาวน์โหลดเกมได้ที่นี่ (ในรูปแบบ. 7z) hex.7z

การควบคุมเมาส์อย่างง่าย LMB วางไทล์ (สามารถวางได้เฉพาะที่ไฮไลต์ด้วยโฮเวอร์เท่านั้น) คะแนนด้านบนรายได้ที่ด้านล่าง ดูว่าคุณจะได้รับกลยุทธ์ที่มีประสิทธิภาพหรือไม่

รหัสสามารถพบได้ที่นี่:

อีเกิล / EagleTest

ในการสร้างจากซอร์สโค้ดคุณต้องใช้ Eagle และ Allegro 5 ทั้ง build ด้วย cmake เกม Hex สร้างด้วยโครงการ CB ในปัจจุบัน

เปิด downvotes เหล่านั้นกลับหัว :)


นั่นคือสิ่งที่อัลกอริทึมใน OP ทำแม้ว่าการตรวจสอบไทล์กระเบื้องก่อนการรวมนั้นจะเร็วกว่าการเอาออกทั้งหมดในตอนท้าย
ScienceSnake

โดยทั่วไปแล้วจะเหมือนกัน แต่ถ้าคุณลบออกเมื่อมันมีประสิทธิภาพมากขึ้นเท่านั้น
BugSquasher

ฉันได้อัปเดตคำตอบของฉันทั้งหมดแล้วและลบคำตอบที่ไม่น่าสนใจด้านล่าง
BugSquasher
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.