Tôi có thể lấy thuật toán tìm kiếm nhị phân C ++ “hữu ích” ở đâu?


106

Tôi cần một thuật toán tìm kiếm nhị phân tương thích với các vùng chứa C ++ STL, giống như std::binary_searchtrong tiêu <algorithm>đề của thư viện chuẩn , nhưng tôi cần nó để trả về trình lặp trỏ đến kết quả, chứ không phải một boolean đơn giản cho tôi biết liệu phần tử có tồn tại hay không.

(Một lưu ý nhỏ, ủy ban tiêu chuẩn đã nghĩ cái quái gì khi họ định nghĩa API cho binary_search ?!)

Mối quan tâm chính của tôi ở đây là tôi cần tốc độ của tìm kiếm nhị phân, vì vậy mặc dù tôi có thể tìm dữ liệu bằng các thuật toán khác, như đã đề cập bên dưới, tôi muốn tận dụng thực tế là dữ liệu của tôi được sắp xếp để có được lợi ích của một tệp nhị phân tìm kiếm, không phải là tìm kiếm tuyến tính.

cho đến nay lower_boundupper_boundkhông thành công nếu thiếu dữ liệu:

//lousy pseudo code
vector(1,2,3,4,6,7,8,9,0) //notice no 5
iter = lower_bound_or_upper_bound(start,end,5)
iter != 5 && iter !=end //not returning end as usual, instead it'll return 4 or 6

Lưu ý: Tôi cũng ổn khi sử dụng thuật toán không thuộc không gian tên std miễn là nó tương thích với vùng chứa. Như, nói boost::binary_search,.


2
Về bản chỉnh sửa: đó là lý do tại sao std :: equal_range là giải pháp. Nếu không, bạn sẽ phải kiểm tra đẳng thức (hoặc tương đương trở lên)
Luc Hermitte

Bạn phải kiểm tra sự bình đẳng sau khi sử dụng (dưới / trên) _bound (xem câu trả lời bên dưới).
Luc Touraille

Tài liệu hướng dẫn bên dưới và bên trên chỉ ra rằng phạm vi phải được sắp xếp và do đó chúng có thể được triển khai dưới dạng tìm kiếm nhị phân.
livingos

@vividos, xin lỗi! bạn chỉ tìm thấy phần tài liệu tôi cần biết về! Cảm ơn!
Robert Gould

Robert, các thuật toán low / upper_bound / equal_range không hoạt động với các dải ô không được sắp xếp. Bạn thật may mắn khi thấy chúng hoạt động với các mẫu phần tử bạn đã lấy.
Luc Hermitte

Câu trả lời:


97

Không có các hàm như vậy, nhưng bạn có thể viết một hàm đơn giản bằng cách sử dụng std::lower_bound, std::upper_boundhoặc std::equal_range.

Một triển khai đơn giản có thể là

template<class Iter, class T>
Iter binary_find(Iter begin, Iter end, T val)
{
    // Finds the lower bound in at most log(last - first) + 1 comparisons
    Iter i = std::lower_bound(begin, end, val);

    if (i != end && !(val < *i))
        return i; // found
    else
        return end; // not found
}

Một giải pháp khác sẽ là sử dụng a std::set, đảm bảo thứ tự của các phần tử và cung cấp một phương thức iterator find(T key)trả về một trình lặp cho mục đã cho. Tuy nhiên, các yêu cầu của bạn có thể không tương thích với việc sử dụng một tập hợp (ví dụ: nếu bạn cần lưu trữ cùng một phần tử nhiều lần).


vâng, cách này hoạt động và tôi cũng có một cách triển khai tương tự ngay bây giờ, tuy nhiên đó là một cách triển khai "ngây thơ", theo nghĩa là nó không tận dụng được bối cảnh của tình huống, trong trường hợp này là dữ liệu được sắp xếp.
Robert Gould

5
Tôi không thực sự hiểu nhận xét của bạn, vì Lower_bound chỉ có thể được sử dụng trên dữ liệu được sắp xếp. Độ phức tạp thấp hơn so với sử dụng find (xem chỉnh sửa).
Luc Touraille

4
Để bổ sung cho câu trả lời của Luc, hãy xem bài viết kinh điển của Matt Austern Tại sao bạn không nên sử dụng set và những gì bạn nên sử dụng thay thế (Báo cáo C ++ 12: 4, tháng 4 năm 2000) để hiểu tại sao tìm kiếm nhị phân với các vectơ được sắp xếp thường được ưu tiên hơn std :: set , là một vùng chứa liên kết dựa trên cây.
ZunTzu

16
Không sử dụng *i == val! Thay vì sử dụng !(val < *i). Lý do là lower_boundsử dụng <, không phải ==(nghĩa Tlà thậm chí không được yêu cầu để có thể so sánh bình đẳng). (Xem STL hiệu quả của Scott Meyers để có lời giải thích về sự khác biệt giữa bình đẳngtương đương .)
gx_

1
@ CanKavaklıoğlu Không có phần tử nào được đặt tại end. Các phạm vi trong thư viện chuẩn C ++ được biểu diễn bằng các khoảng nửa mở: trình lặp cuối "trỏ" sau phần tử cuối cùng. Như vậy, nó có thể được trả về bằng các thuật toán để chỉ ra rằng không tìm thấy giá trị nào.
Luc Touraille

9

Bạn nên xem qua std::equal_range. Nó sẽ trả về một cặp trình vòng lặp cho phạm vi của tất cả các kết quả.


Theo cplusplus.com/reference/algorithm/equal_range , chi phí của std :: equal_range cao gần gấp đôi so với std :: low_bound. Có vẻ như nó kết thúc một cuộc gọi tới std :: Lower_bound và một cuộc gọi tới std :: upper_bound. Nếu bạn biết dữ liệu của mình không có bản sao thì điều đó là quá mức cần thiết và std :: low_bound (như được minh họa trong câu trả lời trên cùng) là lựa chọn tốt nhất.
Bruce Dawson

@BruceDawson: cplusplus.com chỉ cung cấp một triển khai tham chiếu để chỉ định hành vi ; để triển khai thực tế, bạn có thể kiểm tra thư viện chuẩn yêu thích của mình. Ví dụ: trong llvm.org/svn/llvm-project/libcxx/trunk/include/algorithm, chúng ta có thể thấy rằng các lệnh gọi tới Lower_bound và upper_bound được thực hiện trên các khoảng thời gian riêng biệt (sau một số tìm kiếm nhị phân thủ công). Điều đó đang được nói, nó có khả năng đắt hơn, đặc biệt là trên các phạm vi có nhiều giá trị khớp.
Matthieu M.

6

Có một tập hợp chúng:

http://www.sgi.com/tech/stl/table_of_contents.html

Tìm kiếm:

Trên một ghi chú riêng:

Họ có thể nghĩ rằng việc tìm kiếm các vùng chứa có thể tạo ra nhiều hơn một kết quả. Nhưng trong những trường hợp kỳ lạ, nơi bạn chỉ cần kiểm tra sự tồn tại của một phiên bản được tối ưu hóa cũng sẽ rất hay.


3
binary_search không trả về trình lặp như tôi đã đề cập trước đó, đó là lý do tại sao tôi đang tìm kiếm một giải pháp thay thế.
Robert Gould

1
Vâng tôi biết. Nhưng nó phù hợp với tập hợp các thuật toán tìm kiếm nhị phân. Vì vậy, nó tốt cho những người khác biết về.
Martin York

8
binary_search giống như rất nhiều thứ khác trong STL, được đặt tên sai. Tôi ghét điều đó. Kiểm tra sự tồn tại không giống như tìm kiếm một thứ gì đó.
OregonGhost

2
Các hàm tìm kiếm nhị phân này không hữu ích trong trường hợp bạn muốn biết chỉ mục của phần tử bạn đang tìm kiếm. Tôi phải viết hàm đệ quy của riêng mình cho tác vụ này. Tôi hy vọng điều này, mẫu <class T> int bindary_search (const T & item), sẽ được thêm vào phiên bản tiếp theo của C ++.
Kemin Zhou

3

Nếu std :: low_bound quá thấp so với ý muốn của bạn, bạn có thể muốn kiểm tra boost :: container :: flat_multiset . Nó là sự thay thế thả vào cho std :: multiset được triển khai dưới dạng vectơ được sắp xếp bằng cách sử dụng tìm kiếm nhị phân.


1
Liên kết tốt; và cũng có liên kết tốt trong liên kết: lafstern.org/matt/col1.pdf , mô tả cách các tra cứu được triển khai với một vectơ được sắp xếp, thay vì đặt (mặc dù cả hai đều là log (N)), có các hằng số tỷ lệ tốt hơn đáng kể và ~ nhanh gấp đôi (nhược điểm là thời gian INSERTION lớn hơn).
Dan Nissenbaum

2

Cách triển khai ngắn nhất, tự hỏi tại sao nó không được đưa vào thư viện chuẩn:

template<class ForwardIt, class T, class Compare=std::less<>>
ForwardIt binary_find(ForwardIt first, ForwardIt last, const T& value, Compare comp={})
{
    // Note: BOTH type T and the type after ForwardIt is dereferenced 
    // must be implicitly convertible to BOTH Type1 and Type2, used in Compare. 
    // This is stricter than lower_bound requirement (see above)

    first = std::lower_bound(first, last, value, comp);
    return first != last && !comp(value, *first) ? first : last;
}

Từ https://en.cppreference.com/w/cpp/algorithm/lower_bound


Tôi có thể nghĩ đến hai lý do mà điều này không có trong thư viện chuẩn: Họ cho rằng nó dễ thực hiện, nhưng lý do chính có lẽ là nó có thể yêu cầu phiên bản đảo ngược của toán tử () () nếu giá trị không thể hoán đổi với * 1.
user877329

1

Kiểm tra chức năng này, qBinaryFind :

RandomAccessIterator qBinaryFind ( RandomAccessIterator begin, RandomAccessIterator end, const T & value )

Thực hiện tìm kiếm nhị phân của phạm vi [bắt đầu, kết thúc) và trả về vị trí xuất hiện của giá trị. Nếu không có giá trị nào xuất hiện, trả về kết thúc.

Các mục trong phạm vi [bắt đầu, kết thúc) phải được sắp xếp theo thứ tự tăng dần; xem qSort ().

Nếu có nhiều lần xuất hiện có cùng giá trị, bất kỳ lần nào trong số chúng đều có thể được trả về. Sử dụng qLowerBound () hoặc qUpperBound () nếu bạn cần kiểm soát tốt hơn.

Thí dụ:

QVector<int> vect;
 vect << 3 << 3 << 6 << 6 << 6 << 8;

 QVector<int>::iterator i =
         qBinaryFind(vect.begin(), vect.end(), 6);
 // i == vect.begin() + 2 (or 3 or 4)

Hàm được bao gồm trong <QtAlgorithms>tiêu đề là một phần của thư viện Qt .


1
Rất tiếc, thuật toán này không tương thích với các vùng chứa STL.
bartolo-otrit,


0
int BinarySearch(vector<int> array,int var)
{ 
    //array should be sorted in ascending order in this case  
    int start=0;
    int end=array.size()-1;
    while(start<=end){
        int mid=(start+end)/2;
        if(array[mid]==var){
            return mid;
        }
        else if(var<array[mid]){
            end=mid-1;
        }
        else{
            start=mid+1;
        }
    }
    return 0;
}

Ví dụ: Xét một mảng, A = [1,2,3,4,5,6,7,8,9] Giả sử bạn muốn tìm kiếm chỉ mục của 3 Ban đầu, start = 0 và end = 9-1 = 8 Bây giờ , kể từ đầu <= end; giữa = 4; (array [mid] là 5)! = 3 Bây giờ, 3 nằm ở bên trái của mid vì nó nhỏ hơn 5. Do đó, chúng ta chỉ tìm kiếm phần bên trái của mảng Do đó, bây giờ start = 0 và end = 3; mid = 2.Vì mảng [mid] == 3, do đó chúng tôi nhận được số chúng tôi đang tìm kiếm. Do đó, chúng tôi trả về chỉ số của nó bằng giá trị trung bình.


1
Thật tốt khi có mã, nhưng bạn có thể cải thiện câu trả lời bằng cách cung cấp giải thích ngắn gọn về cách nó hoạt động cho những người mới làm quen với ngôn ngữ này.
Taegost

Ai đó đã gắn cờ bài đăng của bạn là chất lượng thấp không chính xác . Một câu trả lời chỉ có mã không phải là chất lượng thấp . Nó có cố gắng trả lời câu hỏi không? Nếu không, hãy gắn cờ là 'không phải là câu trả lời' hoặc đề nghị xóa (nếu trong hàng đợi đánh giá). b) Có sai kỹ thuật không? Phản đối hoặc bình luận.
Wai Ha Lee,

0

Một giải pháp trả về vị trí bên trong phạm vi có thể như thế này, chỉ sử dụng các phép toán trên trình vòng lặp (nó sẽ hoạt động ngay cả khi trình lặp không số học):

template <class InputIterator, typename T>
size_t BinarySearchPos(InputIterator first, InputIterator last, const T& val)
{       
    const InputIterator beginIt = first;
    InputIterator element = first;
    size_t p = 0;
    size_t shift = 0;
    while((first <= last)) 
    {
        p = std::distance(beginIt, first);
        size_t u = std::distance(beginIt, last);
        size_t m = p + (u-p)/2;  // overflow safe (p+u)/2
        std::advance(element, m - shift);
        shift = m;
        if(*element == val) 
            return m; // value found at position  m
        if(val > *element)
            first = element++;
        else
            last  = element--;

    }
    // if you are here the value is not present in the list, 
    // however if there are the value should be at position u
    // (here p==u)
    return p;

}
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.