2009-12-17 38 views
9

Tôi có tối đa 10.000 điểm được định vị ngẫu nhiên trong không gian và tôi cần biết con trỏ nào gần nhất tại bất kỳ thời điểm nào. Để thêm một số bối cảnh, các điểm ở dạng bản vẽ vector, do đó người dùng có thể liên tục và nhanh chóng thêm và xóa và cũng có thể mất cân đối trên không gian canvas.Cấu trúc dữ liệu để lưu trữ hàng nghìn vectơ

Do đó, tôi đang cố gắng tìm cấu trúc dữ liệu hiệu quả nhất để lưu trữ và truy vấn các điểm này. Tôi muốn giữ ngôn ngữ câu hỏi này bất khả tri nếu có thể.

+2

Điều này có thể giúp: http://en.wikipedia.org/wiki/Nearest_neighbor_search – Aziz

+0

Xác nhận rằng người dùng chỉ có thể sửa đổi điểm cuối của đoạn đường và số điểm là không đổi tại n (ví dụ: "10000"). Hầu hết các thuật toán cấu trúc dữ liệu được thiết kế để cung cấp bảo đảm hiệu suất tiệm cận cho việc sử dụng * chung *. – alphazero

+0

Làm rõ, 10000 là số xấp xỉ để cho thấy rằng bản vẽ có thể lớn mà người dùng có thể thêm và xóa các dòng như mong muốn, tôi đang tìm cách tạo một chương trình vẽ vector đơn giản và hiệu suất là một cân nhắc chính. – Tom

Trả lời

5

Sau khi cập nhật cho câu hỏi

  1. Sử dụng hai Red-Black Tree hoặc Skip_list bản đồ. Cả hai cấu trúc dữ liệu tự cân bằng nhỏ gọn đều cho bạn thời gian O (log n) để tìm kiếm, chèn và xóa các hoạt động. Một bản đồ sẽ sử dụng toạ độ X cho mọi điểm dưới dạng khóa và điểm chính nó dưới dạng giá trị và giá trị kia sẽ sử dụng toạ độ Y làm khóa và điểm chính là giá trị.

  2. Như một sự cân nhắc, tôi đề xuất ban đầu hạn chế khu vực tìm kiếm xung quanh con trỏ bằng một hình vuông. Đối với kết hợp hoàn hảo, cạnh vuông phải bằng đường kính của "vòng tròn nhạy cảm" xung quanh con trỏ, tức là nếu bạn chỉ quan tâm đến một người hàng xóm gần nhất trong phạm vi bán kính 10 pixel từ con trỏ thì cạnh vuông phải là 20px. , nếu bạn là người hàng xóm gần nhất bất kể bạn ở gần bạn có thể thử tìm ranh giới động bằng cách đánh giá sàn và trần tương đối so với con trỏ.

  3. Sau đó truy xuất hai tập con của các điểm từ bản đồ nằm trong ranh giới, hợp nhất để chỉ bao gồm các điểm trong cả hai tập con.

  4. Lặp lại kết quả, tính khoảng cách đến mỗi điểm (dx^2 + dy^2, tránh căn bậc hai vì bạn không quan tâm đến khoảng cách thực, chỉ gần), tìm người hàng xóm gần nhất.

  5. Lấy ô vuông từ hình gần để đo khoảng cách đến hàng xóm gần nhất, xem nếu nó lớn hơn bán kính của “vòng tròn nhạy cảm”, nếu nó có nghĩa là không có điểm nào trong vòng tròn.

  6. Tôi khuyên bạn nên thực hiện một số tiêu chí chuẩn cho mọi cách tiếp cận; thật dễ dàng để vượt lên trên cùng với sự tối ưu hóa. Trên phần cứng khiêm tốn của tôi (Duo Core 2) tìm kiếm đơn luồng ngây thơ của một người hàng xóm gần nhất trong vòng 10 nghìn điểm lặp lại hàng nghìn lần mất 350 mili giây trong Java. Miễn là thời gian tái tạo giao diện người dùng tổng thể dưới 100 mili giây, nó sẽ có vẻ ngay lập tức đối với người dùng, hãy nhớ rằng ngay cả tìm kiếm ngây thơ có thể cung cấp cho bạn phản hồi đủ nhanh.

Generic Giải pháp

Cấu trúc dữ liệu hiệu quả nhất phụ thuộc vào các thuật toán bạn đang lập kế hoạch để sử dụng, buôn bán thời gian-không gian tắt và phân phối tương đối dự kiến ​​điểm:

  • Nếu không gian không phải là vấn đề, cách hiệu quả nhất có thể là tính toán trước hàng xóm gần nhất cho mỗi điểm trên màn hình và sau đó lưu trữ id hàng xóm gần nhất trong mảng hai chiều biểu diễn màn hình.
  • Nếu thời gian không phải là vấn đề lưu trữ 10K điểm trong một mảng 2D đơn giản và thực hiện tìm kiếm ngây thơ mỗi lần, tức là lặp qua từng điểm và tính khoảng cách có thể là tùy chọn dễ bảo trì và đơn giản.
  • Đối với một số thỏa hiệp giữa hai, đây là một bài thuyết trình tốt trên nhiều gần tùy chọn Neighbor Tìm kiếm có sẵn: http://dimacs.rutgers.edu/Workshops/MiningTutorial/pindyk-slides.ppt
  • Một loạt các tài liệu chi tiết tốt cho gần các thuật toán Neighbor kiếm khác nhau: http://simsearch.yury.name/tutorial.html, chỉ cần chọn một trong đó phù hợp với nhu cầu của bạn nhất.

Vì vậy, thật sự không thể đánh giá cấu trúc dữ liệu được tách biệt khỏi thuật toán mà khó có thể đánh giá mà không có ý tưởng tốt về ràng buộc và ưu tiên của tác vụ.

Mẫu Java thực hiện

import java.util.*; 
import java.util.concurrent.ConcurrentSkipListMap; 

class Test 
{ 

    public static void main (String[] args) 
    { 

     Drawing naive = new NaiveDrawing(); 
     Drawing skip = new SkipListDrawing(); 

     long start; 

     start = System.currentTimeMillis(); 
     testInsert(naive); 
     System.out.println("Naive insert: "+(System.currentTimeMillis() - start)+"ms"); 
     start = System.currentTimeMillis(); 
     testSearch(naive); 
     System.out.println("Naive search: "+(System.currentTimeMillis() - start)+"ms"); 


     start = System.currentTimeMillis(); 
     testInsert(skip); 
     System.out.println("Skip List insert: "+(System.currentTimeMillis() - start)+"ms"); 
     start = System.currentTimeMillis(); 
     testSearch(skip); 
     System.out.println("Skip List search: "+(System.currentTimeMillis() - start)+"ms"); 

    } 

    public static void testInsert(Drawing d) 
    { 
     Random r = new Random(); 
     for (int i=0;i<100000;i++) 
      d.addPoint(new Point(r.nextInt(4096),r.nextInt(2048))); 
    } 

    public static void testSearch(Drawing d) 
    { 
     Point cursor; 
     Random r = new Random(); 
     for (int i=0;i<1000;i++) 
     { 
      cursor = new Point(r.nextInt(4096),r.nextInt(2048)); 
      d.getNearestFrom(cursor,10); 
     } 
    } 


} 

// A simple point class 
class Point 
{ 
    public Point (int x, int y) 
    { 
     this.x = x; 
     this.y = y; 
    } 
    public final int x,y; 

    public String toString() 
    { 
     return "["+x+","+y+"]"; 
    } 
} 

// Interface will make the benchmarking easier 
interface Drawing 
{ 
    void addPoint (Point p); 
    Set<Point> getNearestFrom (Point source,int radius); 

} 


class SkipListDrawing implements Drawing 
{ 

    // Helper class to store an index of point by a single coordinate 
    // Unlike standard Map it's capable of storing several points against the same coordinate, i.e. 
    // [10,15] [10,40] [10,49] all can be stored against X-coordinate and retrieved later 
    // This is achieved by storing a list of points against the key, as opposed to storing just a point. 
    private class Index 
    { 
     final private NavigableMap<Integer,List<Point>> index = new ConcurrentSkipListMap <Integer,List<Point>>(); 

     void add (Point p,int indexKey) 
     { 
      List<Point> list = index.get(indexKey); 
      if (list==null) 
      { 
       list = new ArrayList<Point>(); 
       index.put(indexKey,list); 
      } 
      list.add(p); 
     } 

     HashSet<Point> get (int fromKey,int toKey) 
     { 
      final HashSet<Point> result = new HashSet<Point>(); 

      // Use NavigableMap.subMap to quickly retrieve all entries matching 
      // search boundaries, then flatten resulting lists of points into 
      // a single HashSet of points. 
      for (List<Point> s: index.subMap(fromKey,true,toKey,true).values()) 
       for (Point p: s) 
       result.add(p); 

      return result; 
     } 

    } 

    // Store each point index by it's X and Y coordinate in two separate indices 
    final private Index xIndex = new Index(); 
    final private Index yIndex = new Index(); 

    public void addPoint (Point p) 
    { 
     xIndex.add(p,p.x); 
     yIndex.add(p,p.y); 
    } 


    public Set<Point> getNearestFrom (Point origin,int radius) 
    { 


      final Set<Point> searchSpace; 
      // search space is going to contain only the points that are within 
      // "sensitivity square". First get all points where X coordinate 
      // is within the given range. 
      searchSpace = xIndex.get(origin.x-radius,origin.x+radius); 

      // Then get all points where Y is within the range, and store 
      // within searchSpace the intersection of two sets, i.e. only 
      // points where both X and Y are within the range. 
      searchSpace.retainAll(yIndex.get(origin.y-radius,origin.y+radius)); 


      // Loop through search space, calculate proximity to each point 
      // Don't take square root as it's expensive and really unneccessary 
      // at this stage. 
      // 
      // Keep track of nearest points list if there are several 
      // at the same distance. 
      int dist,dx,dy, minDist = Integer.MAX_VALUE; 

      Set<Point> nearest = new HashSet<Point>(); 

      for (Point p: searchSpace) 
      { 
      dx=p.x-origin.x; 
      dy=p.y-origin.y; 
      dist=dx*dx+dy*dy; 

      if (dist<minDist) 
      { 
        minDist=dist; 
        nearest.clear(); 
        nearest.add(p); 
      } 
      else if (dist==minDist) 
      { 
       nearest.add(p); 
      } 


      } 

      // Ok, now we have the list of nearest points, it might be empty. 
      // But let's check if they are still beyond the sensitivity radius: 
      // we search area we have evaluated was square with an side to 
      // the diameter of the actual circle. If points we've found are 
      // in the corners of the square area they might be outside the circle. 
      // Let's see what the distance is and if it greater than the radius 
      // then we don't have a single point within proximity boundaries. 
      if (Math.sqrt(minDist) > radius) nearest.clear(); 
      return nearest; 
    } 
} 

// Naive approach: just loop through every point and see if it's nearest. 
class NaiveDrawing implements Drawing 
{ 
    final private List<Point> points = new ArrayList<Point>(); 

    public void addPoint (Point p) 
    { 
     points.add(p); 
    } 

    public Set<Point> getNearestFrom (Point origin,int radius) 
    { 

      int prevDist = Integer.MAX_VALUE; 
      int dist; 

      Set<Point> nearest = Collections.emptySet(); 

      for (Point p: points) 
      { 
      int dx = p.x-origin.x; 
      int dy = p.y-origin.y; 

      dist = dx * dx + dy * dy; 
      if (dist < prevDist) 
      { 
        prevDist = dist; 
        nearest = new HashSet<Point>(); 
        nearest.add(p); 
      } 
      else if (dist==prevDist) nearest.add(p); 

      } 

      if (Math.sqrt(prevDist) > radius) nearest = Collections.emptySet(); 

      return nearest; 
    } 
} 
+0

sẽ không lặp qua việc kiểm tra mảng để xem liệu tọa độ nằm trong quảng trường nhạy cảm có gần như chuyên sâu như một khoảng cách calc không? bốn câu lệnh OR mỗi điểm? – Tom

+0

Tính toán khoảng cách bao gồm hai phép nhân, additon và căn bậc hai đắt nhất (bạn có thể tránh được nếu bạn bị tấn công chỉ ở mức độ gần gũi). So sánh có thể lên đến bốn AND cho mỗi điểm nhưng hầu hết thời gian bạn sẽ kết thúc với ít hơn (vì nếu lần đầu tiên thất bại phần còn lại sẽ không được đánh giá và như vậy). Bạn cũng có thể kết hợp phương pháp "nhạy cảm" này với một số loại chỉ mục cây tùy thuộc vào những gì cần phải được thực hiện thường xuyên hơn: trộn lại điểm hoặc kiểm tra khoảng cách. –

+0

Tôi sẽ cung cấp cho các danh sách bỏ qua một cách tiếp cận, phương pháp của bạn dường như rõ ràng để làm theo, nhờ – Tom

2

Các điểm có được phân phối đồng đều không?

Bạn có thể xây dựng một cây bốn chiều đến độ sâu nhất định, ví dụ, 8. Ở trên cùng, bạn có một nút cây chia màn hình thành bốn phần tư. Bảo quản ở mỗi nút:

  • Đỉnh trái và phía dưới bên phải phối hợp
  • Con trỏ tới bốn nút đứa trẻ, mà phân chia nút thành bốn phần

Xây dựng cây lên đến độ sâu 8 , và tại các nút lá, lưu trữ danh sách các điểm được liên kết với khu vực đó. Danh sách đó bạn có thể tìm kiếm tuyến tính.

Nếu bạn cần chi tiết hơn, hãy xây dựng cây tứ thành độ sâu lớn hơn.

+0

Điều này nghe giống như loại điều tôi đã nghĩ đến, các điểm không thống nhất phân phối tuy nhiên và kích thước vải cũng là biến .. không phải là điều này giảm giá phương pháp này. – Tom

5

Cấu trúc dữ liệu hiệu quả nhất sẽ là một kd-cây link text

+0

Ai đã từng bỏ phiếu này xuống ít nhất có thể đưa ra lý do. – DiggerMeUp

+1

Tôi tự hỏi tại sao điều này được bình chọn, khi OP viết: "để họ có thể được thay đổi liên tục và nhanh chóng bởi người dùng". KD-tree cân bằng sẽ nhanh chóng trở thành một cơn ác mộng. – MaR

+0

@MaR Tôi đồng ý có khả năng nhu cầu tái cân bằng có thể là một vấn đề.Tôi nghĩ rằng được rút gọn ở đây bởi vì: 1) Nếu vị trí điểm mới vẫn còn trong cùng một khu vực thì cây sẽ không cần thay đổi (mỗi nút sẽ chỉ cần lưu trữ điểm gốc và hiện tại). 2) Chỉ có một điểm được thay đổi tại một thời điểm vì vậy sẽ có một lần xóa và một lần chèn. 3) Cây sẽ chỉ cần cân bằng lại nếu bản vẽ vector đã được thay đổi thành một cái gì đó hoàn toàn khác và hiệu suất của tìm kiếm lân cận gần nhất bị suy giảm quá nhiều. 4) Ít hơn của một vấn đề trong 2d. Điều này sẽ cần thử nghiệm. – DiggerMeUp

1

Nó phụ thuộc vào tần số cập nhật và truy vấn. Đối với truy vấn nhanh, cập nhật chậm, một Quadtree (đó là một hình thức của jd-tree cho 2-D) có lẽ sẽ là tốt nhất. Quadtree rất tốt cho điểm không đồng nhất.

Nếu bạn có độ phân giải thấp, bạn có thể xem xét sử dụng mảng thô có chiều rộng x chiều cao của các giá trị được tính trước.

Nếu bạn có rất ít điểm hoặc cập nhật nhanh, một mảng đơn giản là đủ, hoặc có thể là một phân vùng đơn giản (đi về phía quadtree).

Vì vậy, câu trả lời phụ thuộc vào các thông số động lực của bạn. Ngoài ra tôi sẽ thêm rằng ngày nay algo không phải là tất cả; làm cho nó sử dụng nhiều bộ vi xử lý hoặc CUDA có thể cung cấp cho một tăng rất lớn.

6

Tôi muốn khuyên bạn nên tạo một Voronoi DiagramTrapezoidal Map (Về cơ bản giống nhau answer như ta đã ban cho this câu hỏi). Các Voronoi Diagram sẽ phân vùng không gian trong đa giác. Mỗi điểm sẽ có một đa giác mô tả tất cả các điểm gần nhất với nó. Bây giờ khi bạn nhận được một truy vấn của một điểm, bạn cần phải tìm trong đó đa giác nằm. Vấn đề này được gọi là Point Location và có thể được giải quyết bằng cách xây dựng một Trapezoidal Map.

Có thể tạo Voronoi Diagram bằng cách sử dụng Fortune's algorithm, tính các bước tính toán O (n log n) và chi phí không gian O (n). This website cho bạn biết cách tạo bản đồ hình thang và cách truy vấn bản đồ. Bạn cũng có thể tìm thấy một số giới hạn đó:

  • gian tạo dự kiến: O (n log n)
  • Dự kiến ​​không gian phức tạp: O (n) Nhưng
  • quan trọng nhất, truy vấn dự kiến ​​ thời gian: O (log n).
    (Đây là lý thuyết) tốt hơn O (√ n) của cây kD.)
  • Cập nhật sẽ là tuyến tính (O (n)).

Nguồn của tôi (ngoài các liên kết ở trên) là: Computational Geometry: algorithms and applications, chương sáu và bảy.

Ở đó bạn sẽ tìm thấy thông tin chi tiết về hai cấu trúc dữ liệu (bao gồm cả bằng chứng chi tiết). Phiên bản sách của Google chỉ có một phần của những gì bạn cần, nhưng các liên kết khác phải đủ cho mục đích của bạn. Chỉ cần mua cuốn sách nếu bạn quan tâm đến loại điều đó (đó là một cuốn sách hay).

+0

tôi đã thêm ngữ cảnh cho câu hỏi, các điểm lấy dạng bản vẽ vector, giải pháp này vẫn thích hợp? – Tom

+0

Tôi đã xóa nhận xét trước đó của mình và thêm thời gian cập nhật vào câu trả lời của tôi. Cập nhật cấu trúc dữ liệu sẽ mất thời gian O (n) tôi nghĩ. Tôi vẫn nghĩ rằng sẽ có thể chấp nhận được cho một phản ứng với tương tác của người dùng. –

+0

Có các thuật toán để cập nhật gia tăng các sơ đồ Voronoi chỉ mất thời gian O (log n) mỗi lần cập nhật http://www.springerlink.com/content/p8377h68j82l6860. –

0

Bạn chưa chỉ định kích thước của bạn, nhưng nếu đó là bản vẽ đường 2D thì một nhóm bitmap - một mảng 2D gồm danh sách các điểm trong vùng, nơi bạn quét các thùng tương ứng và gần con trỏ có thể thực hiện rất tốt. Hầu hết các hệ thống sẽ vui vẻ xử lý các nhóm bitmap của trật tự 100x100 đến 1000x1000, đầu nhỏ sẽ đặt giá trị trung bình của một điểm cho mỗi nhóm. Mặc dù hiệu suất tiệm cận là O (N), hiệu suất thế giới thực thường rất tốt. Di chuyển các điểm riêng lẻ giữa các nhóm có thể nhanh chóng; các đối tượng chuyển động xung quanh cũng có thể được thực hiện nhanh nếu bạn đặt các đối tượng vào các thùng thay vì các điểm (do đó, một đa giác 12 điểm sẽ được tham chiếu bởi 12 xô, di chuyển nó trở thành 12 lần chèn và loại bỏ chi phí của danh sách xô; lên xô là thời gian không đổi trong mảng 2D). Chi phí chính là tổ chức lại mọi thứ nếu kích thước canvas phát triển trong nhiều bước nhảy nhỏ.

0

Nếu nó ở dạng 2D, bạn có thể tạo lưới ảo bao phủ toàn bộ không gian (chiều rộng và chiều cao lên đến không gian điểm thực) và tìm tất cả các điểm 2D thuộc về mọi ô. Sau đó một ô sẽ là một xô trong một hashtable.

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