อัลกอริทึมอย่างรวดเร็วเพื่อค้นหาอาร์เรย์เรียงลำดับของการลอยเพื่อหาคู่ของการลอยคร่อมค่าการป้อนข้อมูล


10

ฉันมีอาร์เรย์ของลอยเรียงจากที่เล็กที่สุดไปหามากที่สุดและต้องสามารถเลือกลอยที่ใกล้ที่สุดที่มากกว่าหรือน้อยกว่าค่าอินพุตที่ผ่าน ค่าอินพุตนี้ไม่จำเป็นต้องแสดงเป็นค่าในอาร์เรย์

แนวทางที่ไร้เดียงสาคือการค้นหาเชิงเส้นอย่างง่ายผ่านอาร์เรย์ ที่อาจมีลักษณะเช่นนี้:

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

แต่เห็นได้ชัดว่าเมื่ออาร์เรย์มีขนาดใหญ่ขึ้นสิ่งนี้จะช้าลงเรื่อย ๆ

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

ข้อมูลเพิ่มเติม: ค่าทศนิยมในอาร์เรย์ไม่จำเป็นต้องกระจายอย่างสม่ำเสมอ (นั่นคืออาร์เรย์อาจประกอบด้วยค่า "1.f, 2.f, 3.f, 4.f, 100.f, 1200.f , 1203.f, 1400.f "

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


อะไรทำให้คุณคิดว่าการค้นหาไบนารีของคุณไม่สามารถยุติได้เร็ว แน่นอนคุณสามารถทดสอบองค์ประกอบที่ i และ i + 1 เพื่อดูว่าพวกเขายึดค่าเป้าหมายและยุติถ้าพวกเขา?
พอล R

อีกวิธีหนึ่งคือฉันสามารถทดสอบองค์ประกอบที่ i และ i-1 เพื่อดูว่าพวกเขายึดค่าเป้าหมาย ฉันจะต้องทดสอบด้วยว่า 'i' เป็น> = array.size () - 1 หรือไม่ดังนั้นฉันจึงสามารถหลีกเลี่ยงการทดสอบของคุณได้หรือไม่และเป็น <= 0 หรือไม่ดังนั้นฉันจึงสามารถหลีกเลี่ยงการทดสอบของฉันได้ ... เงื่อนไขเพิ่มเติมเพื่อดำเนินการในแต่ละขั้นตอนเพื่อตรวจสอบก่อนออก ฉันคิดว่าพวกเขาจะทำให้อัลกอริทึมช้าลงมาก แต่ฉันจะยอมรับว่าฉันยังไม่ได้ทำโปรไฟล์นั้น
เทรเวอร์พาวเวล

3
ไม่จำเป็นต้องมีความซับซ้อนมาก - ถ้าอาร์เรย์ของคุณมีขนาด N คุณต้องปฏิบัติต่อมันราวกับว่ามันมีขนาด N - 1 ด้วยวิธีนี้มีองค์ประกอบที่ถูกต้องเสมอที่ i + 1 คุณทำ การค้นหาแบบไบนารีเหนือองค์ประกอบ N - 1 สำหรับองค์ประกอบ i ซึ่งน้อยกว่าค่าเป้าหมายของคุณโดยองค์ประกอบ i + 1 จะยิ่งใหญ่กว่ามูลค่าเป้าหมาย
พอล R

คำตอบ:


11

รหัสในคำถาม (การค้นหาเชิงเส้น) ตามที่คุณชี้ให้เห็นอย่างถูกต้องจะช้าลงสำหรับอาร์เรย์ขนาดใหญ่ ในทางเทคนิคแล้วมันคือ O (n) โดยที่ n คือจำนวนค่าทศนิยมในอาร์เรย์ของคุณ

โดยทั่วไปสิ่งที่ดีที่สุดที่คุณสามารถทำได้เพื่อหาค่าในอาเรย์ที่สั่งซื้อคือการค้นหาทรีแบบเรียกซ้ำบางชนิด (เช่นการค้นหาแบบไบนารี่) ซึ่งในกรณีนี้คุณสามารถใช้เวลาค้นหา O (log n) ในจำนวนองค์ประกอบ ในอาร์เรย์ของคุณ O (log n) เป็นมากดีกว่า O (n) สำหรับค่ามาก n

วิธีการที่แนะนำของฉันจะเป็นการค้นหาแบบไบนารีที่ง่ายของอาร์เรย์คือ:

  1. ตั้งค่าดัชนีจำนวนเต็มต่ำสุด / สูงสุดเพื่อให้ครอบคลุมอาร์เรย์โฟลตทั้งหมดของคุณ
  2. ทดสอบค่าที่อยู่ตรงกลางของช่วงที่ index mid = (min + max / 2) เทียบกับค่าการค้นหา x
  3. ถ้า x ต่ำกว่าค่านี้ให้ตั้งค่าสูงสุดเป็นกลางและตั้งค่าอื่นเป็นต่ำสุด
  4. ทำซ้ำ (2-4) จนกว่าคุณจะพบค่าที่ถูกต้อง

นี่เป็นอัลกอริทึม O (log n) ซึ่งควรเร็วพอสำหรับเกือบทุกสถานการณ์ โดยสังเขปมันทำงานโดยลดช่วงที่จะค้นหาในแต่ละขั้นตอนจนกว่าคุณจะพบค่าที่ถูกต้อง

เป็นการยากมากที่จะค้นหาสัตว์ไบนารีแบบง่าย ๆ ดังนั้นหากคุณใช้งานอย่างถูกต้องแล้วคุณอาจจะใกล้เคียงกับความเหมาะสมแล้ว อย่างไรก็ตามหากคุณรู้ว่าการแจกแจงของข้อมูลและ / หรือมีช่วงของค่าการค้นหาที่ จำกัด (x) ยังคงมีเทคนิคขั้นสูงอื่น ๆ อีกมากมายที่คุณสามารถลอง:

  • Bucketing - สร้างที่เก็บข้อมูล (เช่นสำหรับแต่ละช่วงเวลาระหว่างจำนวนเต็มสองจำนวน) แต่ละอันมีรายการที่เรียงลำดับขนาดเล็กกว่าของค่าลอยระหว่างจำนวนเต็มสองจำนวนที่มีขอบเขตสองบวกสองค่าทันทีด้านล่างและเหนือแต่ละช่วงทันที จากนั้นคุณสามารถเริ่มการค้นหาที่ (trunc (x) +0.5) สิ่งนี้จะช่วยให้คุณเร่งความเร็วได้ดีถ้าคุณเลือกที่เก็บถังขนาดใหญ่ (มันเพิ่มประสิทธิภาพในการแยกกิ่งไม้ของต้นไม้ ..... ) หากจำนวนเต็มไม่ทำงานสำหรับคุณคุณสามารถลองใช้ถังที่มีความแม่นยำจุดคงที่อื่น ๆ (เช่นทวีคูณของ 1/16)
  • การแมปบิต - หากช่วงของค่าการค้นหาที่เป็นไปได้มีขนาดเล็กพอคุณสามารถลองสร้างตารางการค้นหาขนาดใหญ่ที่จัดทำดัชนีโดยค่าบิตของ x นี่จะเป็น O (1) แต่คุณอาจต้องการหน่วยความจำจำนวนมากซึ่งจะไม่เป็นมิตรกับแคชของคุณมากนักดังนั้นควรใช้ด้วยความระมัดระวัง นี่เป็นเรื่องที่น่ารังเกียจเป็นพิเศษเพราะคุณกำลังค้นหาค่าลอยดังนั้นคุณอาจต้องใช้หลาย GB สำหรับบัญชีบิตที่สำคัญน้อยกว่า ......
  • การปัดเศษและแฮช - ตารางแฮชอาจไม่ใช่โครงสร้างข้อมูลที่ดีที่สุดสำหรับปัญหานี้ แต่ถ้าคุณสามารถอยู่รอดด้วยการสูญเสียความแม่นยำเพียงเล็กน้อยพวกเขาก็สามารถทำงานได้ - เพียงแค่ปัดบิตต่ำสุดของค่าการค้นหาของคุณแล้วใช้ hashmap เพื่อค้นหา ค่าที่ถูกต้อง คุณจะต้องทดสอบการแลกเปลี่ยนที่ถูกต้องระหว่างขนาด hashmap และความแม่นยำและตรวจสอบให้แน่ใจว่าค่าแฮชที่เป็นไปได้ทั้งหมดนั้นได้รับการเติมเพื่อให้สิ่งนี้เป็นเรื่องยุ่งยาก ......
  • สมดุลต้นไม้ - ต้นไม้ในอุดมคติของคุณควรมีโอกาส 50% ที่จะไปทางซ้ายหรือขวา ดังนั้นหากคุณสร้างแผนภูมิตามการกระจายของค่าการค้นหา (x) คุณสามารถปรับต้นไม้ให้เหมาะสมเพื่อสร้างคำตอบด้วยจำนวนการทดสอบที่น้อยที่สุด นี่น่าจะเป็นทางออกที่ดีถ้าค่าจำนวนมากในอาร์เรย์โฟลตของคุณอยู่ใกล้กันมากเพราะจะช่วยให้คุณหลีกเลี่ยงการค้นหาสาขาเหล่านี้บ่อยเกินไป
  • ต้นไม้ Crit-bit -ต้นไม้เหล่านี้ยังคงเป็นต้นไม้ (ดังนั้นยังคงเป็น O (log n) ... ) แต่บางกรณี: คุณจะต้องแปลงทุ่นของคุณให้อยู่ในรูปแบบจุดคงที่เพื่อที่จะทำการเปรียบเทียบ

อย่างไรก็ตามถ้าคุณไม่อยู่ในสถานการณ์ที่พิเศษมากฉันอาจแนะนำให้ใช้การค้นหาแบบไบนารีอย่างง่าย เหตุผล:

  • มันง่ายกว่าที่จะใช้
  • มันเร็วมากสำหรับกรณีทั่วไป
  • ค่าใช้จ่ายเพิ่มเติมของวิธีการที่ซับซ้อนมากขึ้น (เช่นการใช้งานหน่วยความจำแคช / ความดันสูง) มักจะมากกว่าเมื่อเทียบกับกำไรทางทฤษฎีเล็กน้อย
  • มันจะแข็งแกร่งยิ่งขึ้นต่อการเปลี่ยนแปลงในอนาคตของการแจกแจงข้อมูล ....

1

ดูเหมือนง่ายพอ:

ทำการค้นหาแบบไบนารีสำหรับโฟลตที่คุณต้องการผูก - O (บันทึก n) เวลา

จากนั้นองค์ประกอบทางด้านซ้ายของมันคือขอบเขตล่างและองค์ประกอบทางด้านขวาของมันคือขอบบน


0

คำตอบที่ชัดเจนคือการเก็บลอยในต้นไม้ การสนับสนุนการทำงาน 'ก่อนหน้า' และ 'ถัดไป' นั้นไม่สำคัญในทรี ดังนั้นเพียงแค่ทำ 'ถัดไป' กับค่าของคุณแล้วทำ 'ก่อนหน้า' กับค่าที่คุณพบในขั้นตอนแรก


1
นี่คือหลักเหมือนกับการค้นหาแบบไบนารี
วินไคลน์

-1

บทความนี้ ("การค้นหา sublogarithmic โดยไม่มีการคูณซ้ำ") อาจเป็นที่สนใจ มันยังมีซอร์สโค้ดอยู่บ้าง สำหรับวัตถุประสงค์ในการเปรียบเทียบคุณสามารถใช้ตัวเลขทศนิยมเป็นจำนวนเต็มด้วยรูปแบบบิตเดียวกัน นี่เป็นหนึ่งในเป้าหมายการออกแบบของมาตรฐานจุดลอยตัว IEEE

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