บทที่ 7: โครงสร้างข้อมูลต้นไม้ (Tree)

วัตถุประสงค์

โครงสร้างข้อมูลต้นไม้มีลักษณะคล้ายกิ่งก้านของต้นไม้ตามธรรมชาติ แต่จะมีรากอยู่ด้านบน จุดที่มีการแตกกิ่งก้านสาขาออกไปจะเรียกว่าโหนด (node) ซึ่งใช้เก็บข้อมูล โดยแต่ละโหนดจะเก็บข้อมูลสามส่วนคือตัวข้อมูล (element) ตัวชี้ไปยังลูกทางซ้าย และตัวชี้ไปยังลูกทางขวา ต้นไม้แต่ละต้นอาจจะมีแต่ราก (root) เท่านั้น หรือมีลูกทางซ้าย (left) หรือมีลูกทางขวา (right) หรืออาจจะไม่มีข้อมูลก็ได้ (ว่าง)

Placeholder image

รากหรือลูกแต่ละคน เราเรียกว่าโหนด (node) แต่ละโหนดมีลูกได้ไม่เกิน 2 คน (binary tree) ที่เป็นพี่น้องกัน (sibling) ส่วนโหนดที่ไม่มีลูก เราเรียกว่าโหนดใบ (leaf)
โครงสร้างข้อมูลแบบต้นไม้คู่แท้ Strictly Binary Tree -->> Top
โครงสร้างข้อมูลแบบต้นไม้คู่แท้ Strictly Binary Tree
ต้นไม้ชนิดนี้ ทุกๆโหนดที่ไม่ใช่ใบ จะมีโหนดลูก 2 คนเสมอ และต้นไม้คู่แท้ ที่มี n ใบ มีโหนดทั้งหมด 2n – 1 โหนด เช่น ต้นไม้คู้แท้ที่มีใบ 4 ใบ จะมีโหนดในต้นไม้ทั้งหมด 7 โหนดเป็นต้น
Placeholder image

ระดับความลึก (Depth) หรือความสูง (Height) ของต้นไม้นับจากรากลงมา โดยรากมีความลึก 0 และแต่ละระดับจะมีความลึกเพิ่มขึ้นระดับละ 1 เช่นตัวอย่างต้นไม้ในรูปด้านบน จะมีความลึกเป็น 3
โครงสร้างข้อมูลแบบต้นไม้คู่สมบูรณ์ Complete Binary Tree
ต้นไม้คู่สมบูรณ์จะมีใบทุกใบอยู่ที่ระดับเดียวกัน ดังนั้นที่ระดับความลึก d จะมีจำนวนโหนด 2d
Placeholder image

Placeholder image
Placeholder image

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

ปีนลง Preorder (depth-first order)
ลำดับคือ root-left-right
1. แวะตรงกลาง (root)
2. ไปทางลูกซ้าย (left)
3. ไปทางลูกขวา (right)
ตัวอย่างต้นไม้ด้านบน จะได้ผลลัพธ์ดังนี้ 15, 4, 1, 8, 19, 18, 22
ปีนตามลำดับ Inorder (symmetric order)
ลำดับคือ left-root-right
1. ไปทางลูกซ้าย (left)
2. แวะตรงกลาง (root)
3. ไปทางลูกขวา (right)
ตัวอย่างต้นไม้ด้านบน จะได้ผลลัพธ์ดังนี้ 1, 4, 8, 15, 18, 19, 22
ปีนขวา Postorder
ลำดับคือ left-right-root
1. ไปทางลูกซ้าย (left)
2. ไปทางลูกขวา (right)
3. แวะตรงกลาง (root)
ตัวอย่างต้นไม้ด้านบน จะได้ผลลัพธ์ดังนี้ 1, 8, 4, 18, 22, 19, 15
Placeholder image
แบบฝึกหัด A -->> Top
จากต้นไม้ต่อไปนี้ ให้ปีนต้นไม้แบบ in-order, pre-order, และ post-order
Placeholder image
ตัวอย่างการใช้งานต้นไม้ (Tree Applications)
1. นิพจน์ต้นไม้ (Expression Tree) เป็นการเก็บนิพจน์โดยใช้ต้นไม้แท้ โดยที่ใบจะใช้เก็บ operand และโหนดที่ไม่ใช่ใบใช้เก็บ operators
• ถ้าปีนลง (Preorder) จะได้นิพจน์ Preifx
• ถ้าปีนตามลำดับ (Inorder) จะได้นิพจน์ Infix
• ถ้าปีนขวา (Postorder) จะได้นิพจน์ Postfix

2. ต้นไม้ทั่วไป (general tree) ใช้ในการเก็บข้อมูลที่มีลูกมากว่า 2 คน เช่นการเก็บไฟล์ในระบบคอมพิวเตอร์ (windows explorer)
3. ต้นไม้ค้นหา (Binary Search Tree) เป็นการสร้างต้นไม้แบบเรียงลำดับเพื่อความสะดวกรวดเร็วในการค้นหาโหนดในต้นไม้
• กรณีเรียงลำดับจากน้อยไปมาก โหนดทางซ้ายมีค่าน้อยกว่าโหนดทางขวาเสมอ
นิพจน์ต้นไม้ Expression Trees
การสร้างนิพจน์ต้นไม้ เราต้องใช้นิพจน์แบบ postfix แล้วใช้สแตคมาช่วยในการสร้างนิพจน์ต้นไม้ดังนี้
1. อ่าน postfix ถ้าเจอ operand ให้สร้างโหนดต้นไม้ แล้ว push ลงบนสแตค
2. ถ้าเจอ operator ให้ pop สแตค 2 ครั้ง แล้วสร้างโหนดต้นไม้ใหม่โดยให้ operator เป็นราก (root) และ 2 โหนดที่ pop มาเป็นลูกทางขวาและลูกทางซ้าย ตามลำดับ เสร็จแล้วให้ Push โหนดใหม่นี้ลงบนสแตค
3. ถ้าอ่าจนจบนิพจน์แล้วโหนดที่เหลืออยู่บนสแตคก็คือนิพจน์ต้นไม้
เช่นนิพจน์ Postfix: a b + c d e + * *
Placeholder image

แบบฝึกหัด B -->> Top
1. ให้สร้างต้นไม้เพื่อเก็บนิพจน์ (a+b) * c * (d+e) จากนั้นให้ปีนต้นไม้ เพื่อแสดงนิพจน์ infix prefix และ postfix ตามตัวอย่าง
Hint: เปลี่ยน template [] เป็น [] และสร้างโหนดตามลำดับตามตัวอย่าง
Placeholder image
Placeholder image

2. กำหนดนิพจน์ postfix: AB*C-DEFG*H$$/+ ให้สร้างนิพจน์ต้นไม้ แล้วปีนต้นไม้ เพื่อหานิพจน์ infix, prefix และ postfix
ต้นไม้ทั่วไป (General Tree) -->> Top
ต้นไม้ทั่วไป (General Tree)
คุณสมบัติของต้นไม้ทัวไป คือแต่ละโหนดจะมีลูกกี่คนหรือไม่มีลูกเลยก็ได้ ส่วนการค้นหาข้อมูลในต้นไม้ทั่วไป ก็ใช้วิธีการเดียวกันกับการปีนต้นไม้คู่ ตัวอย่างต้นไม้ทัวไป เช่นโปรแกรม File Explorer บน windows

Placeholder image
โปรแกรมสร้าง general tree -->> Top

 		 namespace Tree1 {
  		  using System;
    // BinaryNode class; stores a node in a tree;
    // contains recursive routines to compute size and height.
    //
    // CONSTRUCTION: with (a) no parameters, or (b) an Object,
    //     or (c) an Object, left child, and right child.
    //
    // *******************PUBLIC OPERATIONS**********************
    // int Size( )            --[>] Return size of subtree at node
    // int Height( )          --[>] Return height of subtree at node
    // void PrintPostOrder( ) --[>] Print a postorder tree traversal
    // void PrintInOrder( )   --[>] Print an inorder tree traversal
    // void PrintPreOrder( )  --[>] Print a preorder tree traversal
    // BinaryNode Duplicate( )--[>] Return a duplicate tree
    public class BinaryNode
    {
        private AnyType element;
        private BinaryNode left;
        private BinaryNode right;

        public BinaryNode(AnyType theElement, BinaryNode lt, BinaryNode rt)
        {
            element = theElement;
            left = lt;
            right = rt;
        }
        // Return the size of the binary tree rooted at t.
        public static int Size(BinaryNode t)
        {
            if (t == null)
                return 0;
            else
                return 1 + Size(t.left) + Size(t.right);
        }
        // Return the height of the binary tree rooted at t.
        public static int Height(BinaryNode t)
        {
            if (t == null)
                return -1;
            else
                return 1 + Math.Max(Height(t.left), Height(t.right));
        }
        // Print tree rooted at current node using preorder traversal.
        public void PrintPreOrder()
        {
            Console.Write(element+" ");        // Node
            if (left != null)
                left.PrintPreOrder();           // Left
            if (right != null)
                right.PrintPreOrder();          // Right
        }
        // Print tree rooted at current node using postorder traversal.
        public void PrintPostOrder()
        {
            if (left != null)
                left.PrintPostOrder();          // Left
            if (right != null)
                right.PrintPostOrder();          // Right
            Console.Write(element+" ");          // Node
        }
        // Print tree rooted at current node using inorder traversal.
        public void PrintInOrder()
        {
            if (left != null)
                left.PrintInOrder();            // Left
            Console.Write(element+" ");       // Node
            if (right != null)
                right.PrintInOrder();           // Right
        }
        // Return a reference to a node that is the root of a
        // duplicate of the binary tree rooted at the current node.
        public BinaryNode Duplicate()
        {
            BinaryNode root = new BinaryNode(element, null, null);

            if (left != null)            // If there's a left subtree
                root.left = left.Duplicate();    // Duplicate; attach
            if (right != null)          // If there's a right subtree
                root.right = right.Duplicate();  // Duplicate; attach
            return root;                      // Return resulting tree
        }
        public AnyType Element
        {
            get
            {
                return element;
            }
            set
            {
                element = value;
            }
        }
        public BinaryNode Left
        {
            get
            {
                return left;
            }
            set
            {
                left = value;
            }
        }
        public BinaryNode Right
        {
            get
            {
                return right;
            }
            set
            {
                right = value;
            }
        }
    }

    // BinaryTree class; stores a binary tree and illustrates the calling of
    // BinaryNode recursive routines and Merge.
    //
    // CONSTRUCTION: with (a) no parameters or (b) an object to
    //    be placed in the root of a one-element tree.
    //
    // *******************PUBLIC OPERATIONS**********************
    // Various tree traversals, Size, Height, IsEmpty, MakeEmpty.
    // Also, the following tricky method
    // void Merge( Object root, BinaryTree t1, BinaryTree t2 )
    //                        --[>]Construct a new tree
    // *******************ERRORS*********************************
    // Error message printed for illegal Merges.
    public class BinaryTree
    {
        private BinaryNode root;
        public BinaryTree()
        {
            root = null;
        }
        public BinaryTree(AnyType rootItem)
        {
            root = new BinaryNode(rootItem, null, null);
        }
        public void PrintPreOrder()
        {
            if (root != null)
                root.PrintPreOrder();
        }
        public void PrintInOrder()
        {
            if (root != null)
                root.PrintInOrder();
        }
        public void PrintPostOrder()
        {
            if (root != null)
                root.PrintPostOrder();
        }
        public void MakeEmpty()
        {
            root = null;
        }
        public bool IsEmpty()
        {
            return root == null;
        }
        // Merge routine for BinaryTree class.
        // Forms a new tree from rootItem, t1 and t2.
        // Does not allow t1 and t2 to be the same.
        // Correctly handles other aliasing conditions.
        public void Merge(AnyType rootItem, BinaryTree t1, BinaryTree t2)
        {
            if (t1.root == t2.root && t1.root != null)
            {
                Console.Error.WriteLine("leftTree==rightTree; merge aborted");
                return;
            }
            // Allocate new node
            root = new BinaryNode(rootItem, t1.root, t2.root);
            // Ensure that every node is in one tree
            if (this != t1)
                t1.root = null;
            if (this != t2)
                t2.root = null;
        }
        public int Size()
        {
            return BinaryNode.Size(root);
        }
        public int Height()
        {
            return BinaryNode.Height(root);
        }
        public BinaryNode Root
        {
            get
            {
                return root;
            }
        }
    }
}

ตัวอย่างการสร้าง general tree -->> Top
สร้างต้นไม้ทั่วไปตามรูปข้างล่าง
สร้างโหนด 4 โหนด เก็บค่าจำนวนเต็ม 1,3,5,7
สร้างโหนด 2 จากโหนด 1 และ 3
สร้างโหนด 6 จากโหนด 5 และ 7
สร้างโหนด 4 จากโหนด 2 และ 6
ปีนต้นไม้แบบ Inorder, Preorder, Postorder

Placeholder image


class Program { public static void Main(string[] args) { BinaryTree t1 = new BinaryTree(1); BinaryTree t3 = new BinaryTree(3); BinaryTree t5 = new BinaryTree(5); BinaryTree t7 = new BinaryTree(7); BinaryTree t2 = new BinaryTree(); BinaryTree t4 = new BinaryTree(); BinaryTree t6 = new BinaryTree(); t2.Merge(2, t1, t3); t6.Merge(6, t5, t7); t4.Merge(4, t2, t6); Console.WriteLine("t4 should be perfect 1-7; t2 empty"); Console.WriteLine("----------------"); Console.WriteLine("t4"); t4.PrintInOrder(); Console.WriteLine(); Console.WriteLine("----------------"); Console.WriteLine("t2"); t2.PrintInOrder(); Console.WriteLine(); Console.WriteLine("----------------"); Console.WriteLine("t4 size: " + t4.Size()); Console.WriteLine("t4 height: " + t4.Height()); Console.WriteLine("t4 In Order"); t4.PrintInOrder(); Console.WriteLine(); Console.WriteLine("----------------"); Console.WriteLine("t4 Pre Order"); t4.PrintPreOrder(); Console.WriteLine(); Console.WriteLine("----------------"); Console.WriteLine("t4 Post Order"); t4.PrintPostOrder(); Console.WriteLine(); Console.WriteLine("----------------"); Console.ReadKey(); } }
Placeholder image
สรุป -->> Top
• ต้นไม้คือโครงสร้างข้อมูลที่เก็บข้อมูลใน 3 ลักษณะคือมีแต่ราก (root) เท่านั้น หรือมีลูกทางซ้าย (left) หรือมีลูกทางขวา (right) หรือแต่ละลักษณะอาจจะไม่มีข้อมูลก็ได้ (ว่าง)
• การดำเนินการกับต้นไม้คือ Insert, Remove, Find, FindMin, FindMax
• โครงสร้างข้อมูลแบบต้นไม้ค้นหา (Bianry Search Tree) สามารถใช้ในการค้นหาข้อมูลได้อย่างรวดเร็ว
• เราสามารถเก็บข้อมูลใดๆก็ได้ไว้ในต้นไม้ เช่นนิพจน์ ตัวเลข นักเรียน หนังสือ คอมพิวเตอร์
• การเก็บไฟล์ในคอมพิวเตอร์จะเก็บโดยใช้โครงสร้างต้นไม้ทั่วไป

2020. mnet50.com