2011-09-03 50 views
8

Tôi đã có danh sách ~ 5000 điểm (được chỉ định là cặp kinh độ/vĩ độ) và tôi muốn tìm 5 điểm gần nhất đến điểm khác, được chỉ định bởi người dùng.Thuật toán cho điểm gần nhất

Có ai có thể đề xuất thuật toán hiệu quả để làm việc này không? Tôi đang thực hiện điều này trong Ruby, vì vậy nếu có một thư viện phù hợp thì đó sẽ là điều tốt để biết, nhưng tôi vẫn quan tâm đến thuật toán!

CẬP NHẬT: Một vài người đã hỏi thêm chi tiết cụ thể về sự cố. Vì vậy, ở đây đi:

  • 5000 điểm chủ yếu nằm trong cùng một thành phố. Có thể có một vài bên ngoài nó, nhưng nó an toàn để giả định rằng 99% trong số họ nằm trong bán kính 75km, và tất cả chúng nằm trong bán kính 200km.
  • Danh sách các điểm thay đổi hiếm khi. Vì lợi ích của đối số, giả sử nó được cập nhật một lần mỗi ngày và chúng tôi phải xử lý một vài nghìn yêu cầu trong thời gian đó.
+0

Nếu nó là vài điểm nó là ok để đi từng người một. – Andrey

+1

Bất kể bạn chọn thuật toán nào, bạn có thể tiết kiệm thời gian bằng cách so sánh khoảng cách bình phương thay vì khoảng cách thực tế. Không cần phải thực hiện các thao tác căn bậc hai nếu bạn không cần biết khoảng cách thực tế. –

Trả lời

3

Bạn có thể nhận được một ước tính trên nhanh trên giới hạn trên khoảng cách sử dụng khoảng cách Manhattan (được chia tỷ lệ cho vĩ độ), điều này sẽ đủ tốt để từ chối 99,9% ứng cử viên nếu họ không ở gần (EDIT: kể từ đó bạn hãy cho chúng tôi biết họ đang ở gần. Trong trường hợp đó, số liệu của bạn phải là khoảng cách bình phương, theo nhận xét của Lars H). Hãy xem xét việc này tương đương với việc từ chối bất cứ điều gì bên ngoài một hộp hình chữ nhật hình cầu (như một xấp xỉ với một vòng tròn bao quanh hộp). tôi không làm Ruby vì vậy đây là thuật toán với giả:

Hãy vĩ độ, kinh độ của điểm tham chiếu P (pa, po) của bạnđiểm X khác (xa, xo). Precompute ka, hệ số chia tỷ lệ cho khoảng cách theo chiều dọc: ka (= cos (pa in °)). (Đúng ra, ka = liên tục là một xấp xỉ tuyến tính trong vùng lân cận của P.)

Sau đó, ước lượng khoảng cách là: D(X,P) = ka*|xa-pa| + |xo-po| = ka*da + do

nơi | z | có nghĩa là abs (z). Tại tồi tệ nhất, điều này đánh giá quá cao khoảng cách thực sự bởi hệ số √2 (khi da == làm), do đó chúng tôi cho phép như sau:

Thực hiện tìm kiếm và giữ Dmin, khoảng cách nhỏ nhất thứ năm-Manhattan-distance- ước tính. Do đó bạn có thể từ chối trả trước tất cả các điểm mà D (X, P)> √2 * Dmin (vì chúng phải cách xa ít nhất √ ((ka * da) ² + do²) - điều đó sẽ loại bỏ 99,9% điểm). Giữ một danh sách tất cả các điểm ứng cử viên còn lại với D (X, P) < = √2 * Dmin. Cập nhật Dmin nếu bạn tìm thấy hàng đợi thứ tự ưu tiên nhỏ thứ năm D. hoặc danh sách (coord, D) là cấu trúc dữ liệu tốt. Lưu ý rằng chúng tôi không bao giờ tính khoảng cách Euclide, chúng tôi chỉ sử dụng phép nhân nhân và phép cộng.

(Xem xét tương tự này để quadtree trừ lọc ra tất cả mọi thứ ngoại trừ các khu vực mà bạn quan tâm chúng ta, do đó không cần phải tính toán khoảng cách chính xác trả trước hoặc xây dựng cấu trúc dữ liệu.)

Nó sẽ giúp đỡ nếu bạn cho chúng tôi biết sự lây lan dự kiến Trong tất cả các điểm gần, yếu tố in2 trong bộ ước lượng này sẽ quá thận trọng và đánh dấu mọi điểm là ứng cử viên, một ước tính khoảng cách dựa trên bảng tra cứu sẽ thích hợp hơn.)

Mã giả:

initialize Dmin with the fifth-smallest D from the first five points in list 
for point X in list: 
    if D(X,P) <= √2 * Dmin: 
     insert the tuple (X,D) in the priority-queue of candidates 
     if (Dmin>D): Dmin = D 
# after first pass, reject candidates with D > √2 * Dmin (use the final value of Dmin) 
# ... 
# then a second pass on candidates to find lowest 5 exact distances 
5

Bạn có thể đẩy nhanh tiến độ tìm kiếm bằng cách phân vùng không gian 2D với một quad-tree hoặc một kd-tree và sau đó một khi bạn đã đạt được một nút lá bạn so sánh khoảng cách còn lại từng cái một cho đến khi bạn tìm thấy những trận đấu gần nhất.

Xem thêm this blog post đề cập đến this other blog post cả hai đều thảo luận về các tìm kiếm lân cận gần nhất với cây kd trong Ruby.

+0

Nói chung - một ý tưởng hay, nhưng với 5000 điểm, bạn sẽ mất nhiều thời gian hơn để tạo cấu trúc dữ liệu hơn là tính toán tất cả khoảng cách có thể bằng tay. – Gleno

+0

tùy thuộc vào tần suất danh sách thay đổi ~ 5000 điểm –

2

Vì danh sách của bạn khá ngắn nên tôi khuyên bạn nên đánh giá cao sức mạnh vũ phu. Chỉ cần so sánh tất cả 5000 với điểm do người dùng chỉ định. Nó sẽ là O (n) và bạn sẽ được trả tiền.

Ngoài ra, một cây quad-tree hoặc Kd-tree là phương pháp thông thường để phân chia không gian. Nhưng trong trường hợp của bạn, bạn sẽ kết thúc làm một số tuyến tính chèn vào cây, và sau đó một số liên tục của tra cứu logarit ... một chút của một sự lãng phí, khi bạn có lẽ tốt hơn off chỉ làm một số tuyến tính của so sánh khoảng cách và được thực hiện với nó.

Bây giờ, nếu bạn muốn tìm N điểm gần nhất, bạn đang xem xét sắp xếp trên khoảng cách tính toán và lấy N đầu tiên, nhưng đó vẫn là O (n log n) ish.

EDIT: Cần lưu ý rằng việc xây dựng cây không gian trở nên đáng giá nếu bạn định sử dụng lại danh sách các điểm cho nhiều truy vấn.

0

Vì bạn có vài điểm, tôi khuyên bạn nên thực hiện tìm kiếm bạo lực, với hiệu quả cố gắng tất cả các điểm với nhau là hoạt động O(n^2), với n = 5000 hoặc khoảng 25/2 triệu lần lặp lại thuật toán và chỉ lưu trữ các kết quả có liên quan. Điều này sẽ có thời gian thực hiện phụ 100 ms trong C, vì vậy chúng tôi đang xem xét một hoặc hai giây nhiều nhất trong Ruby.

Khi người dùng chọn một điểm, bạn có thể sử dụng dữ liệu được lưu trữ của mình để cung cấp kết quả theo thời gian không đổi.

EDIT Tôi đọc lại câu hỏi của bạn và dường như người dùng cung cấp điểm cuối cùng của riêng mình. Trong trường hợp đó, nhanh hơn chỉ cần thực hiện tìm kiếm tuyến tính O(n) thông qua bộ của bạn mỗi khi người dùng cung cấp một điểm.

1

Thay vì sức mạnh vũ phu thuần túy, đối với 5000 nút, tôi sẽ tính khoảng cách x + y riêng lẻ cho mỗi nút, thay vì khoảng cách đường thẳng.

Khi bạn đã sắp xếp danh sách đó, ví dụ: x + y cho nút thứ 5 là 38, bạn có thể loại trừ bất kỳ nút nào có khoảng cách x hoặc y là> 38. Bằng cách này, bạn có thể loại bỏ nhiều nút mà không phải tính khoảng cách đường thẳng. Sau đó, sức mạnh vũ phu tính toán khoảng cách đường thẳng cho các nút còn lại.

1

Các thuật toán này không dễ giải thích, do đó tôi sẽ chỉ cung cấp cho bạn một số gợi ý đúng hướng. Bạn nên tìm Voronoi Diagrams. Với Sơ đồ Voronoi bạn có thể dễ dàng tính toán trước một đồ thị trong thời gian O (n^2 log n) và tìm kiếm điểm gần nhất trong thời gian O (log n).

Việc xử lý trước được thực hiện với công việc cron vào ban đêm và tìm kiếm vẫn hoạt động. Điều này tương ứng với đặc điểm kỹ thuật của bạn.

Bây giờ bạn có thể lưu các cặp k closests của mỗi 5000 điểm và sau đó bắt đầu từ điểm gần nhất từ ​​Sơ đồ Voronoi và tìm kiếm 4 điểm còn lại.

Nhưng được cảnh báo rằng các thuật toán này không dễ thực hiện.

Một tài liệu tham khảo tốt là:

  • de Berg: Computational Geometry Các thuật toán ứng dụng (2008) chương 7,1 và 7,2
0

nếu bạn cần phải lặp lại điều này nhiều lần, với các địa điểm người dùng nhập vào khác nhau , nhưng không muốn thực hiện một quad-tree (hoặc không thể tìm thấy một thư viện thực hiện) thì bạn có thể sử dụng một phương pháp tiếp cận băm (nhạy cảm) theo địa phương khá trực quan:

  • mất (x, y) cặp của bạn và tạo hai danh sách, một trong những (x, i) và là một trong (y, i) trong đó i là chỉ số của điểm
  • loại cả hai danh sách

sau đó , khi đưa ra một điểm (X, Y),

  • chia làm hai đoạn sắp xếp cho X và Y
  • mở rộng ra phía ngoài vào cả hai danh sách, tìm kiếm các chỉ số chung
  • cho chỉ số chung, tính toán khoảng cách chính xác
  • ngừng mở rộng khi sự khác biệt trong X và Y vượt quá khoảng cách chính xác của khoảng cách xa nhất của 5 điểm hiện tại.

tất cả các bạn đang làm là nói rằng một điểm lân cận phải có một x tương tự và một giá trị y tương tự ...

Các vấn đề liên quan