เหตุใด std :: map ถูกนำไปใช้เป็นต้นไม้สีแดงดำ


195

ทำไมถูกstd::mapนำไปใช้เป็นต้นไม้สีแดงดำ ?

มีโครงสร้างการค้นหาแบบทวิภาคที่สมดุลหลายต้น (BST) อะไรคือข้อดีของการออกแบบต้นไม้สีแดง - ดำ


26
แม้ว่าการนำไปใช้ทั้งหมดที่ฉันเคยเห็นใช้ RB-tree โปรดทราบว่านี่ยังขึ้นอยู่กับการนำไปใช้งาน
โทมัส

3
@Thomas มันขึ้นอยู่กับการนำไปใช้งานดังนั้นทำไมจึงเป็นเช่นนั้นเพื่อให้การใช้งานทั้งหมดใช้ต้นไม้ RB?
เดนิส Gorodetskiy

1
ฉันอยากรู้ว่าผู้ดำเนินการ STL คนใดมีความคิดเกี่ยวกับการใช้รายการข้าม
Matthieu M.

2
แผนที่และการตั้งค่าของ C ++ นั้นจริงแล้วได้รับคำสั่งจากแผนที่และการสั่งซื้อ พวกเขาจะไม่ดำเนินการโดยใช้ฟังก์ชั่นแฮช แบบสอบถามทั้งหมดจะใช้เวลาO(logn)และไม่O(1)แต่จะเรียงลำดับค่าเสมอ เริ่มต้นจาก C ++ 11 (ฉันคิดว่า) มีunordered_mapและunordered_setที่นำมาใช้โดยใช้ฟังก์ชันแฮชและในขณะที่พวกเขาไม่ได้เรียงแบบสอบถามและการดำเนินงานส่วนใหญ่เป็นไปได้ในO(1)(เฉลี่ย)
SomethingSomething

@ โทมัสที่เป็นจริง แต่ไม่น่าสนใจในทางปฏิบัติ มาตรฐานทำให้การรับประกันความซับซ้อนด้วยอัลกอริทึมเฉพาะหรือชุดของอัลกอริทึมในใจ
Justin Meiners

คำตอบ:


126

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

ในขณะที่ทั้งสองอัลกอริทึมการดำเนินการแทรก / ลบคือ O (บันทึก n) ในกรณีของการหมุนปรับสมดุลต้นไม้สีแดงดำเป็นการดำเนินการO (1)ในขณะที่ AVL เป็นการดำเนินการO (บันทึก n)ทำให้ ต้นไม้สีแดง - ดำมีประสิทธิภาพมากขึ้นในแง่มุมของขั้นตอนการปรับสมดุลและหนึ่งในเหตุผลที่เป็นไปได้ที่ใช้กันมากขึ้น

ต้นไม้สีแดงดำถูกใช้ในห้องสมุดส่วนใหญ่รวมถึงข้อเสนอจาก Java และ Microsoft .NET Framework


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

35
คุณต้องมองข้ามความซับซ้อนไปยังรันไทม์จริงเพื่อดูความแตกต่าง - โดยทั่วไปทรี AVL มีรันไทม์รวมที่ต่ำกว่าเมื่อมีการค้นหามากกว่าการแทรก / ลบ ต้นไม้ RB มีรันไทม์รวมที่ต่ำกว่าเมื่อมีการเพิ่ม / ลบจำนวนมาก สัดส่วนที่แน่นอนที่การหยุดพักเกิดขึ้นแน่นอนว่าขึ้นอยู่กับรายละเอียดมากมายของการนำไปใช้งานฮาร์ดแวร์และการใช้งานที่แน่นอน แต่เนื่องจากผู้เขียนห้องสมุดต้องสนับสนุนรูปแบบการใช้งานที่หลากหลายพวกเขาต้องเดาการศึกษา AVL นั้นใช้งานได้ยากขึ้นเล็กน้อยดังนั้นคุณอาจต้องการผลประโยชน์ที่พิสูจน์แล้วว่าใช้ได้
Steve Jessop

6
ต้นไม้ RB ไม่ใช่ "การใช้งานเริ่มต้น" ผู้ดำเนินการแต่ละคนเลือกการใช้งาน เท่าที่เราทราบพวกเขาได้เลือกต้นไม้ RB ดังนั้นมันน่าจะเหมาะกับประสิทธิภาพหรือเพื่อความสะดวกในการติดตั้งและบำรุงรักษา ดังที่ฉันได้กล่าวจุดพักสำหรับประสิทธิภาพอาจไม่ได้หมายความว่าพวกเขาคิดว่ามีส่วนแทรก / ลบมากกว่าการค้นหาเพียงว่าอัตราส่วนระหว่างทั้งสองนั้นสูงกว่าระดับที่พวกเขาคิดว่า RB อาจชนะ AVL
Steve Jessop

9
@Denis: น่าเสียดายที่วิธีเดียวที่จะได้รับตัวเลขคือการสร้างรายการของstd::mapการใช้งานติดตามนักพัฒนาและถามพวกเขาว่าเกณฑ์ที่พวกเขาใช้ในการตัดสินใจจึงยังคงเก็งกำไร
Steve Jessop

4
สิ่งที่ขาดไปทั้งหมดนี้คือต้นทุนต่อโหนดในการจัดเก็บข้อมูลเสริมที่จำเป็นสำหรับการตัดสินใจยอดคงเหลือ ต้นไม้สีแดงดำต้อง 1 บิตเพื่อแสดงสี ต้นไม้ AVL ต้องการอย่างน้อย 2 บิต (เพื่อเป็นตัวแทน -1, 0 หรือ 1)
SJHowe

47

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

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


1
คุณแน่ใจเกี่ยวกับสิ่งนั้น ??? โดยส่วนตัวผมคิดว่าต้นไม้สีแดงดำมีความซับซ้อนมากกว่าหรือไม่ง่ายกว่านี้ สิ่งเดียวที่อยู่ในต้นไม้ Rd-Black การปรับสมดุลเกิดขึ้นน้อยกว่า AVL
Eric Ouellet

1
@Eric ตามหลักวิชาทั้งต้นไม้ R / B และต้นไม้ AVL มีความซับซ้อน O (log n)) สำหรับการแทรกและการลบ แต่ส่วนใหญ่ของค่าใช้จ่ายในการดำเนินงานคือการหมุนซึ่งแตกต่างจากต้นไม้สองต้นนี้ โปรดดูที่Discuss.fogcreek.com/joelonsoftware/อ้างจาก: "การสร้างทรี AVL อาจต้องใช้การหมุน O (log n) ในขณะที่ทรีสีดำสีแดงจะใช้เวลามากถึงสองรอบในการหมุนเพื่อให้เกิดความสมดุล (แม้ว่าอาจต้อง ตรวจสอบโหนด O (log n) เพื่อตัดสินใจว่าจำเป็นต้องใช้การหมุนแบบใด " แก้ไขความคิดเห็นของฉันตาม
webbertiger

27

ต้นไม้ AVL มีความสูงสูงสุด 1.44 ส่วนในขณะที่ต้น RB มีจำนวนสูงสุด 2 ต้น การแทรกองค์ประกอบใน AVL อาจหมายถึงการปรับสมดุลที่จุดหนึ่งในต้นไม้ การปรับสมดุลเสร็จสิ้นการแทรก หลังจากการแทรกใบไม้ใหม่การอัพเดตบรรพบุรุษของใบไม้นั้นจะต้องดำเนินการจนถึงรากหรือจนถึงจุดที่ทั้งสอง subtrees มีความลึกเท่ากัน ความน่าจะเป็นที่ต้องอัปเดตโหนด k คือ 1/3 ^ k การปรับสมดุลคือ O (1) การลบองค์ประกอบอาจบ่งบอกถึงการปรับสมดุลมากกว่าหนึ่ง (ขึ้นไปครึ่งความลึกของต้นไม้)

RB-trees เป็น B-trees ของลำดับ 4 ที่แสดงเป็นแผนภูมิการค้นหาแบบไบนารี 4-node ใน B-tree ส่งผลให้มีสองระดับใน BST ที่เทียบเท่า ในกรณีที่แย่ที่สุดโหนดทั้งหมดของทรีคือ 2-nodes โดยมีเพียงเชนเดียวของ 3-nodes ลงไปที่ leaf ใบไม้นั้นจะอยู่ห่างจากราก 2 โล

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

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

ในที่สุดต้นไม้ยังสามารถนำข้อมูลน้ำหนักไปยังโหนดเพื่อให้เกิดความสมดุลของน้ำหนัก สามารถใช้รูปแบบที่หลากหลาย หนึ่งควรปรับสมดุลเมื่อทรีย่อยมีมากกว่า 3 เท่าของจำนวนองค์ประกอบของทรีย่อยอื่น ๆ การปรับสมดุลจะทำอีกครั้งผ่านการหมุนครั้งเดียวหรือสองครั้ง ซึ่งหมายถึงกรณีที่เลวร้ายที่สุดของ 2.4logn หนึ่งสามารถหนีไปได้ 2 ครั้งแทนที่จะเป็น 3 ซึ่งเป็นอัตราส่วนที่ดีกว่ามาก แต่นั่นอาจหมายถึงการทิ้งทรีย่อย 1% ของทรีย่อยที่ไม่สมดุลที่นี่และที่นั่น หากิน!

ต้นไม้ชนิดใดดีที่สุด? แน่นอน AVL พวกมันง่ายที่สุดในการเขียนโค้ดและมีความสูงที่แย่ที่สุดในการเข้าสู่ระบบ สำหรับต้นไม้ที่มีองค์ประกอบ 1000000 รายการ AVL จะมีความสูงสูงสุด 29, RB 40 และน้ำหนักที่สมดุล 36 หรือ 50 ขึ้นอยู่กับอัตราส่วน

มีตัวแปรอื่น ๆ อีกมากมาย: การสุ่มอัตราส่วนการเพิ่มการลบการค้นหาและอื่น ๆ


2
คำตอบที่ดี. แต่ถ้า AVL ดีที่สุดทำไมไลบรารีมาตรฐานจึงใช้ std :: map เป็น tree RB
เดนิส Gorodetskiy

14
ฉันไม่เห็นด้วยว่าต้นไม้ของ AVL นั้นดีที่สุดอย่างไม่ต้องสงสัย แม้ว่าพวกเขาจะมีความสูงต่ำ แต่พวกเขาต้องการงานที่ต้องทำการรีไฟแนนซ์มากกว่าต้นไม้สีแดง / ดำ (O (บันทึก n) การปรับสมดุลงานกับ O (1) งานการปรับสมดุลที่ถูกตัดจำหน่าย ต้นไม้ที่เล่นได้อาจดีกว่ามากและการยืนยันของคุณว่าผู้คนกลัวพวกเขาจะไม่มีมูลความจริง ไม่มีแผนการปรับสมดุลต้นไม้ "ดีที่สุด" ที่เป็นสากล
templatetypedef

คำตอบที่สมบูรณ์แบบเกือบ ทำไมคุณถึงบอกว่า AVL ดีที่สุด นั่นเป็นสิ่งที่ผิดและนั่นเป็นสาเหตุที่การใช้งานทั่วไปส่วนใหญ่ใช้ต้นไม้สีแดง - ดำ คุณต้องมีอัตราส่วนการอ่านมากกว่าการจัดการเพื่อเลือก AVL ค่อนข้างสูง นอกจากนี้ AVL มีหน่วยความจำน้อยกว่า RB เล็กน้อย
Eric Ouellet

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

25

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

ทำไมไม่ตารางแฮช

ประเภทต้องการ<ตัวดำเนินการ (การเปรียบเทียบ) เท่านั้นจึงจะใช้เป็นคีย์ในทรี อย่างไรก็ตามตารางแฮชต้องการให้แต่ละประเภทคีย์มีhashฟังก์ชันที่กำหนดไว้ การรักษาข้อกำหนดของประเภทให้น้อยที่สุดนั้นสำคัญมากสำหรับการเขียนโปรแกรมทั่วไปดังนั้นคุณสามารถใช้กับประเภทและอัลกอริทึมที่หลากหลายได้

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

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

(C ++ 11 เพิ่มตารางแฮชด้วยunordered_mapคุณสามารถดูได้จากเอกสารที่ต้องใช้นโยบายการตั้งค่าเพื่อกำหนดค่าตัวเลือกเหล่านี้จำนวนมาก)

ต้นไม้อื่น ๆ ล่ะ?

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

Alexander Stepanov (ผู้สร้าง STL) กล่าวว่าเขาจะใช้ต้นไม้ B * แทนต้นไม้สีแดงดำถ้าเขาเขียนstd::mapอีกครั้งเพราะเป็นมิตรกับแคชหน่วยความจำสมัยใหม่

หนึ่งในการเปลี่ยนแปลงที่ยิ่งใหญ่ที่สุดนับตั้งแต่นั้นมาก็คือการเติบโตของแคช การพลาดแคชนั้นมีค่าใช้จ่ายสูงมากดังนั้นการอ้างอิงในท้องที่จึงมีความสำคัญมากกว่าในตอนนี้ โครงสร้างข้อมูลที่ใช้โหนดซึ่งมีตำแหน่งอ้างอิงต่ำทำให้มีความรู้สึกน้อยลง ถ้าฉันออกแบบ STL วันนี้ฉันจะมีชุดของภาชนะที่แตกต่างกัน ตัวอย่างเช่น In-memory B * -tree เป็นตัวเลือกที่ดีกว่าต้นไม้สีแดงดำสำหรับการนำคอนเทนเนอร์ที่เชื่อมโยงไปใช้ - Alexander Stepanov

แผนที่ควรใช้ต้นไม้หรือไม่

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

ฉันจำเป็นต้องใช้แผนที่ด้วยหรือไม่

ข้อควรพิจารณาเกี่ยวกับแคชหมายถึงไม่ค่อยมีเหตุผลที่จะใช้งานstd::listหรือstd::dequeมากกว่าstd:vectorสำหรับสถานการณ์ที่เราสอนในโรงเรียน (เช่นการลบองค์ประกอบออกจากรายการกลาง) การใช้เหตุผลแบบเดียวกันนั้นการใช้การวนลูปกับการค้นหาเชิงเส้นรายการมักจะมีประสิทธิภาพและสะอาดกว่าการสร้างแผนที่สำหรับการค้นหาสองสามครั้ง

แน่นอนว่าการเลือกคอนเทนเนอร์ที่อ่านได้มักมีความสำคัญมากกว่าประสิทธิภาพ


3

ปรับปรุง 2017-06-14: webbertiger แก้ไขคำตอบหลังจากที่ฉันแสดงความคิดเห็น ฉันควรชี้ให้เห็นว่าคำตอบคือตอนนี้ดีขึ้นมากในสายตาของฉัน แต่ฉันได้รับคำตอบเหมือนข้อมูลเพิ่มเติม ...

เนื่องจากความจริงที่ว่าฉันคิดว่าคำตอบแรกนั้นผิด (การแก้ไข: ไม่ใช่ทั้งสองอีกต่อไป) และข้อที่สามมีการยืนยันที่ผิด ฉันรู้สึกว่าฉันต้องอธิบายสิ่งต่าง ๆ ...

ต้นไม้ที่ได้รับความนิยมสูงสุด 2 รายการคือ AVL และ Red Black (RB) ความแตกต่างที่สำคัญคือการใช้งาน:

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

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

หากคุณพิจารณาถึงประสิทธิภาพการทำงานสำหรับการปรับเปลี่ยนของต้นไม้ (นัยสำคัญ) เทียบกับประสิทธิภาพของการปรึกษาหารือของต้นไม้ (เกือบไม่มีนัยสำคัญ) มันจะกลายเป็นธรรมชาติที่ต้องการ RB มากกว่า AVL สำหรับกรณีทั่วไป


2

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

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