ฉันจะหาอัลกอริธึมการค้นหาไบนารี C ++ ที่“ มีประโยชน์” ได้จากที่ใด


110

ฉันต้องการอัลกอริทึมการค้นหาแบบไบนารีที่เข้ากันได้กับคอนเทนเนอร์ C ++ STL บางอย่างเช่นstd::binary_searchใน<algorithm>ส่วนหัวของไลบรารีมาตรฐานแต่ฉันต้องการให้มันส่งคืนตัววนซ้ำที่ชี้ไปที่ผลลัพธ์ไม่ใช่บูลีนธรรมดาที่บอกฉันว่ามีองค์ประกอบอยู่หรือไม่

(โปรดทราบว่าคณะกรรมการมาตรฐานคิดอะไรอยู่เมื่อพวกเขากำหนด API สำหรับ binary_search!)

ข้อกังวลหลักของฉันที่นี่คือฉันต้องการความเร็วของการค้นหาแบบไบนารีดังนั้นแม้ว่าฉันจะสามารถค้นหาข้อมูลด้วยอัลกอริทึมอื่น ๆ ดังที่ได้กล่าวไว้ด้านล่าง แต่ฉันต้องการใช้ประโยชน์จากข้อเท็จจริงที่ว่าข้อมูลของฉันถูกจัดเรียงเพื่อให้ได้ประโยชน์จากไบนารี ค้นหาไม่ใช่การค้นหาเชิงเส้น

จนถึงตอนนี้lower_boundและupper_boundล้มเหลวหากไม่มีข้อมูล:

//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

หมายเหตุ:ฉันยังใช้อัลกอริทึมที่ไม่ได้เป็นของเนมสเปซมาตรฐานตราบใดที่เข้ากันได้กับคอนเทนเนอร์ ชอบพูดboost::binary_search.


2
เกี่ยวกับการแก้ไข: นั่นคือเหตุผลว่าทำไม std :: equal_range จึงเป็นทางออก มิฉะนั้นคุณจะต้องทดสอบความเท่าเทียมกัน (หรือความเท่าเทียมกันเพื่อให้มากกว่านี้)
Luc Hermitte

คุณต้องทดสอบความเท่าเทียมกันหลังจากใช้ (ล่าง / บน) _bound (ดูคำตอบด้านล่าง)
Luc Touraille

เอกสารคู่มือ lower_bound และ upper_bound ระบุว่าต้องจัดเรียงช่วงและด้วยเหตุนี้จึงสามารถนำไปใช้เป็นการค้นหาแบบไบนารีได้
vividos

@vividos, ไชโย! คุณพบเพียงชิ้นส่วนของเอกสารที่ฉันต้องการทราบ! ขอบคุณ!
Robert Gould

Robert อัลกอริทึมล่าง / upper_bound / equal_range ไม่ทำงานกับช่วงที่ไม่เรียงลำดับ คุณโชคดีที่ได้เห็นพวกเขาทำงานกับตัวอย่างองค์ประกอบที่คุณหยิบมา
Luc Hermitte

คำตอบ:


98

ไม่มีฟังก์ชั่นดังกล่าว แต่คุณสามารถเขียนง่ายๆโดยใช้std::lower_bound, หรือstd::upper_boundstd::equal_range

การใช้งานง่ายๆอาจเป็นได้

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
}

อีกวิธีหนึ่งคือการใช้ a std::setซึ่งรับประกันการเรียงลำดับขององค์ประกอบและให้วิธีการiterator find(T key)ที่ส่งคืนตัววนซ้ำไปยังรายการที่กำหนด อย่างไรก็ตามข้อกำหนดของคุณอาจใช้ไม่ได้กับการใช้ชุด (ตัวอย่างเช่นหากคุณต้องการจัดเก็บองค์ประกอบเดียวกันหลายครั้ง)


ใช่มันใช้งานได้และตอนนี้ฉันมีการใช้งานที่คล้ายกัน แต่มันเป็นการใช้งานที่ "ไร้เดียงสา" ในแง่ที่ว่ามันไม่ได้ใช้ประโยชน์จากบริบทของสถานการณ์ในกรณีนี้ข้อมูลที่จัดเรียง
Robert Gould

5
ฉันไม่เข้าใจความคิดเห็นของคุณมากนักเนื่องจาก lower_bound สามารถใช้ได้กับข้อมูลที่จัดเรียงเท่านั้น ความซับซ้อนต่ำกว่าการใช้ find (ดูแก้ไข)
Luc Touraille

4
เพื่อเติมเต็มคำตอบของ Luc ให้ตรวจสอบบทความคลาสสิกของ Matt Austern ว่าทำไมคุณไม่ควรใช้ set และสิ่งที่คุณควรใช้แทน (C ++ Report 12: 4, เมษายน 2000) เพื่อทำความเข้าใจว่าเหตุใดการค้นหาไบนารีที่มีเวกเตอร์ที่เรียงลำดับจึงควรใช้ std :: set ซึ่งเป็นคอนเทนเนอร์ที่เชื่อมโยงกับต้นไม้
ZunTzu

16
อย่าใช้*i == val! ค่อนข้างใช้!(val < *i). เหตุผลก็คือการlower_boundใช้<ไม่ใช่==(กล่าวTคือไม่จำเป็นต้องเทียบเคียงความเท่าเทียมกันด้วยซ้ำ) (ดูสกอตต์เมเยอร์สSTL ที่มีประสิทธิภาพสำหรับคำอธิบายความแตกต่างระหว่างที่ความเสมอภาคและเท่าเทียมกัน .)
gx_

1
@ CanKavaklıoğluไม่มีองค์ประกอบใดตั้งอยู่ที่end. ช่วงที่อยู่ในห้องสมุด c ++ มาตรฐานเป็นตัวแทนกับช่วงเวลาครึ่งเปิด: ปลาย iterator "จุด" หลังจากองค์ประกอบสุดท้าย ด้วยเหตุนี้อัลกอริทึมจึงสามารถส่งคืนเพื่อระบุว่าไม่พบค่า
Luc Touraille

9

คุณควรดูที่std::equal_range. มันจะส่งคืนคู่ของตัวทำซ้ำไปยังช่วงของผลลัพธ์ทั้งหมด


อ้างอิงจากcplusplus.com/reference/algorithm/equal_rangeต้นทุนของ std :: equal_range จะสูงกว่า std :: lower_bound ประมาณสองเท่า ดูเหมือนว่าจะตัดการเรียกไปที่ std :: lower_bound และการเรียก std :: upper_bound หากคุณรู้ว่าข้อมูลของคุณไม่มีข้อมูลซ้ำแสดงว่า overkill และ std :: lower_bound (ดังที่แสดงในคำตอบด้านบน) เป็นตัวเลือกที่ดีที่สุด
Bruce Dawson

@BruceDawson: cplusplus.com ให้การอ้างอิงเพื่อระบุพฤติกรรมเท่านั้น สำหรับการใช้งานจริงคุณสามารถตรวจสอบไลบรารีมาตรฐานที่คุณชื่นชอบได้ ตัวอย่างเช่นในllvm.org/svn/llvm-project/libcxx/trunk/include/algorithmเราจะเห็นว่าการเรียกไปที่ lower_bound และ upper_bound นั้นเกิดขึ้นในช่วงเวลาที่ไม่ปะติดปะต่อกัน (หลังจากการค้นหาไบนารีด้วยตนเอง) ดังที่กล่าวมามีแนวโน้มที่จะแพงกว่าโดยเฉพาะในช่วงที่มีค่าหลายค่าตรงกัน
Matthieu M.

6

มีชุดของพวกเขา:

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

ค้นหา:

ในหมายเหตุแยกต่างหาก:

พวกเขาอาจคิดว่าการค้นหาคอนเทนเนอร์สามารถเรียกผลลัพธ์ได้มากกว่าหนึ่งรายการ แต่ในโอกาสแปลก ๆ ที่คุณต้องทดสอบการมีอยู่ของเวอร์ชันที่ปรับให้เหมาะสมก็จะดีเช่นกัน


3
binary_search ไม่ส่งคืนตัววนซ้ำตามที่ฉันได้กล่าวไว้ก่อนหน้านี้นั่นคือเหตุผลที่ฉันกำลังมองหาทางเลือก
Robert Gould

1
ใช่ฉันรู้. แต่พอดีกับชุดของอัลกอริทึมการค้นหาแบบไบนารี ดังนั้นจึงเป็นเรื่องดีสำหรับคนอื่นที่รู้
Martin York

8
binary_search ก็เหมือนกับสิ่งอื่น ๆ ใน STL ที่ตั้งชื่อผิด ฉันเกลียดสิ่งนั้น การทดสอบการดำรงอยู่ไม่เหมือนกับการค้นหาบางสิ่ง
OregonGhost

2
ฟังก์ชันการค้นหาแบบไบนารีเหล่านี้ไม่มีประโยชน์ในกรณีที่คุณต้องการทราบดัชนีขององค์ประกอบที่คุณกำลังมองหา ฉันต้องเขียนฟังก์ชันเรียกซ้ำของตัวเองสำหรับงานนี้ ฉันหวังว่านี่ควรเพิ่มเทมเพลต <class T> int bindary_search (const T & item) ใน C ++ เวอร์ชันถัดไป
Kemin Zhou

3

หากมาตรฐาน :: lower_bound เกินไปในระดับต่ำสำหรับความชอบของคุณคุณอาจต้องการตรวจสอบเพิ่ม :: ภาชนะ :: flat_multiset เป็นการแทนที่แบบดรอปอินสำหรับ std :: multiset ที่ใช้เป็นเวกเตอร์ที่เรียงลำดับโดยใช้การค้นหาแบบไบนารี


1
ลิงค์ที่ดี; และลิงค์ที่ดีในลิงค์: lafstern.org/matt/col1.pdfซึ่งอธิบายถึงวิธีการใช้การค้นหาด้วยเวกเตอร์ที่เรียงลำดับแทนที่จะตั้งค่า (แม้ว่าทั้งสองจะเป็นบันทึก (N)) มีค่าคงที่ของสัดส่วนที่ดีกว่าอย่างมีนัยสำคัญและเป็น ~ เร็วขึ้นสองเท่า (ข้อเสียคือเวลา INSERTION ที่ใหญ่กว่า)
Dan Nissenbaum

2

การใช้งานที่สั้นที่สุดสงสัยว่าทำไมจึงไม่รวมอยู่ในไลบรารีมาตรฐาน:

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

จากhttps://en.cppreference.com/w/cpp/algorithm/lower_bound


ฉันนึกถึงเหตุผลสองประการที่ไม่ได้อยู่ในไลบรารีมาตรฐาน: พวกเขาคิดว่าง่ายต่อการนำไปใช้ แต่สาเหตุหลักคืออาจต้องใช้ตัวดำเนินการเวอร์ชันย้อนกลับ () () หากค่าไม่สามารถใช้แทนกันได้กับ * ก่อน
user877329

1

ตรวจสอบฟังก์ชันนี้qBinaryFind :

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

ทำการค้นหาแบบไบนารีของช่วง [เริ่มต้นสิ้นสุด) และส่งกลับตำแหน่งของค่าที่เกิดขึ้น หากไม่มีค่าเกิดขึ้นให้คืนค่าสิ้นสุด

รายการในช่วง [เริ่มต้นสิ้นสุด) ต้องเรียงลำดับจากน้อยไปมาก ดู qSort ()

หากมีการเกิดขึ้นหลายครั้งที่มีค่าเดียวกันอาจส่งคืนรายการใดรายการหนึ่ง ใช้ qLowerBound () หรือ qUpperBound () หากคุณต้องการการควบคุมที่ละเอียดกว่านี้

ตัวอย่าง:

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)

ฟังก์ชันนี้รวมอยู่ใน<QtAlgorithms>ส่วนหัวซึ่งเป็นส่วนหนึ่งของไลบรารีQt


1
น่าเสียดายที่อัลกอริทึมนี้เข้ากันไม่ได้กับคอนเทนเนอร์ 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;
}

ตัวอย่าง: พิจารณาอาร์เรย์ A = [1,2,3,4,5,6,7,8,9] สมมติว่าคุณต้องการค้นหาดัชนีของ 3 ในขั้นต้น start = 0 และ end = 9-1 = 8 ตอนนี้ ตั้งแต่เริ่ม <= end; กลาง = 4; (array [mid] ซึ่งเป็น 5)! = 3 ตอนนี้ 3 อยู่ทางซ้ายของ mid เนื่องจากมีขนาดเล็กกว่า 5 ดังนั้นเราจึงค้นหาเฉพาะส่วนด้านซ้ายของอาร์เรย์ดังนั้นตอนนี้ start = 0 และ end = 3; mid = 2 เนื่องจาก array [mid] == 3 ดังนั้นเราจึงได้หมายเลขที่เรากำลังค้นหา ดังนั้นเราจึงคืนค่าดัชนีซึ่งเท่ากับค่ากลาง


1
เป็นเรื่องดีที่จะมีรหัส แต่คุณสามารถปรับปรุงคำตอบได้โดยให้คำอธิบายสั้น ๆ เกี่ยวกับวิธีการทำงานสำหรับผู้ที่เพิ่งเริ่มใช้ภาษา
Taegost

ใครบางคนที่ไม่ถูกต้องถูกตั้งค่าสถานะโพสต์ของคุณเป็นที่มีคุณภาพต่ำ คำตอบรหัสเท่านั้นไม่ได้มีคุณภาพต่ำ มันพยายามตอบคำถามหรือไม่? ถ้าไม่ใช่ให้ตั้งค่าสถานะเป็น 'ไม่ใช่คำตอบ' หรือแนะนำให้ลบ (หากอยู่ในคิวการตรวจสอบ) b) ผิดทางเทคนิคหรือไม่? โหวตลงหรือแสดงความคิดเห็น
Wai Ha Lee

0

วิธีแก้ปัญหาที่ส่งคืนตำแหน่งภายในช่วงอาจเป็นเช่นนี้โดยใช้การดำเนินการกับตัววนซ้ำเท่านั้น (ควรใช้งานได้แม้ว่าตัววนซ้ำจะไม่ใช่เลขคณิต)

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;

}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.