ความแตกต่างระหว่างแผนภูมิการค้นหาแบบไบนารี่และฮีปแบบไบนารีคืออะไร


91

ทั้งสองดูเหมือนกันมากและมีโครงสร้างเกือบเหมือนกัน ความแตกต่างคืออะไร? ความซับซ้อนของเวลาสำหรับการดำเนินงานที่แตกต่างกันของแต่ละคนคืออะไร

คำตอบ:


63

ฮีปจะรับประกันว่าองค์ประกอบในระดับที่สูงกว่านั้นจะใหญ่กว่า (สำหรับแม็กซ์ฮีป) หรือเล็กกว่า (สำหรับมิน - ฮีป) กว่าองค์ประกอบในระดับที่ต่ำกว่าในขณะที่ BST รับประกันคำสั่งซื้อ (จาก "ซ้าย" ถึง "ขวา") ถ้าคุณต้องการเรียงองค์ประกอบไปด้วย BST โดย Dante ไม่ได้เกินบรรยาย

ฮีปจะดีกว่าที่ findMin / findMax (O (1)) ในขณะที่ BST นั้นดีในการค้นหาทั้งหมด (O (logN)) ส่วนแทรกคือ O (logN) สำหรับโครงสร้างทั้งสอง หากคุณสนใจเกี่ยวกับ findMin / findMax (เช่นลำดับความสำคัญที่เกี่ยวข้อง) ให้ไปกับ heap ถ้าคุณต้องการเรียงทุกอย่างไปด้วย BST

โดย xysun


ฉันคิดว่า BST จะดีกว่าใน findMin & findMax stackoverflow.com/a/27074221/764592
Yeo

10
ฉันคิดว่านี่เป็นเพียงความเข้าใจผิดที่พบบ่อย ต้นไม้ไบนารีสามารถปรับเปลี่ยนได้อย่างง่ายดายเพื่อค้นหา min และ max ตามที่ Yeo ชี้ นี่เป็นข้อ จำกัดของฮีป: การค้นหาที่มีประสิทธิภาพเพียงอย่างเดียวคือขั้นต่ำหรือสูงสุด ข้อได้เปรียบที่แท้จริงของฮีปคือการแทรกเฉลี่ย O (1)ตามที่ฉันอธิบาย: stackoverflow.com/a/29548834/895245
Ciro Santilli 新疆改造中心中心法轮功六四事件

ตามวิดีโอนี้คุณสามารถมีค่าที่สูงกว่าในระดับที่ต่ำกว่าตราบใดที่ค่าที่ใหญ่กว่านั้นไม่ได้เป็นค่าที่ต่ำกว่า
Whoan

ฮีปจะถูกจัดเรียงแบบรูทไปจนถึงลีฟและ BST เรียงจากซ้ายไปขวา
Deep Joshi

34

ทั้งแผนผังการค้นหาแบบทวิตและฮีปแบบไบนารีเป็นโครงสร้างข้อมูลแบบทรี

ฮีปจำเป็นต้องใช้โหนดเพื่อให้ความสำคัญกับลูก ๆ ใน max heap ลูกของแต่ละโหนดต้องน้อยกว่าตัวเอง นี่คือสิ่งที่ตรงกันข้ามสำหรับนาทีกอง:

Binary Max ฮีป

แผนผังการค้นหาแบบไบนารี (BST) จะติดตามการสั่งซื้อเฉพาะ (การสั่งซื้อล่วงหน้าการสั่งซื้อภายหลังการสั่งซื้อ) ระหว่างโหนดพี่น้อง ต้นไม้จะต้องเรียงลำดับเหมือนกอง:

ต้นไม้ค้นหาแบบทวิภาค

BST มีค่าเฉลี่ยสำหรับการแทรกการลบและการค้นหา Binary Heaps มีค่าเฉลี่ยสำหรับ findMin / findMax และสำหรับการแทรกและการลบO(logn)O ( 1 ) O ( บันทึกn )
O(1)O(logn)


1
@ การสกัด FrankW คือไม่ใช่หรือ O(logn)
flow2k

32

สรุป

          Type      BST (*)   Heap
Insert    average   log(n)    1
Insert    worst     log(n)    log(n) or n (***)
Find any  worst     log(n)    n
Find max  worst     1 (**)    1
Create    worst     n log(n)  n
Delete    worst     log(n)    log(n)

เวลาเฉลี่ยทั้งหมดในตารางนี้เท่ากับเวลาที่แย่ที่สุดยกเว้นการแทรก

  • *: ทุกที่ในคำตอบนี้ BST == สมดุล BST เนื่องจากความไม่สมดุลจะดูด asymptotically
  • **: การใช้การดัดแปลงเล็กน้อยอธิบายในคำตอบนี้
  • ***: log(n)สำหรับทรีของตัวชี้ฮีปnสำหรับฮีปอาร์เรย์แบบไดนามิก

ข้อดีของไบนารีฮีปผ่าน BST

ข้อได้เปรียบของ BST มากกว่าไบนารีฮีป

  • O(log(n))ค้นหาสำหรับองค์ประกอบโดยพลการเป็น นี่คือคุณสมบัตินักฆ่าของ BST

    สำหรับกองมันเป็นโดยทั่วไปยกเว้นสำหรับองค์ประกอบที่ใหญ่ที่สุดซึ่งเป็นO(n)O(1)

ข้อได้เปรียบ "เท็จ" ของกองมากกว่า BST

  • กองคือO(1)จะหา max, O(log(n))BST

    นี่เป็นความเข้าใจผิดที่พบบ่อยเพราะมันเป็นเรื่องเล็กน้อยที่จะแก้ไข BST เพื่อติดตามองค์ประกอบที่ใหญ่ที่สุดและอัปเดตเมื่อใดก็ตามที่องค์ประกอบนั้นสามารถเปลี่ยนแปลงได้: ในการแทรกของ swap ที่ใหญ่กว่าในการลบค้นหาที่ใหญ่เป็นอันดับสอง https://stackoverflow.com/questions/7878622/can-we-use-binary-search-tree-to-simulate-heap-operation (กล่าวถึงโดย Yeo )

    จริงๆแล้วนี่เป็นข้อ จำกัดของฮีปเมื่อเปรียบเทียบกับ BST: การค้นหาที่มีประสิทธิภาพเพียงอย่างเดียวคือสำหรับองค์ประกอบที่ใหญ่ที่สุด

ค่าเฉลี่ยของการแทรกฮีปไบนารีคือ O(1)

แหล่งที่มา:

อาร์กิวเมนต์ที่ใช้งานง่าย:

  • ระดับต้นไม้ด้านล่างมีองค์ประกอบมากกว่าระดับบนแบบทวีคูณดังนั้นองค์ประกอบใหม่จึงเกือบจะแน่นอนที่จะไปที่ด้านล่าง
  • การแทรกฮีปเริ่มจากด้านล่าง BST ต้องเริ่มจากด้านบน

ใน binary heap การเพิ่มค่าที่ดัชนีที่กำหนดนั้นก็O(1)เป็นเช่นเดียวกัน แต่ถ้าคุณต้องการที่จะทำว่ามันเป็นโอกาสที่คุณจะต้องการให้ดัชนีพิเศษ up-to-date เกี่ยวกับการดำเนินงานของกองhttps://stackoverflow.com/questions/17009056/how-to-implement-ologn-decrease- key-operation-for-min-heap-based-priority-queuเช่นสำหรับ Dijkstra เป็นไปได้โดยไม่มีค่าใช้จ่ายเพิ่มเติม

ไลบรารีมาตรฐาน GCC C ++ แทรกมาตรฐานในฮาร์ดแวร์จริง

ฉันเปรียบเทียบ C + + std::set( ต้นไม้สีแดงดำ BST ) และstd::priority_queue( ไดนามิกอาร์เรย์ฮีป ) แทรกเพื่อดูว่าฉันถูกเกี่ยวกับเวลาแทรกและนี่คือสิ่งที่ฉันได้รับ:

ป้อนคำอธิบายรูปภาพที่นี่

ชัดเจนมาก:

  • เวลาแทรกฮีปเป็นค่าคงที่โดยทั่วไป

    เราสามารถเห็นคะแนนการปรับขนาดอาร์เรย์แบบไดนามิกได้อย่างชัดเจน เนื่องจากเรากำลังเฉลี่ยเม็ดมีดทุก ๆ 10k ที่สามารถมองเห็นสิ่งต่าง ๆ ได้จากเสียงรบกวนของระบบทั้งหมดยอดเขาเหล่านั้นจึงมีขนาดใหญ่กว่าที่แสดงจริงประมาณ 10k เท่า!

    กราฟที่ซูมไม่รวมถึงการปรับขนาดของอาเรย์เท่านั้นและแสดงให้เห็นว่าเม็ดมีดเกือบทั้งหมดอยู่ในระดับต่ำกว่า 25 นาโนวินาที

  • BST เป็นลอการิทึม เม็ดมีดทั้งหมดนั้นช้ากว่าเม็ดมีดทั่วไปมาก

  • การวิเคราะห์รายละเอียด BST vs hashmap ที่: https://stackoverflow.com/questions/18414579/what-data-structure-is-inside-stdmap-in-c/51945119#51945119

ไลบรารีมาตรฐาน GCC C ++ จะแทรกเกณฑ์มาตรฐานบน gem5

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

ป้อนคำอธิบายรูปภาพที่นี่

การตีความ:

  • กองยังคงคงที่ แต่ตอนนี้เราเห็นรายละเอียดเพิ่มเติมว่ามีเพียงไม่กี่บรรทัดและแต่ละบรรทัดที่สูงขึ้นจะกระจัดกระจายมากขึ้น

    สิ่งนี้จะต้องสอดคล้องกับเวลาแฝงในการเข้าถึงหน่วยความจำที่ทำเพื่อแทรกสูงขึ้นและสูงขึ้น

  • สิ่งที่ต้องทำฉันไม่สามารถตีความ BST อย่างเต็มที่เพราะมันไม่ได้ดูลอการิทึมและค่อนข้างคงที่

    ด้วยรายละเอียดที่มากขึ้นนี้ แต่เราสามารถเห็นได้ว่ามีเส้นที่แตกต่างกันสองสามอัน แต่ฉันไม่แน่ใจว่ามันหมายถึงอะไร: ฉันคาดว่าบรรทัดล่างจะบางกว่าเนื่องจากเราแทรกด้านบนสุด

เทียบกับเรื่องนี้ติดตั้ง Buildrootบน aarch64 HPI CPU

BST ไม่สามารถนำไปใช้อย่างมีประสิทธิภาพในอาเรย์

การดำเนินการฮีปจำเป็นต้องทำให้ฟองต้นไม้ขึ้นหรือลงเพียงครั้งเดียวดังนั้นค่าเฉลี่ยของO(log(n))กรณีที่แย่ที่สุดO(1)

การรักษาความสมดุล BST นั้นต้องใช้การหมุนต้นไม้ซึ่งสามารถเปลี่ยนองค์ประกอบยอดนิยมสำหรับอีกอันหนึ่งได้และจะต้องย้ายอาร์เรย์ทั้งหมดไปรอบ ๆ ( O(n))

สามารถใช้ Heaps ได้อย่างมีประสิทธิภาพในอาเรย์

ผู้ปกครองและเด็กดัชนีสามารถคำนวณได้จากดัชนีปัจจุบันเป็นที่แสดงที่นี่

ไม่มีการดำเนินการที่สมดุลเช่น BST

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

หากคุณกำลังแทรกโหนดเดียวสำหรับทุก ๆ โหนดที่คุณลบคุณจะเสียความได้เปรียบของการแทรกเฉลี่ยแบบ asymptotic O (1) ที่ฮีปมีให้ซึ่งการลบจะมีอิทธิพลเหนือและคุณอาจใช้ BST อย่างไรก็ตาม Dijkstra อัพเดตโหนดหลายครั้งสำหรับการลบแต่ละครั้งดังนั้นเราจึงใช้ได้

ฮีปอาร์เรย์แบบไดนามิกกับฮีปต้นไม้ของพอยเตอร์

สามารถดำเนินการฮีปได้อย่างมีประสิทธิภาพที่ด้านบนของฮีปพอยน์เตอร์: https://stackoverflow.com/questions/19720438/is-it-possible-to-make-efficient-pointer-binary-heap-implementations

การใช้อาเรย์แบบไดนามิกนั้นมีพื้นที่มากขึ้น สมมติว่าแต่ละองค์ประกอบของฮีปประกอบด้วยเพียงตัวชี้ไปที่struct:

  • การใช้งานทรีต้องเก็บพอยน์เตอร์สามตัวสำหรับแต่ละองค์ประกอบ: parent, child child และ right child ดังนั้นการใช้หน่วยความจำจึงเป็นตลอดเวลา4n(3 พอยน์เตอร์พอยน์เตอร์ + 1 พอยน์เตอร์struct)

    Tree BSTs จะต้องการข้อมูลเพิ่มเติมเช่นสมดุลสีดำ - แดง - เนส

  • การใช้งานอาร์เรย์แบบไดนามิกอาจมีขนาด2nหลังจากเพิ่มขึ้นสองเท่า 1.5nดังนั้นโดยเฉลี่ยจะเป็นไปได้

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

อย่างไรก็ตามอาร์เรย์สำรองสองเท่าจะถูกO(1)ตัดจำหน่ายดังนั้นจึงพิจารณาถึงความล่าช้าสูงสุด กล่าวถึงที่นี่

ปรัชญา

  • BSTs รักษาทรัพย์สินส่วนกลางระหว่างผู้ปกครองและผู้สืบทอดทั้งหมด (เหลือน้อยกว่าใหญ่กว่า)

    โหนดบนสุดของ BST เป็นองค์ประกอบกลางซึ่งต้องใช้ความรู้ระดับโลกในการบำรุงรักษา (รู้จำนวนองค์ประกอบที่เล็กและใหญ่กว่า)

    สถานที่ให้บริการส่วนกลางนี้มีราคาแพงกว่าในการรักษา (แทรก log n) แต่ให้การค้นหาที่มีประสิทธิภาพมากขึ้น (การค้นหาบันทึก n)

  • กองรักษาทรัพย์สินในท้องถิ่นระหว่างผู้ปกครองและเด็กโดยตรง (ผู้ปกครอง> เด็ก)

    สิ่งสำคัญอันดับต้น ๆ ของฮีปคือองค์ประกอบขนาดใหญ่ซึ่งต้องการเพียงความรู้ในท้องถิ่นในการบำรุงรักษา (การรู้จักผู้ปกครองของคุณ)

รายการที่ลิงก์ทวีคูณ

รายการที่เชื่อมโยงเป็นทวีคูณสามารถเห็นได้เป็นชุดย่อยของฮีปที่รายการแรกมีลำดับความสำคัญมากที่สุดดังนั้นให้เปรียบเทียบที่นี่ด้วย:

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

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

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

เปรียบเทียบ BST ที่สมดุลต่างกัน

ถึงแม้ว่าการแทรกเชิงเส้นกำกับและค้นหาเวลาสำหรับโครงสร้างข้อมูลทั้งหมดที่จัดอยู่ในประเภท "สมดุล BSTs" ที่ฉันเคยเห็นมาแล้วนั้นเหมือนกัน แต่ BBSTs ที่แตกต่างกันมีการแลกเปลี่ยนที่แตกต่างกัน ฉันยังไม่ได้ศึกษาอย่างเต็มที่ แต่ก็เป็นการดีที่จะสรุปการแลกเปลี่ยนเหล่านี้ที่นี่:

  • สีแดงสีดำต้นไม้ ดูเหมือนจะเป็น BBST ที่ใช้กันมากที่สุดในปี 2019 เช่นที่ใช้โดย GCC 8.3.0 C ++
  • ต้นไม้ AVL ดูเหมือนจะมีความสมดุลมากกว่า BST เล็กน้อยดังนั้นจึงน่าจะดีกว่าสำหรับการค้นหาเวลาแฝงในราคาต้นทุนการค้นหาที่แพงกว่าเล็กน้อย วิกิพีเดียสรุป: "ต้นไม้ AVL มักจะถูกเปรียบเทียบกับต้นไม้สีแดง - ดำเพราะทั้งสองสนับสนุนชุดปฏิบัติการเดียวกันและใช้เวลา [เดียวกัน] สำหรับการดำเนินงานขั้นพื้นฐานสำหรับแอปพลิเคชันที่ค้นหาอย่างมากต้นไม้ AVL เร็วกว่าต้นไม้สีแดง - ดำเพราะ พวกมันมีความสมดุลที่เข้มงวดมากขึ้นเช่นเดียวกับต้นไม้สีแดง - ดำต้นไม้ AVL นั้นมีความสมดุลสูงโดยทั่วไปทั้งสองนั้นไม่ได้มีความสมดุลของน้ำหนักและ mu-balanced สำหรับ mu <1/2; นั่นคือโหนดของ sibling สามารถมีได้อย่างมหาศาล จำนวนลูกหลานที่แตกต่างกัน "
  • WAVL กระดาษเดิมกล่าวถึงข้อดีของรุ่นว่าในแง่ของขอบเขตในการปรับสมดุลการดำเนินงานและการหมุน

ดูสิ่งนี้ด้วย

คำถามที่คล้ายกันเกี่ยวกับ CS: อะไรคือความแตกต่างระหว่างแผนภูมิการค้นหาแบบไบนารีและแบบไบนารีของฮีป


1
คำตอบที่ดี แอปพลิเคชันทั่วไปของฮีปคือค่ามัธยฐาน, k นาที, องค์ประกอบยอดนิยม สำหรับการดำเนินการที่พบบ่อยที่สุดเหล่านี้ให้ลบ min แล้วใส่ (โดยทั่วไปเรามีกองเล็ก ๆ ที่มีการดำเนินการแทรกแบบไม่กี่อัน) ดังนั้นดูเหมือนว่าในทางปฏิบัติสำหรับอัลกอริทึมเหล่านี้มันไม่ได้มีประสิทธิภาพสูงกว่า BST
yura

1
คำตอบที่ยอดเยี่ยม !!! ด้วยการใช้ deque เป็นโครงสร้างฮีปพื้นฐานคุณสามารถลดเวลาการปรับขนาดได้อย่างมากแม้ว่าจะยังคงเป็นกรณีที่แย่ที่สุด O (n) เนื่องจากจำเป็นต้องจัดสรรอาร์เรย์ของตัวชี้ให้เล็กลง
Bulat

13

ด้วยโครงสร้างข้อมูลหนึ่งจะต้องแยกระดับของความกังวล

  1. นามธรรมโครงสร้างข้อมูล (วัตถุที่เก็บไว้, การดำเนินงานของพวกเขา) ในคำถามนี้จะแตกต่างกัน หนึ่งใช้คิวลำดับความสำคัญอีกชุดหนึ่ง คิวลำดับความสำคัญไม่สนใจในการค้นหาองค์ประกอบตามอำเภอใจโดยเฉพาะคิวที่มีลำดับความสำคัญสูงสุด

  2. การใช้โครงสร้างอย่างเป็นรูปธรรม ตั้งแต่แรกเห็นทั้งคู่เป็นต้นไม้ (binary) ที่มีคุณสมบัติโครงสร้างต่างกัน ทั้งลำดับที่สัมพันธ์ของคีย์และโครงสร้างส่วนกลางที่เป็นไปได้นั้นแตกต่างกัน (ค่อนข้างไม่แน่ชัดในBSTคีย์ถูกสั่งจากซ้ายไปขวาในฮีปจะถูกสั่งจากบนลงล่าง) เนื่องจาก IPlant พูดอย่างถูกต้องฮีปควรเป็น "สมบูรณ์"

  3. มีความแตกต่างสุดท้ายในคือการดำเนินงานในระดับต่ำ แผนภูมิการค้นหาแบบไบนารี (ไม่สมดุล) มีการนำมาตรฐานไปปฏิบัติโดยใช้พอยน์เตอร์ ฮีปไบนารีเป็นทางตรงกันข้ามมีการนำไปใช้อย่างมีประสิทธิภาพโดยใช้อาร์เรย์ (แม่นยำเนื่องจากโครงสร้างที่ จำกัด )


1

ด้านบนของคำตอบก่อนหน้าฮีปต้องมีคุณสมบัติโครงสร้างฮีป ต้นไม้จะต้องเต็มและชั้นล่างสุดซึ่งไม่สามารถเต็มได้เสมอต้องเต็มไปทางซ้ายสุดไปทางขวาสุดโดยไม่มีช่องว่าง

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