ทำไมต้นไม้สีแดงดำจึงเป็นที่นิยม


46

ดูเหมือนว่าทุกที่ที่ฉันมองมีการใช้โครงสร้างข้อมูลโดยใช้ต้นไม้สีแดงดำ ( std::setใน C ++, SortedDictionaryใน C #, ฯลฯ )

มีต้นไม้ปกคลุม (a, b), สีแดง - ดำและ AVL ในชั้นเรียนอัลกอริทึมของฉันนี่คือสิ่งที่ฉันได้ออกไป (จากการถามอาจารย์, มองผ่านหนังสือสองสามเล่มและ googling เล็กน้อย):

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

มีมาตรฐานออนไลน์มากมายที่เปรียบเทียบ AVL กับต้นไม้สีแดง - ดำ แต่สิ่งที่ทำให้ฉันสะดุดคือศาสตราจารย์ของฉันพูดโดยทั่วไปว่าโดยปกติคุณจะทำสองอย่าง:

  • ไม่ว่าคุณจะไม่สนใจเรื่องประสิทธิภาพมากนักซึ่งในกรณีนี้ความแตกต่างระหว่าง 10-20% ของ AVL กับสีแดง - ดำในกรณีส่วนใหญ่ไม่สำคัญเลย
  • หรือคุณสนใจเกี่ยวกับการแสดงอย่างมากในกรณีที่คุณทิ้งทั้งต้นไม้ AVL และต้นไม้สีแดงดำและไปกับต้นไม้ B ซึ่งสามารถปรับแต่งให้ทำงานได้ดีขึ้นมาก (หรือ (a, b) - ต้นไม้ฉัน ฉันจะเอาของทั้งหมดใส่ตะกร้าใบเดียว)

สาเหตุที่เป็นเพราะ B-tree เก็บข้อมูลในหน่วยความจำให้แน่นมากขึ้น (โหนดหนึ่งมีค่าหลายค่า) จะมีแคชน้อยกว่ามาก คุณสามารถปรับแต่งการใช้งานตามกรณีการใช้งานและทำให้ลำดับของทรี B ขึ้นอยู่กับขนาดแคชของ CPU เป็นต้น

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

ที่ถูกกล่าวว่ามีเหตุผลพิเศษหรือไม่ที่จะใช้ต้นไม้สีแดงดำทุกที่เมื่อพิจารณาจากสิ่งที่กล่าวข้างต้นต้นไม้ B ควรมีประสิทธิภาพสูงกว่าต้นไม้เหล่านั้นหรือไม่ (เป็นมาตรฐานเดียวที่ฉันสามารถค้นหาได้แสดงให้เห็นถึงhttp://lh3lh3.users.sourceforge.net/udb.shtmlแต่มันอาจเป็นเรื่องของการใช้งานเฉพาะ) หรือเป็นเหตุผลว่าทำไมทุกคนใช้ต้นไม้สีแดงดำเพราะมันค่อนข้างง่ายต่อการติดตั้งหรือใช้มันในคำที่แตกต่างกัน

นอกจากนี้การเปลี่ยนแปลงนี้จะเกิดขึ้นเมื่อเราย้ายไปยังขอบเขตของภาษาที่ใช้งานได้อย่างไร ดูเหมือนว่าทั้ง Clojure และ Scala ใช้Hash array ที่พยายามแมปซึ่ง Clojure ใช้ตัวประกอบสาขาที่ 32


8
เพื่อเพิ่มความเจ็บปวดของคุณบทความส่วนใหญ่ที่เปรียบเทียบแผนภูมิการค้นหาประเภทต่าง ๆ ทำงาน ... น้อยกว่าการทดลองในอุดมคติ
ราฟาเอล

1
ฉันไม่เคยเข้าใจสิ่งนี้ด้วยตัวเองในความคิดของฉันต้นไม้ AVL นั้นง่ายต่อการติดตั้งมากกว่าต้นไม้สีแดงดำ
Jordi Vermeulen

3
การสนทนาที่เกี่ยวข้องโดยเพื่อน ๆ ของเราที่ stackoverflow เหตุใด std :: map จึงถูกนำไปใช้เป็นต้นไม้สีแดงดำ .
Hendrik Jan

คำตอบ:


10

เพื่ออ้างอิงจากคำตอบของคำถาม " การสำรวจเส้นทางจากรากในต้นไม้ AVL และต้นไม้สีแดงดำ "

สำหรับต้นไม้ค้นหาไบนารีบางชนิดรวมถึงต้นไม้สีแดงดำ แต่ไม่ใช่ต้นไม้ AVL การ "แก้ไข" กับต้นไม้สามารถทำนายได้อย่างง่ายดายถึงทางลงและดำเนินการในระหว่างการส่งจากบนลงล่างเพียงครั้งเดียว อัลกอริธึมการแทรกเช่นนั้นมักจะนำมาใช้กับการวนซ้ำมากกว่าการเรียกซ้ำและมักจะทำงานได้เร็วขึ้นเล็กน้อยในทางปฏิบัติมากกว่าการส่งแบบสองรอบ

ดังนั้นการแทรกทรี RedBlack สามารถดำเนินการได้โดยไม่ต้องเรียกซ้ำบนการเรียกซ้ำของ CPU บางอย่างมีราคาแพงมากถ้าคุณใช้งานเกินแคชการเรียกใช้ฟังก์ชัน (เช่นSPARCเนื่องจากใช้หน้าต่างลงทะเบียน )

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

การไม่เสี่ยงที่จะหมดสแต็กก็เป็นประโยชน์เช่นกัน


แต่ทรีที่สมดุลซึ่งมี 2 ^ 32 โหนดจะต้องใช้การเรียกซ้ำไม่เกิน 32 ระดับ แม้ว่าสแต็กเฟรมของคุณคือ 64 ไบต์ แต่นั่นก็เป็นพื้นที่สแต็กไม่เกิน 2 kb สามารถสร้างความแตกต่างได้จริงหรือ ฉันจะสงสัยมัน
Björn Lindqvist

@ BjörnLindqvistในโปรเซสเซอร์ SPARC ในปี 1990 ฉันมักจะมีความเร็วเพิ่มขึ้น 10 เท่าโดยเปลี่ยนเส้นทางรหัสทั่วไปจากความลึก 7 ถึง 6! อ่านข้อมูลเกี่ยวกับวิธีการที่จะไม่ลงทะเบียนไฟล์ ....
เอียน Ringrose

9

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

มีบางกรณีที่คุณไม่สามารถใช้ B-trees เลย

หนึ่งในกรณีที่โดดเด่นคือstd::mapจาก C ++ STL มาตรฐานกำหนดไว้ว่าinsertจะไม่ทำให้การวนซ้ำที่มีอยู่ใช้ไม่ได้

ไม่มีตัววนซ้ำหรือการอ้างอิงถูกต้อง

http://en.cppreference.com/w/cpp/container/map/insert

กฎนี้ใช้ B-tree เป็นการนำไปใช้เนื่องจากการแทรกจะย้ายไปรอบ ๆ องค์ประกอบที่มีอยู่

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

// non intrusive
struct Node<T> {
    T value;
    Node<T> *left;
    Node<T> *right;
};
using WalrusList = Node<Walrus>;

// intrusive
struct Walrus {
    // Tree part
    Walrus *left;
    Walrus *right;

    // Object part
    int age;
    Food[4] stomach;
};

คุณไม่สามารถสร้าง B-tree ล่วงล้ำเพราะมันไม่ได้เป็นโครงสร้างข้อมูลตัวชี้อย่างเดียว

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

ฉันยังเชื่อว่าการใช้ "single pass tail recursive" ไม่ใช่เหตุผลของความนิยมของต้นไม้สีดำสีแดงในฐานะโครงสร้างข้อมูลที่ไม่แน่นอน

logn

O(1)

O(1)

ตัวแปรที่อธิบายในโครงสร้าง opendatastures ใช้พอยน์เตอร์พอยน์เตอร์, รหัสผ่านแบบเรียกซ้ำสำหรับการแทรกและวนซ้ำแบบวนซ้ำสำหรับการแก้ไข การเรียกซ้ำอยู่ในตำแหน่งท้ายและคอมไพเลอร์ปรับให้เหมาะกับการวนซ้ำ (ฉันได้ตรวจสอบสิ่งนี้ในภาษาสนิม)

O(1)


3

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

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

2) พวกเขาต้องการข้อมูลพิเศษเพียง 1 บิตต่อโหนดและคุณมักจะสามารถหาวิธีที่จะได้รับฟรี

3) ฉันไม่ต้องทำบ่อยนักดังนั้นทุกครั้งที่ฉันทำฉันต้องหาวิธีที่จะทำมันซ้ำอีกครั้ง กฎง่ายๆและการติดต่อกับต้นไม้ 2-4 ทำให้มันดูเหมือนง่ายทุกครั้งแม้ว่ารหัสจะออกมาจะมีความซับซ้อนทุกครั้ง ฉันยังหวังว่าสักวันรหัสจะกลายเป็นเรื่องง่าย

4) วิธีที่ต้นไม้สีแดงสีดำแยกโหนดต้นไม้ 2-4 ที่สอดคล้องกันและแทรกปุ่มตรงกลางในแม่ 2-4 ปุ่มเพียงแค่เปลี่ยนสีเป็นสง่าที่สุด ฉันรักที่จะทำ


0

ต้นไม้สีแดงดำหรือ AVL มีข้อได้เปรียบเหนือต้นไม้ B และมีลักษณะคล้ายเมื่อคีย์ยาวหรือด้วยเหตุผลอื่นที่ทำให้คีย์มีราคาแพง

ฉันสร้างทางเลือกของฉันเองstd::setภายในโครงการสำคัญด้วยเหตุผลหลายประการ ฉันเลือก AVL มากกว่าสีแดงดำเพราะเหตุผลด้านประสิทธิภาพ (แต่การเพิ่มประสิทธิภาพเล็กน้อยนั้นไม่ใช่เหตุผลสำหรับการม้วนตัวเองแทน std :: set) "กุญแจ" มีความซับซ้อนและยากต่อการเคลื่อนย้ายเป็นปัจจัยสำคัญ ต้นไม้ (ก, ข) ยังคงสมเหตุสมผลอยู่หรือไม่ถ้าคุณต้องการความอ้อมอีกระดับหนึ่งข้างหน้าแป้น? ต้นไม้ AVL และต้นไม้สีแดงดำสามารถปรับโครงสร้างได้โดยไม่ต้องย้ายกุญแจดังนั้นจึงมีข้อได้เปรียบเมื่อกุญแจมีราคาแพงในการเคลื่อนย้าย


กระแทกแดกดันต้นไม้สีแดงดำเป็น "เฉพาะ" กรณีพิเศษของ (a, b) - ต้นไม้ดังนั้นเรื่องที่ดูเหมือนว่าจะลงมาเพื่อปรับแต่งพารามิเตอร์? (cc @Gilles)
ราฟาเอล
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.