ต้นไม้จัดโดยโครงสร้าง“ firstchild, nextsibling” หรือไม่? ถ้าไม่ทำไมล่ะ


12

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

       +-----------------------------------------+
       |        root                             | 
       | child1            child2         child3 |
       +--+------------------+----------------+--+
          |                  |                |
+---------------+    +---------------+    +---------------+
|    node1      |    |     node2     |    |     node3     |
| child1 child2 |    | child1 child2 |    | child1 child2 |
+--+---------+--+    +--+---------+--+    +--+---------+--+
   |         |          |         |          |         |

มันดูเป็นธรรมชาติ แต่มันมาพร้อมกับปัญหาบางอย่าง ตัวอย่างเช่นเมื่อจำนวนโหนดย่อยแตกต่างกันไปคุณจำเป็นต้องมีสิ่งต่าง ๆ เช่นอาร์เรย์หรือรายการเพื่อจัดการ childs

โดยใช้ลูกคนแรก (คนแรก) และตัวชี้พี่น้อง (ต่อไป) แทนเราจะได้รับสิ่งที่ดูเหมือนว่า:

       +-------------------+
       |        root       |
       | child    sibling  +--->NULL
       +--+----------------+
          |             
+----------------+    +----------------+    +----------------+
|    node1       |    |     node2      |    |     node3      |
| child  sibling +--->| child  sibling +--->| child  sibling +--->NULL
+--+-------------+    +--+-------------+    +--+-------------+
   |                     |                     |

เห็นได้ชัดว่าโครงสร้างประเภทนี้สามารถเป็นตัวแทนของต้นไม้ได้เช่นกัน แต่ก็มีข้อได้เปรียบบางประการ สิ่งสำคัญที่สุดคือเราไม่ต้องกังวลเกี่ยวกับจำนวนโหนดย่อยอีกต่อไป เมื่อใช้สำหรับการแยกวิเคราะห์ต้นไม้จะให้การแทนแบบธรรมชาติสำหรับคำเช่น "a + b + c + d + e" โดยไม่ต้องกลายเป็นต้นไม้ที่ลึก

ไลบรารีคอลเลกชันมีโครงสร้างแบบต้นไม้หรือไม่ parsers ใช้โครงสร้างดังกล่าวหรือไม่? ถ้าไม่เหตุผลคืออะไร


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

4
การใส่ไอเท็มในรายการที่ลิงก์จะแนะนำO(n)ปัจจัยในอัลกอริทึม

และจะได้รับ Node3 จากรากที่คุณจะต้องใช้เวลาcddarราก ...
Tacroy

Tacroy: ถูกต้องกลับไปหารากไม่ว่าง่าย แต่ถ้าผมต้องการที่ตัวชี้กลับจะ approriate (แม้ว่ามันจะทำให้เสียแผนภาพ ;-)
user281377

คำตอบ:


7

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

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

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

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


1
ตัวอย่างเช่นต้นไม้ B + จะไม่ใช้โครงสร้าง "firstchild, nextsibling" นี้ มันจะไม่มีประสิทธิภาพถึงจุดที่ไร้สาระสำหรับต้นไม้ที่ใช้ดิสก์และยังไม่มีประสิทธิภาพมากสำหรับต้นไม้ที่ใช้หน่วยความจำ ต้นไม้ R-in- หน่วยความจำสามารถทนต่อโครงสร้างนี้ แต่มันก็ยังคงหมายถึงการพลาดแคชมากขึ้น ฉันรู้สึกกดดันที่จะนึกถึงสถานการณ์ที่ "เด็กคนแรกเด็ก ๆ " จะดีกว่า ใช่มันสามารถใช้ได้กับต้นไม้ไวยากรณ์ตามที่ ammoQ พูดถึง มีอะไรอีกไหม
Qwertie

3
"คุณต่อท้ายเด็กใน O (1)" - ฉันคิดว่าคุณสามารถแทรกเด็กที่ดัชนี 0 ใน O (1) ได้ทุกครั้ง แต่การต่อท้ายเด็กดูเหมือนจะชัดเจน O (n)
Scott Whitlock

การจัดเก็บต้นไม้ทั้งหมดในอาร์เรย์เดียวนั้นเป็นเรื่องปกติสำหรับกอง
Brian

1
@Scott: ดีฉันคิดว่ารายการที่เชื่อมโยงยังมีตัวชี้ / อ้างอิงถึงรายการสุดท้ายเช่นกันซึ่งจะทำให้ O (1) สำหรับ POS แรกหรือ POS สุดท้าย ... แม้ว่ามันจะหายไปในตัวอย่าง
OPs

ฉันพนันได้เลยว่า (ยกเว้นในกรณีที่แย่ลงอย่างมาก ) การใช้งาน“ Firstchild, nextsibling” นั้นไม่เคยมีประสิทธิภาพมากกว่าการใช้งานตารางลูกแบบอิงอาร์เรย์ ชนะตำแหน่งแคชแล้วครั้งยิ่งใหญ่ ต้นไม้ B ได้รับการพิสูจน์แล้วว่าเป็นการนำไปใช้ที่มีประสิทธิภาพมากที่สุดโดยสถาปัตยกรรมที่ทันสมัยเอาชนะกับต้นไม้สีแดง - ดำที่ใช้แบบดั้งเดิมอย่างแม่นยำเนื่องจากตำแหน่งแคชที่ได้รับการปรับปรุง
Konrad Rudolph

2

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

การใช้ tree (node) ที่เป็นที่โปรดปรานในโมเดลแรกของคุณคือ one-liner (ใน C #):

public class node : List<node> { /* props go here */ }

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

ต้นไม้ของผู้ปกครองเท่านั้น

แบบจำลองอื่นที่คุณไม่ได้กล่าวถึงคือรุ่นที่เด็กทุกคนมีการอ้างอิงถึงผู้ปกครอง:

               null
                 |
       +---------+---------------------------------+
       |       parent                              |
       | root                                      |
       +-------------------------------------------+
          |                   |                |
+---------+------+    +-------+--------+    +--+-------------+
|     parent     |    |     parent     |    |     parent     |
|     node 1     |    |     node 2     |    |     node 3     |
+----------------+    +----------------+    +----------------+

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

โดยทั่วไปแล้วจะเห็นต้นไม้ของต้นไม้เท่านั้นในแอปพลิเคชันฐานข้อมูล การค้นหาโหนดย่อยของโหนดทำได้ง่ายด้วยคำสั่ง "SELECT * WHERE ParentId = x" อย่างไรก็ตามเราไม่ค่อยพบสิ่งเหล่านี้เปลี่ยนเป็นวัตถุคลาสต้นไม้โหนด ในแอปพลิเคชั่น statefull (เดสก์ท็อป) อาจถูกห่อไว้ในการควบคุมทรีโหนดที่มีอยู่ ในแอปพลิเคชันไร้สาย (เว็บ) แม้อาจเป็นไปได้ยาก ฉันได้เห็นเครื่องมือสร้างคลาส ORM การจับคู่โยนข้อผิดพลาดล้นสแตกเมื่อสร้างคลาสสำหรับตารางที่มีความสัมพันธ์กับตัวเอง (หัวเราะหึ ๆ ) ดังนั้นต้นไม้เหล่านี้อาจไม่เหมือนกัน

ต้นไม้นำทางแบบสองทิศทาง

อย่างไรก็ตามในกรณีที่ใช้งานได้จริงมันสะดวกที่จะมีสิ่งที่ดีที่สุดทั้งสองโลก โหนดที่มีรายการลูก ๆ และรู้จักพ่อแม่ของพวกมันด้วย: ต้นไม้นำทางสองทิศทาง

                          null
                            |
       +--------------------+--------------------+
       |                  parent                 |
       |        root                             | 
       | child1            child2         child3 |
       +--+------------------+----------------+--+
          |                  |                |
+---------+-----+    +-------+-------+    +---+-----------+
|      parent   |    |     parent    |    |  parent       |
|    node1      |    |     node2     |    |     node3     |
| child1 child2 |    | child1 child2 |    | child1 child2 |
+--+---------+--+    +--+---------+--+    +--+---------+--+
   |         |          |         |          |         |

สิ่งนี้นำมาซึ่งแง่มุมอื่น ๆ อีกมากมายที่ควรพิจารณา:

  • จะใช้การเชื่อมโยงและยกเลิกการเชื่อมโยงของผู้ปกครองได้ที่ไหน
    • ปล่อยให้บิสซิเนสลอจิกดูแลและปล่อยให้มุมมองออกไปจากโหนด (พวกมันจะลืม!)
    • โหนดมีวิธีในการสร้างลูก (ไม่อนุญาตให้เรียงลำดับใหม่) (ตัวเลือกของ Microsoft ในการใช้งาน System.Xml.XmlDocument DOM ซึ่งเกือบทำให้ฉันบ้าเมื่อฉันเจอครั้งแรก)
    • โหนดนำพาเรนต์ไปยังคอนสตรัคเตอร์ของพวกเขา (ไม่อนุญาตให้เรียงลำดับใหม่)
    • ในวิธีการเพิ่มทั้งหมด (), แทรก () และลบ () และโอเวอร์โหลดของโหนด (โดยปกติจะเป็นตัวเลือกของฉัน)
  • ติดตา
    • วิธีการเดินต้นไม้เมื่อยังคงอยู่ (เช่นออกจากลิงค์ผู้ปกครอง)
    • วิธีสร้างการเชื่อมโยงแบบสองทางใหม่หลังจากยกเลิกการทำให้เป็นอนุกรม (ตั้งค่าผู้ปกครองทั้งหมดอีกครั้งเป็นการดำเนินการภายหลังการดีซีเรียลไลซ์เซชัน)
  • การแจ้งเตือน
    • กลไกแบบคงที่ (ธง IsDirty) จัดการในคุณสมบัติแบบเรียกซ้ำ?
    • เหตุการณ์ฟองสบู่ผ่านผู้ปกครอง, ลงผ่านเด็ก ๆ , หรือทั้งสองวิธี (พิจารณาตัวอย่างเช่นปั๊มข้อความ windows)

ตอนนี้เพื่อตอบคำถามต้นไม้นำทางสองทิศทางมีแนวโน้มที่จะ (ในอาชีพและทุ่งนาของฉัน) ที่ใช้กันอย่างแพร่หลาย ตัวอย่างคือการใช้งาน Microsoft ของ System.Windows.Forms.Control หรือ System.Web.UI.Control ในกรอบงาน. Net แต่การใช้งาน DOM (Document Object Model) ทุกรุ่นจะมีโหนดที่รู้จักพาเรนต์รวมถึงการแจงนับ ของเด็ก ๆ เหตุผล: ความง่ายในการใช้งานมากกว่าความง่ายในการใช้งาน นอกจากนี้เหล่านี้มักจะเป็นคลาสพื้นฐานสำหรับคลาสที่เฉพาะเจาะจงมากขึ้น (XmlNode อาจเป็นฐานของคลาสแท็กแอตทริบิวต์และข้อความ) และคลาสพื้นฐานเหล่านี้เป็นสถานที่ตามธรรมชาติเพื่อวางลำดับทั่วไปและสถาปัตยกรรมการจัดการเหตุการณ์

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


1

ฉันไม่รู้เกี่ยวกับไลบรารีคอนเทนเนอร์ใด ๆ ที่สนับสนุนกรณีที่สองของคุณโดยตรง แต่ไลบรารีคอนเทนเนอร์ส่วนใหญ่สามารถรองรับสถานการณ์นั้นได้อย่างง่ายดาย ตัวอย่างเช่นใน C ++ คุณอาจมี:

class Node;  // forward reference to satisfy the compiler
typedef std::list<Node*> NodeList;
class Node : public NodeList { /* . . . */ };  // a node is also a list

Node* n = new Node;
n->push_back(new Node);
Node* tree = new Node;
tree->push_back(new Node);
tree->push_back(n);

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


1

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

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

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