Thuật toán nhanh để tìm kiếm một mảng nổi đã được sắp xếp để tìm cặp phao đặt giá trị đầu vào


10

Tôi có một mảng các số float, được sắp xếp từ nhỏ nhất đến lớn nhất và cần có thể chọn ra số float gần nhất lớn hơn hoặc nhỏ hơn một giá trị đầu vào được truyền. Giá trị đầu vào này không nhất thiết phải là một giá trị trong mảng.

Một cách tiếp cận ngây thơ sẽ là thực hiện một tìm kiếm tuyến tính đơn giản thông qua mảng. Điều đó có thể trông như thế này:

void FindClosestFloatsInArray( float input, std::vector<float> array, 
                               float *min_out, float *max_out )
{
    assert( input >= array[0] && input < array[ array.size()-1 ] );
    for( int i = 1; i < array.size(); i++ )
    {
        if ( array[i] >= input )
        {
            *min = array[i-1];
            *max = array[i];
        }
    }
}

Nhưng rõ ràng là khi mảng trở nên lớn hơn, điều này sẽ trở nên chậm hơn và chậm hơn.

Có ai có ý tưởng về một thuật toán cho phép tôi tìm dữ liệu này tối ưu hơn không? Tôi đã chuyển sang tìm kiếm nhị phân, điều này đã cải thiện phần nào vấn đề, nhưng nó vẫn chậm hơn rất nhiều so với tôi muốn và vì tôi không thực sự tìm kiếm một giá trị cụ thể tồn tại trong mảng, nên nó không bao giờ có thể chấm dứt sớm.

Thông tin thêm: Các giá trị dấu phẩy động trong mảng không nhất thiết phải được phân phối đồng đều (nghĩa là mảng có thể bao gồm các giá trị "1.f, 2.f, 3.f, 4.f, 100.f, 1200.f , 1203.f, 1400.f ".

Tôi đang thực hiện thao tác này hàng trăm ngàn lần, nhưng tôi có thể thực hiện bất kỳ số lượng tiền xử lý nào trên mảng phao, nếu nó sẽ cải thiện thời gian tra cứu. Tôi hoàn toàn có thể thay đổi để sử dụng một cái gì đó ngoài một vectơ để lưu trữ chúng, nếu điều đó sẽ giúp ích.


Điều gì khiến bạn nghĩ rằng tìm kiếm nhị phân của bạn không thể kết thúc sớm? Chắc chắn bạn chỉ có thể kiểm tra các yếu tố tại i và i + 1 để xem liệu chúng có đóng khung giá trị đích và chấm dứt nếu chúng không?
Paul R

Thay phiên, tôi có thể kiểm tra các yếu tố tại i và i-1 để xem liệu chúng có đóng khung giá trị đích hay không. Tôi cũng cần kiểm tra xem 'i' đã> = Array.size () - 1 để tôi có thể tránh làm bài kiểm tra của mình hay không và liệu đó có phải là <= 0 hay không để tôi có thể tránh làm bài kiểm tra của mình ... thực sự rất nhiều điều kiện bổ sung để thực hiện ở mỗi bước, để kiểm tra sớm. Tôi tưởng tượng họ sẽ làm chậm thuật toán rất nhiều, mặc dù tôi sẽ thú nhận rằng tôi chưa thực sự hiểu được điều đó.
Trevor Powell

3
Không cần quá phức tạp - nếu mảng của bạn có kích thước N thì bạn chỉ cần xử lý nó như thể nó có kích thước N - 1. Bằng cách đó, luôn có một yếu tố hợp lệ tại i + 1. Bạn làm một tìm kiếm nhị phân trên phần tử N - 1 cho phần tử i nhỏ hơn giá trị đích của bạn, với phần tử i + 1 lớn hơn giá trị đích.
Paul R

Câu trả lời:


11

Mã trong câu hỏi (một tìm kiếm tuyến tính), như bạn chỉ ra một cách chính xác, sẽ bị chậm đối với các mảng float lớn. Về mặt kỹ thuật, đó là O (n) trong đó n là số lượng giá trị float trong mảng của bạn.

Nói chung, cách tốt nhất bạn có thể làm để tìm giá trị trong mảng được sắp xếp là tìm kiếm cây đệ quy thuộc loại nào đó (ví dụ: tìm kiếm nhị phân), trong trường hợp đó bạn có thể đạt được thời gian tra cứu O (log n) về số lượng phần tử trong mảng của bạn. O (log n) là nhiều hơn O (n) cho giá trị lớn của n.

Do đó, cách tiếp cận được đề xuất của tôi sẽ là một tìm kiếm nhị phân đơn giản của mảng , tức là:

  1. Đặt chỉ mục số nguyên tối thiểu / tối đa để bao trùm toàn bộ mảng float của bạn
  2. kiểm tra giá trị ở giữa phạm vi ở chỉ số mid = (min + max / 2) so với giá trị tìm kiếm x
  3. nếu x thấp hơn giá trị này, đặt max thành mid, khác đặt min thành mid
  4. lặp lại (2-4) cho đến khi bạn tìm thấy giá trị chính xác

Đây là thuật toán O (log n) đủ nhanh cho hầu hết các tình huống. Theo trực giác, nó hoạt động bằng cách giảm một nửa phạm vi được tìm kiếm ở mỗi bước cho đến khi bạn tìm thấy giá trị chính xác.

Thật sự rất khó để tìm kiếm nhị phân đơn giản, vì vậy nếu bạn đã thực hiện điều này một cách chính xác thì bạn có thể đã khá gần với tối ưu rồi. Tuy nhiên, nếu bạn biết phân phối dữ liệu và / hoặc có phạm vi giá trị tra cứu giới hạn (x), vẫn còn một số thủ thuật nâng cao khác mà bạn có thể thử:

  • Bucketing - tạo các thùng (ví dụ: cho mỗi khoảng giữa hai số nguyên), mỗi số chứa một danh sách được sắp xếp nhỏ hơn của các giá trị float giữa hai số nguyên giới hạn cộng với hai giá trị ngay bên dưới và ngay trên mỗi phạm vi. Sau đó, bạn có thể bắt đầu tìm kiếm của mình tại (trunc (x) +0.5). Điều này sẽ giúp bạn tăng tốc tốt nếu bạn chọn các thùng có kích thước phù hợp (nó thực sự làm tăng hệ số phân nhánh của cây .....). Nếu số nguyên không phù hợp với bạn, thì bạn có thể thử các nhóm có độ chính xác điểm cố định khác (ví dụ: bội số của 1/16).
  • Ánh xạ bit - nếu phạm vi của các giá trị tra cứu có thể đủ nhỏ, bạn có thể thử tạo một bảng tra cứu lớn được lập chỉ mục theo giá trị bitwise của x. Đây sẽ là O (1) nhưng bạn có thể cần rất nhiều bộ nhớ sẽ rất không thân thiện trên bộ đệm của bạn ... vì vậy hãy thận trọng khi sử dụng. Điều này đặc biệt khó chịu vì bạn đang tìm kiếm các giá trị float, do đó bạn có thể cần vài GB để tính tất cả các bit ít quan trọng hơn ......
  • Làm tròn và băm - bảng băm có thể không phải là cấu trúc dữ liệu tốt nhất cho vấn đề này, nhưng nếu bạn có thể sống sót khi mất một chút độ chính xác thì chúng có thể hoạt động - chỉ cần làm tròn các bit thấp nhất của giá trị tra cứu của bạn và sử dụng hàm băm để trực tiếp tra cứu đúng giá trị. Bạn sẽ phải thử nghiệm sự đánh đổi đúng đắn giữa kích thước và độ chính xác của hàm băm và cũng đảm bảo rằng tất cả các giá trị băm có thể được đưa vào để điều này có thể hơi khó khăn ......
  • Cân bằng cây - cây lý tưởng của bạn nên có 50% cơ hội đi sang trái hoặc phải. Vì vậy, nếu bạn tạo một cây dựa trên sự phân phối các giá trị tra cứu (x), thì bạn có thể tối ưu hóa cây để đưa ra câu trả lời với số lượng thử nghiệm tối thiểu. Đây có thể là một giải pháp tốt nếu nhiều giá trị trong mảng float của bạn rất gần nhau, vì nó sẽ cho phép bạn tránh tìm kiếm các nhánh này quá thường xuyên.
  • Cây crit-bit - đây vẫn là cây (vì vậy vẫn là O (log n) ...) nhưng một số trường hợp: tuy nhiên bạn cần chuyển đổi phao của bạn thành một số định dạng điểm cố định để làm cho phép so sánh hoạt động

Tuy nhiên, trừ khi bạn ở trong một tình huống rất đặc biệt, có lẽ tôi khuyên bạn nên gắn bó với tìm kiếm nhị phân đơn giản. Lý do:

  • nó dễ thực hiện hơn nhiều
  • nó rất nhanh đối với hầu hết các trường hợp phổ biến
  • chi phí hoạt động thêm của các phương pháp phức tạp hơn (ví dụ như sử dụng bộ nhớ / áp suất bộ nhớ cache cao hơn) thường vượt xa mức tăng lý thuyết nhỏ
  • nó sẽ mạnh mẽ hơn đối với những thay đổi trong tương lai trong phân phối dữ liệu ....

1

Điều này có vẻ đơn giản:

Thực hiện tìm kiếm nhị phân cho float mà bạn muốn ràng buộc - thời gian O (log n).

Sau đó, phần tử ở bên trái của nó là giới hạn dưới và phần tử ở bên phải của nó là giới hạn trên.


0

Câu trả lời rõ ràng là lưu trữ phao trong cây . Hỗ trợ các hoạt động 'trước' và 'tiếp theo' là chuyện nhỏ trong một cái cây. Vì vậy, chỉ cần thực hiện 'tiếp theo' trên giá trị của bạn và sau đó thực hiện 'trước' trên giá trị bạn tìm thấy trong bước đầu tiên.


1
Điều này về cơ bản giống như một tìm kiếm nhị phân.
kevin cline

-1

Bài viết này ("tìm kiếm sublogarithmic mà không cần nhân") có thể được quan tâm; nó thậm chí còn chứa một số mã nguồn. Đối với mục đích so sánh, bạn có thể coi số float là một số nguyên có cùng mẫu bit; đây là một trong những mục tiêu thiết kế của tiêu chuẩn điểm nổi IEEE.

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.