Thuật toán hiệu quả cho ranh giới của một tập hợp các ô


12

Tôi có một lưới các ô có kích thước hữu hạn đã biết tạo thành bản đồ. Một số gạch bên trong bản đồ được đặt vào một tập hợp được gọi là lãnh thổ. Lãnh thổ này được kết nối, nhưng không có gì được biết về hình dạng của nó. Hầu hết thời gian nó sẽ là một đốm màu khá đều đặn, nhưng nó có thể rất dài theo một hướng, và nó thậm chí có thể có lỗ hổng. Tôi quan tâm đến việc tìm ra biên giới (bên ngoài) của lãnh thổ.

Đó là, tôi muốn một danh sách tất cả các ô chạm vào một trong các ô trong lãnh thổ mà không nằm trong lãnh thổ. Một cách hiệu quả để tìm thấy điều này là gì?

Đối với khó khăn thêm, điều đó xảy ra là các ô của tôi là hình lục giác, nhưng tôi nghi ngờ điều này không tạo ra quá nhiều sự khác biệt, mỗi ô vẫn được gắn nhãn với tọa độ x và y và, được cung cấp một ô, tôi có thể dễ dàng tìm thấy các hàng xóm của nó. Dưới đây là một vài ví dụ: Màu đen là lãnh thổ và màu xanh là đường viền tôi muốn tìm. Ví dụ về lãnh thổ và biên giới Bản thân điều này không phải là một vấn đề khó, Một thuật toán đơn giản cho điều này, trong giả hành, là:

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)

Tuy nhiên điều này là chậm và tôi muốn một cái gì đó tốt hơn. Tôi có một vòng lặp O (n) trên lãnh thổ, một vòng lặp khác (một đoạn ngắn, nhưng vẫn) trên tất cả các hàng xóm và sau đó tôi phải kiểm tra tư cách thành viên qua hai danh sách, một trong số đó có kích thước n. Điều đó mang lại một tỷ lệ khủng khiếp của O (n ^ 2). Tôi có thể giảm điều đó xuống O (n) bằng cách sử dụng các tập hợp thay vì danh sách cho đường viền và lãnh thổ để kiểm tra tư cách thành viên nhanh chóng, nhưng nó vẫn không tuyệt vời. Tôi hy vọng sẽ có nhiều trường hợp lãnh thổ rộng nhưng biên giới nhỏ do diện tích đơn giản so với tỷ lệ đường. Ví dụ: nếu lãnh thổ là một hình lục giác có bán kính 5, nó có kích thước 91 nhưng đường viền chỉ có kích thước 36.

Bất cứ ai có thể đề xuất một cái gì đó tốt hơn?

Biên tập:

Để trả lời một số câu hỏi dưới đây. Lãnh thổ có thể có kích thước, từ khoảng 20 đến 100 hoặc hơn. Tập hợp các ô hình thành lãnh thổ là một thuộc tính của một đối tượng và đối tượng này cần một tập hợp tất cả các ô viền.

Ban đầu, lãnh thổ được tạo thành một khối, và sau đó chủ yếu giành được từng viên một. Trong trường hợp này, đúng là cách nhanh nhất chỉ là giữ một bộ đường viền và chỉ cập nhật nó trên ô có được. Đôi khi, một sự thay đổi lớn đối với lãnh thổ có thể xảy ra - vì vậy nó sẽ cần được tính toán lại đầy đủ sau đó.

Bây giờ tôi cho rằng làm một thuật toán tìm đường viền đơn giản là giải pháp tốt nhất. Sự phức tạp bổ sung duy nhất mà điều này tăng lên là để đảm bảo rằng đường viền được tính toán lại mỗi lần nó có thể cần, nhưng không nhiều hơn thế. Tôi khá tự tin rằng điều này có thể được thực hiện một cách đáng tin cậy trong khuôn khổ hiện tại của tôi.

Về thời gian, trong mã hiện tại của tôi, tôi có một số thói quen cần kiểm tra mọi ô của lãnh thổ. Không phải mỗi lượt, nhưng về sáng tạo và đôi khi sau đó. Điều đó chiếm hơn 50% thời gian chạy bộ mã kiểm tra của tôi mặc dù đó là một phần rất nhỏ của chương trình hoàn chỉnh. Do đó tôi rất muốn giảm thiểu bất kỳ sự lặp lại. TUY NHIÊN, mã kiểm tra liên quan đến việc tạo đối tượng nhiều hơn so với việc chạy chương trình bình thường (một cách tự nhiên), vì vậy tôi nhận ra điều này có thể không liên quan lắm.


10
Tôi nghĩ rằng nếu không có gì được biết về hình dạng, thuật toán O (N) có vẻ hợp lý. Bất cứ điều gì nhanh hơn sẽ yêu cầu không nhìn vào mọi yếu tố của lãnh thổ, nó sẽ chỉ hoạt động nếu bạn biết điều gì đó về hình dạng, tôi nghĩ.
amitp

3
Bạn có thể không cần phải làm điều đó rất thường xuyên. Ngoài ra n không lớn lắm, ít hơn nhiều so với tổng số gạch.
Trilarion

1
Làm thế nào những khu vực này được tạo / thay đổi? Và họ có thường xuyên thay đổi không? Nếu chúng được chọn theo từng ô thì bạn có thể tạo danh sách hàng xóm khi bạn đi và trừ khi chúng thay đổi thường xuyên thì bạn có thể lưu trữ một loạt các lãnh thổ và ranh giới của chúng và thêm hoặc xóa khỏi chúng khi bạn đi (vì vậy không cần liên tục tính toán lại chúng).
DaveMongoose

2
Quan trọng: Đây có phải là một vấn đề hiệu suất được chẩn đoán và định hình thực tế? Với một vấn đề được đặt ra là nhỏ (chỉ vài trăm yếu tố, thực sự?) Tôi thực sự không nghĩ rằng O (n ^ 2) hoặc O (n) này là một vấn đề. Âm thanh như tối ưu hóa sớm trên một hệ thống sẽ không được chạy mọi khung hình.
Delioth

1
Thuật toán đơn giản là O (n) vì có nhiều nhất 6 hàng xóm.
Eric

Câu trả lời:


11

Tìm một thuật toán thường được thực hiện tốt nhất với cấu trúc dữ liệu giúp thuật toán dễ dàng.

Trong trường hợp này, lãnh thổ của bạn.

Lãnh thổ phải là một tập hợp các phần tử và phần tử không có thứ tự (O (1)).

Bất cứ khi nào bạn thêm một yếu tố vào lãnh thổ, bạn lặp lại trên các ô liền kề và xem liệu chúng có phải là một đường viền không; trong trường hợp này, chúng là một ô viền nếu chúng không phải là một phần tử gạch.

Bất cứ khi nào bạn trừ đi một yếu tố khỏi lãnh thổ, bạn đảm bảo rằng các ô liền kề của nó vẫn ở trong lãnh thổ và bạn xem liệu mình có nên trở thành một ô viền không. Nếu bạn cần điều này thật nhanh, hãy để các đường viền theo dõi "số lượng liền kề" của chúng.

Điều này làm cho O (1) hoạt động bất cứ khi nào bạn thêm hoặc xóa một ô đến hoặc ra khỏi lãnh thổ. Tham quan đường viền mất O (chiều dài đường viền). Miễn là bạn muốn biết "biên giới là gì" thường xuyên hơn so với việc bạn thêm / xóa các yếu tố khỏi lãnh thổ, điều này sẽ giành chiến thắng.


9

Nếu bạn cũng cần tìm các cạnh của lỗ ở giữa lãnh thổ của mình, thì tuyến tính của bạn trong khu vực của lãnh thổ bị ràng buộc là điều tốt nhất chúng ta có thể làm. Bất kỳ ô nào ở bên trong có thể có khả năng là một lỗ mà chúng ta cần phải đếm, vì vậy chúng ta cần xem xét mọi ô trong khu vực giới hạn bởi đường viền của lãnh thổ ít nhất một lần để chắc chắn rằng chúng ta đã tìm thấy tất cả các lỗ.

Nhưng nếu bạn chỉ quan tâm đến việc tìm đường viền bên ngoài (không phải lỗ bên trong), thì chúng ta có thể làm điều này hiệu quả hơn một chút:

  1. Tìm một cạnh tách biệt lãnh thổ của bạn. Bạn có thể làm điều này bằng cách ...

    • (nếu bạn biết ít nhất một ô lãnh thổ và biết rằng bạn có chính xác một đốm lãnh thổ được kết nối trên bản đồ của bạn)

      ... bắt đầu tại một ô tùy ý trong lãnh thổ của bạn và di chuyển về phía rìa gần nhất của bản đồ. Khi bạn làm như vậy, hãy nhớ cạnh cuối cùng nơi bạn đã chuyển từ lát xếp lãnh thổ sang ô không thuộc lãnh thổ. Khi bạn chạm vào cạnh của bản đồ, cạnh được nhớ này là cạnh bắt đầu của bạn.

      Quét này là tuyến tính trong đường kính của bản đồ.

    • hoặc (nếu bạn không biết trước bất kỳ ô lãnh thổ nào của mình hoặc bản đồ của bạn có thể chứa một số lãnh thổ bị ngắt kết nối)

      ... bắt đầu từ một cạnh của bản đồ của bạn, quét dọc theo mỗi hàng cho đến khi bạn chạm vào một ô địa hình. Cạnh cuối cùng bạn vượt qua từ phi địa hình đến địa hình là cạnh bắt đầu của bạn.

      Quá trình quét này có thể ở tuyến tính tệ nhất trong khu vực bản đồ (bậc hai trong đường kính của nó), nhưng nếu bạn có bất kỳ giới hạn nào để hạn chế tìm kiếm của mình (giả sử, bạn biết rằng lãnh thổ hầu như luôn luôn vượt qua các hàng giữa), bạn có thể cải thiện điều tồi tệ nhất này- hành vi vụ án.

  2. Bắt đầu từ cạnh bắt đầu của bạn được tìm thấy ở bước 1, đi theo nó xung quanh chu vi địa hình của bạn, thêm từng ô không địa hình ở bên ngoài vào bộ sưu tập viền của bạn, cho đến khi bạn quay trở lại cạnh bắt đầu.

    Bước tiếp theo cạnh này là tuyến tính theo chu vi của phác thảo địa hình của bạn, chứ không phải là khu vực của nó. Nhược điểm là mã phức tạp hơn vì bạn cần tính đến từng loại biến mà cạnh có thể thực hiện và tránh đếm các ô viền biên ở hai cửa vào.

Nếu các ví dụ của bạn là đại diện cho kích thước dữ liệu thực của bạn trong một vài bậc độ lớn, thì bản thân tôi sẽ tìm kiếm khu vực ngây thơ - nó sẽ vẫn nhanh chóng trên một số lượng gạch nhỏ như vậy và việc viết đơn giản hơn rất nhiều , hiểu và duy trì (thường dẫn đến ít lỗi hơn!)


7

Lưu ý : Việc gạch có nằm trên ranh giới hay không chỉ phụ thuộc vào nó và hàng xóm của nó.

Bởi vì điều đó:

  • Thật dễ dàng để chạy truy vấn này một cách lười biếng. Ví dụ: Bạn không cần tìm kiếm ranh giới trên toàn bản đồ, chỉ trên những gì có thể nhìn thấy.

  • Thật dễ dàng để chạy truy vấn này song song. Trong thực tế, tôi có thể hình ảnh một số mã shader làm điều này. Và nếu bạn cần nó cho một cái gì đó khác mà trực quan hóa, bạn có thể kết xuất thành một kết cấu và sử dụng nó.

  • Nếu một ô thay đổi, ranh giới chỉ thay đổi cục bộ, điều đó có nghĩa là bạn không cần phải tính toán lại toàn bộ.

Bạn cũng có thể tính toán trước ranh giới. Đó là, nếu bạn đang điền vào hex, bạn có thể quyết định xem một ô có ranh giới tại thời điểm đó không. Đó có nghĩa là:

  • Nếu bạn sử dụng một vòng lặp để điền vào lưới và nó cũng giống như bạn sử dụng để quyết định ranh giới là gì.
  • Nếu bạn bắt đầu với một lưới trống và chọn gạch để thay đổi, thì bạn có thể cập nhật ranh giới cục bộ.

Không sử dụng danh sách cho ranh giới. Sử dụng một bộ nếu bạn thực sự phải ( Tôi không biết bạn muốn ranh giới để làm gì. ). Tuy nhiên, nếu bạn tạo bất kỳ ô nào là ranh giới hoặc không phải là thuộc tính của ô đó thì bạn không phải chuyển đến cấu trúc dữ liệu khác để kiểm tra.


2

Di chuyển khu vực của bạn lên một ô, sau đó lên trên bên phải, sau đó xuống bên phải, vv .. Sau đó xóa khu vực ban đầu.

Hợp nhất tất cả sáu bộ nên là O (n), sắp xếp O (n.log (n)), đặt chênh lệch O (n). Nếu các ô gốc được lưu trữ ở một số định dạng được sắp xếp, thì bộ hợp nhất cũng có thể được sắp xếp theo O (n).

Tôi không nghĩ có một thuật toán có ít hơn O (n), vì bạn cần truy cập mỗi ô ít nhất một lần.


1

Tôi chỉ viết một bài blog về cách làm điều này. Điều này sử dụng phương pháp đầu tiên mà @DMGregory đã đề cập bắt đầu bằng một ô cạnh và diễu hành quanh chu vi. Nó ở C # thay vì Python nhưng khá dễ thích nghi.

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


0

BÀI VIẾT GỐC:

Tôi không thể nhận xét trên trang web này vì vậy tôi sẽ cố gắng trả lời bằng thuật toán mã giả.

Bạn biết mỗi lãnh thổ có nhiều nhất là sáu nước láng giềng là một phần của ranh giới. Đối với mỗi ô trong lãnh thổ, hãy thêm sáu ô lân cận vào danh sách viền tiềm năng. Sau đó trừ đi mọi ô trong lãnh thổ khỏi đường viền và bạn chỉ còn lại các ô viền. Nó sẽ hoạt động tốt nhất nếu bạn sử dụng một bộ không có thứ tự để lưu trữ từng danh sách. Hy vọng tôi đã có ích.

EDIT Có nhiều cách hiệu quả hơn nhiều so với việc lặp đơn giản. Khi tôi cố gắng nêu câu trả lời (hiện đã bị xóa) của mình bên dưới, bạn có thể đạt được O (1) trong trường hợp tốt nhất và O (n) trong trường hợp xấu nhất.

Thêm lát vào lãnh thổ O (1) - O (N):

Trong trường hợp không có hàng xóm, bạn chỉ cần tạo một lãnh thổ mới.

Trong trường hợp của một người hàng xóm, bạn thêm gạch mới vào lãnh thổ hiện có.

Trong trường hợp 5 hoặc 6 hàng xóm bạn biết tất cả đã được kết nối, vì vậy bạn thêm lát mới vào lãnh thổ hiện có. Đây là tất cả các hoạt động O (1) và cập nhật các lãnh thổ biên giới mới cũng là O (1), vì đây là một sự hợp nhất đơn giản của một tập hợp với một tập hợp khác.

Trong trường hợp 2, 3 hoặc 4 lãnh thổ lân cận, bạn có thể phải hợp nhất tối đa 3 lãnh thổ duy nhất. Đây là O (N) trên kích thước lãnh thổ kết hợp.

Xóa một lát khỏi lãnh thổ O (1) - O (N):

Không có hàng xóm xóa lãnh thổ. Ô (1)

Với một người hàng xóm gỡ gạch khỏi lãnh thổ. Ô (1)

Với hai hoặc nhiều hàng xóm, có thể tạo tối đa 3 lãnh thổ mới. Đây là O (N).

Tôi đã dành thời gian rảnh rỗi trong vài tuần qua để phát triển một chương trình trình diễn là một trò chơi lãnh thổ dựa trên hex đơn giản. Cố gắng tăng thu nhập của bạn bằng cách đặt các lãnh thổ cạnh nhau. 3 người chơi, Đỏ, Xanh lục và Xanh lam cạnh tranh để tạo ra nhiều doanh thu nhất bằng cách chiến lược đặt các ô trên một lĩnh vực trò chơi hạn chế.

Bạn có thể tải xuống trò chơi tại đây (ở định dạng .7z) hex.7z

Điều khiển chuột đơn giản LMB đặt một ô (chỉ có thể đặt ở nơi được tô sáng bằng di chuột). Điểm trên, thu nhập trên dưới. Xem nếu bạn có thể đưa ra một chiến lược hiệu quả.

Mã có thể được tìm thấy ở đây:

Đại bàng / EagleTest

Để xây dựng từ mã nguồn, bạn cần Eagle và Allegro 5. Cả hai đều xây dựng với cmake. Hiện tại trò chơi Hex được xây dựng với dự án CB.

Biến những downvote lộn ngược. :)


Đó thực chất là những gì thuật toán trong OP thực hiện, mặc dù việc kiểm tra các ô lân cận trước khi đưa vào nhanh hơn một chút so với loại bỏ tất cả chúng ở cuối.
ScienceSnake

Về cơ bản là giống nhau nhưng nếu bạn chỉ trừ chúng một lần thì hiệu quả hơn
BugSquasher

Tôi đã cập nhật đầy đủ câu trả lời của mình và xóa câu trả lời cực đoan bên dưới.
BugSquasher
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.