4.4 AVL Trees AVL (Adelson-Velskii and Landis) tree เป็น binary search tree กำกับด้วยเงื่อนไขของการสมดุล และประกันว่า depth ของ tree จะเป็น O(log n) อย่างง่ายที่สุดคือกำหนดว่า left และ right subtrees มี height เท่ากัน ดัง Figure 4.28 ซึ่งไม่ทำให้ tree ตื้นได้ เงื่อนไขการสมดุลอีกอย่าง คือ ทุก ๆ node จะต้องมี left และ right subtrees ที่มี height เท่ากัน ถ้ากำหนดให้ height ของ empty subtree เป็น -1 (ใช้กันทั่วไป) แล้ว จะมีก็แต่เฉพาะ tree ที่เป็น perfect balanced (มี 2k - 1 โนด) เท่านั้นที่เป็นไปตามข้อกำหนดนี้ เงื่อนไขการสมดุลนี้เข้มงวดเกินไปจนอาจใช้ประโยชน์ได้ไม่ดี
4.4. AVL Trees Figure 4.28 A bad binary tree. Requiring balance at the root is not enough.
4.4. AVL Trees AVL tree คือ binary search tree ที่ทุกโนดใน tree มี height ของ left และ right subtrees แตกต่างกันไม่เกิน 1 (height ของ empty tree ให้เป็น -1) 6 2 4 1 8 3 7 6 2 4 1 8 3 5 Figure 4.29 Binary search trees 2 ตัวที่ด้านซ้ายมือเป็น AVL
จำนวนโนดที่น้อยที่สุด (S(h)) ใน AVL tree ที่มี height เป็น h หาได้ด้วย 4.4. AVL Trees AVL tree ที่มี height = 9 และมีจำนวนโนดน้อยที่สุด (143) แสดงใน Figure 4.30 โดย tree นี้มี left subtree เป็น AVL tree ที่มี height = 7 ด้วยขนาดเล็กที่สุด และมี right subtree เป็น AVL tree ที่มี height = 8 ด้วยขนาดเล็กที่สุดเช่นกัน จำนวนโนดที่น้อยที่สุด (S(h)) ใน AVL tree ที่มี height เป็น h หาได้ด้วย S(h) = S(h -1) + S(h - 2) + 1 สำหรับ h = 0, มี S(h) = 1 และ h = 1, มี S(h) = 2 AVL tree height ประมาณ 1.44log(N+2)-1.382 N เป็นจำนวน node
Figure 4.30 AVL tree ที่เล็กที่สุดที่ height = 9
tree operations ทั้งหมดใช้เวลาเป็น O(log n), ยกเว้นการบรรจุค่าใหม่ 4.4. AVL Trees tree operations ทั้งหมดใช้เวลาเป็น O(log n), ยกเว้นการบรรจุค่าใหม่ ขณะที่เราเพิ่มค่าใหม่ หรือ ลบค่า เราจะต้องทำการปรับปรุงข้อมูลการสมดุลของ node ในเส้นทางการ traverse กลับขึ้นถึงไปจนถึงroot เนื่องจากการเพิ่มหรือลบอาจทำให้คุณสมบัติความเป็น AVL tree เสียหายได้ เช่นเพิ่ม 7 ลงใน AVL tree ของรูปที่ 4.29 การปรับปรุงให้รักษาคุณสมบัติความเป็น AVL tree นั้นทำได้ด้วยการ rotation
การเสียสมดุลอาจเกิดขึ้นใน 4 กรณี ดังนี้: 4.4. AVL Trees ให้ node ที่ต้องการปรับสมดุล ว่า โดย มีความต่างของ height ระหว่าง left subtree กับ right subtree มากกว่า 1 “หนักกว่า” การเสียสมดุลอาจเกิดขึ้นใน 4 กรณี ดังนี้: น้ำหนักเกิดขึ้นใน left subtree ของ left child ของ น้ำหนักเกิดขึ้นใน right subtree ของ left child ของ น้ำหนักเกิดขึ้นใน left subtree ของ right child ของ น้ำหนักเกิดขึ้นใน right subtree ของ right child ของ กรณี 1 และ 4 เป็น mirror image symmetries กันเมื่อเทียบกับ , เช่นเดียวกับกรณี 2 และ 3 –ดังนั้นโดยพื้นฐานแล้วจึงเหลือ 2 กรณีเท่านั้น
กรณีที่ 1 น้ำหนักเกิดขึ้นทางด้านนอก "outside“ 4.4. AVL Trees กรณีที่ 1 น้ำหนักเกิดขึ้นทางด้านนอก "outside“ left-left หรือ right-right จัดการได้ด้วย single rotation กรณีที่ 2 น้ำหนักเกิดขึ้นทางด้านใน "inside“ left-right หรือ right-left จัดการได้ด้วย double rotation
Figure 4.31 Single rotation rotation เป็นเพียงการปรับเปลี่ยน pointer สามครั้งเท่านั้น และ ทำให้โครงสร้างของ tree เปลี่ยนไปโดยยังคงรักษาคุณสมบัติการเป็น search tree ไว้ k2 k1 X Y Z k2 k1 X Y Z X < k1 < Y < k2 <Z Figure 4.31 Single rotation
4.4.1. Single Rotation 5 2 4 1 8 3 7 6 5 2 4 1 7 3 6 8 Figure 4.32 คุณสมบัติ AVL เสียหายไปเมื่อบรรจุ 6 และแก้ไขด้วย rotation เพียงครั้งเดียว
4.4.1. Single Rotation ถ้าเริ่มต้นด้วย AVL tree ว่างและบรรจุค่า 3, 2, 1 และ 4 ถึง 7 เรียงตามลำดับ Add 3 Add 2 Add 1 Rotate 2 3 3 2 1 Add 4 Add 5 Rotate 3 4 4 5 2 1 3
4.4.1. Single Rotation 5 3 2 1 4 6 Add 6 Rotate 2 4 5 3 2 1 4 6 5 3 2 1 4 6 7 6 3 2 1 4 7 5 Add 7 Rotate 5 6
4.4.2. Double Rotation ไม่สามารถใช้ Single rotation สำหรับกรณี 2 และ 3 ได้ ดังแสดงใน Figure 4.34 k2 k1 X Y Z k2 k1 X Y Z Height Y คงที่
การใช้ double rotation แสดงไว้ใน Figure 4.35 และ Figure 4.36 k3 k1 A D k2 B C k3 k1 A D k2 B C k3 k1 A D k2 B C Figure 4.35 Left-right double rotation สำหรับกรณี 2
Figure 4.36 Right-left rotation สำหรับกรณี 3 4.4.2. Double Rotation k1 k3 A D k2 B C k1 k3 D A k2 C B k3 k1 A D k2 B C Figure 4.36 Right-left rotation สำหรับกรณี 3
4.4.2. Double Rotation ตัวอย่างต่อเนื่องจากตัวอย่างก่อนหน้าโดยการบรรจุ 16 ถึง 10 แล้วตามด้วย 8 และ 9 6 3 2 1 4 7 5 16 15 6 3 2 1 4 7 5 16 15 Add 16 Add 17 Double Rotate 15 16 7
Add 14 Double Rotate 7 15 6 Add 13 Rotate 4 7 6 3 2 1 4 7 5 7 3 2 1 4 4.4.2. Double Rotation 15 6 3 2 1 4 7 5 16 14 15 7 3 2 1 4 5 6 16 14 Add 14 Double Rotate 7 15 6 15 7 3 2 1 4 5 6 16 14 13 7 3 2 1 4 5 6 16 15 14 13 Add 13 Rotate 4 7
Add 12 Rotate 13 14 Add 11 Rotate Add 10 rotate Add 8 7 3 2 1 4 5 6 7 4.4.2. Double Rotation Add 12 Rotate 13 14 7 3 2 1 4 5 6 16 15 14 13 12 7 3 2 1 4 5 6 16 15 13 12 14 Add 11 Rotate Add 10 rotate Add 8 7 3 2 1 4 5 6 15 13 11 10 12 14 16 8
การเขียนโปรแกรมก็ไม่ซับซ้อน เพียงแต่มีกรณีการเลือกหลายตัวเลือกเท่านั้น 4.4.2. Double Rotation การเขียนโปรแกรมก็ไม่ซับซ้อน เพียงแต่มีกรณีการเลือกหลายตัวเลือกเท่านั้น ในการเพิ่ม node ใหม่ที่มีค่า X เข้าใน AVL tree T เราจะทำการเพิ่มลงใน subtree ของ T (ต่อไปเรียกว่า TLR) ถ้า height ของ TLR ไม่เปลี่ยนก็ไม่ต้องทำอะไรต่อไป แต่ถ้าเกิดความไม่สมดุลของ height ขึ้นใน T เราก็จะทำการ single หรือ double rotation แล้วแต่กรณีขึ้นอยู่กับค่า X และข้อมูลใน T และ TLR, และทำการปรับปรุงค่า heights
ใช้การลบแบบเดียวกับ Binary search three การลบ ข้อมูล ใช้การลบแบบเดียวกับ Binary search three ซึ่งเป็นการลบ Node ที่ลึกที่สุด ที่เป็น leaf หรือ node ที่มี child เดียว ตามทางจาก leaf ตัวใหม่ ขึ้นไปจนถึง root พิจารณา node x ว่า height ต่างกันเกิน 1 หรือไม่ ถ้าไม่ ขึ้นไปทำงานกับ parent ถ้า height ต่างกัน เกิน 1 rotate ที่ x ตามกรณี หลังจากนั้น ขึ้นไปทำงานกับ parent
Routine ในโปรแกรม ******************PUBLIC OPERATIONS********************* // void insert( x ) --> Insert x // void remove( x ) --> Remove x (unimplemented) // Comparable find( x ) --> Return item that matches x // Comparable findMin( ) --> Return smallest item // Comparable findMax( ) --> Return largest item // boolean isEmpty( ) --> Return true if empty; else false // void makeEmpty( ) --> Remove all items // void printTree( ) --> Print tree in sorted order
Figure 4.37 การประกาศโนดของ AVL trees class AvlNode { AvlNode( Comparable theElement ){ this( theElement, null, null ); } AvlNode( Comparable theElement, AvlNode lt, AvlNode rt ) { element = theElement; left = lt; right = rt; height = 0; } // Friendly data; accessible by other package routines Comparable element; // The data in the node AvlNode left; // Left child AvlNode right; // Right child int height; // Height } Figure 4.37 การประกาศโนดของ AVL trees
AVL tree class public class AvlTree { public AvlTree( ) { root = null; } public void insert( Comparable x ){ root =insert( x, root );} public void remove( Comparable x ) { System.out.println( "Sorry, remove unimplemented" ); } public Comparable findMin( ) { return elementAt( findMin( root ) ); } public Comparable findMax( ) { return elementAt( findMax( root ) ); } public Comparable find( Comparable x ) { return elementAt( find( x, root )); } public void makeEmpty( ) { root = null; } public boolean isEmpty( ) { return root == null; } // Other private functions go here /** The tree root. */ private AvlNode root;
Figure 4.38 Method สำหรับคำนวณ height ของ AVL trees /** * Return the height of node t, or -1, if null. */ private static int height( AvlNode t ) { return t == null ? -1 : t.height; }
Figure 4.39 การบรรจุค่าลงใน AVL trees private AvlNode insert( Comparable x, AvlNode t ) { if ( t == null ) t = new AvlNode( x, null, null ); else if(x.compareTo( t.element ) < 0 ) t.left = insert( x, t.left ); if(height(t.left)-height(t.right)==2 ) if(x.compareTo(t.left.element) < 0 ) t = rotateWithLeftChild( t ); else t = doubleWithLeftChild( t ); } else if(x.compareTo(t.element) > 0 ) { t.right = insert( x, t.right ); if(height(t.right)-height(t.left)==2) if (x.compareTo(t.right.element) >0) t = rotateWithRightChild( t ); else t = doubleWithRightChild( t ); } ; // Duplicate; do nothing t.height = max(height( t.left ), height( t.right )) + 1; return t;
Figure 4.41 Routine สำหรับ single rotation /* Rotate binary tree node with left child. * For AVL trees, this is a single rotation for case 1. * Update heights, then return new root. */ private static AvlNode rotateWithLeftChild( AvlNode k2 ) { AvlNode k1 = k2.left; k2.left = k1.right; k1.right = k2; k2.height = max( height( k2.left ), height( k2.right )) + 1; k1.height = max( height( k1.left ), k2.height ) + 1; return k1; }
rotateWithRightChild /** * Rotate binary tree node with right child. * For AVL trees, this is a single rotation for case 4. * Update heights, then return new root. */ private static AvlNode rotateWithRightChild(AvlNode k1 ) { AvlNode k2 = k1.right; k1.right = k2.left; k2.left = k1; k1.height = max( height( k1.left ), height( k1.right ) ) + 1; k2.height = max( height( k2.right ), k1.height ) + 1; return k2; }
Figure 4.43 Routine สำหรับ double rotation /** * Double rotate binary tree node: * first left child with its right child; * then node k3 with new left child. * For AVL trees, this is a double rotation for case 2. * Update heights, then return new root. */ private static AvlNode doubleWithLeftChild( AvlNode k3 ) { k3.left = rotateWithRightChild( k3.left ); return rotateWithLeftChild( k3 ); }
Figure 4.43 (ต่อ) /** * Double rotate binary tree node: * first right child with its left child; * then node k1 with new right child. * For AVL trees, this is a double rotation for case 3. * Update heights, then return new root. */ private static AvlNode doubleWithRightChild( AvlNode k1 ) { k1.right = rotateWithLeftChild( k1.right ); return rotateWithRightChild( k1 ); }
Node deleteNode(Node root, int key) { // STEP 1: PERFORM STANDARD BST DELETE if (root == null) return root; // If the key to be deleted is smaller than // the root's key, then it lies in left subtree if (key < root.key) root.left = deleteNode(root.left, key); // If the key to be deleted is greater than the // root's key, then it lies in right subtree else if (key > root.key) root.right = deleteNode(root.right, key); // if key is same as root's key, then // this is the node to be deleted else { // node with only one child or no child if ((root.left==null)||(root.right==null)) { Node temp = null; if (temp == root.left) temp = root.right; else temp = root.left; // No child case if (temp == null) { temp = root; root = null; } else // One child case root = temp; // Copy the contents of // the non-empty child } else { // node with two children: Get the inorder // successor (smallest in the right subtree) Node temp = minValueNode(root.right); // Copy the inorder successor's data to this node root.key = temp.key; // Delete the inorder successor root.right = deleteNode(root.right, temp.key); } // If the tree had only one node then return
Node deleteNode(Node root, int key) // STEP 2: UPDATE HEIGHT OF THE CURRENT NODE root.height = max(height(root.left), height(root.right)) + 1; // STEP 3: GET THE BALANCE FACTOR OF THIS NODE // (to check whether this node became unbalanced) int balance = getBalance(root); // If this node becomes unbalanced, // then there are 4 cases // Left Left Case if (balance > 1 && getBalance(root.left) >= 0) return rightRotate(root); // Left Right Case if (balance > 1 && getBalance(root.left) < 0) { root.left = leftRotate(root.left); } // Right Right Case if (balance < -1 && getBalance(root.right) <= 0) return leftRotate(root); // Right Left Case if (balance < -1 && getBalance(root.right) > 0) root.right = rightRotate(root.right); return root; // Get Balance factor of node N int getBalance(Node N) if (N == null) return 0; return height(N.left) - height(N.right); https://www.geeksforgeeks.org/avl-tree-set-2-deletion/
4.5 Splay Trees splay tree ประกันว่าการดำเนินการใด ๆ ที่ต่อเนื่องกัน M ครั้ง โดยเริ่มต้นจาก tree ว่างจะใช้เวลาสูงสุดเป็น O(M log N) ปกติแล้ว ถ้ามีการดำเนินการใด ๆ ที่ต่อเนื่องกัน M ครั้งแล้วมี worst-case running time รวมเป็น O(M f(N)) เราจะเรียกว่ามันมี amortized running time เป็น O(f(N)) ดังนั้น splay tree มี amortized cost per operation เป็น O(log N) การดำเนินการที่ต่อเนื่องกันนาน ๆ อาจจะใช้เวลามากหรือน้อยกว่าที่กล่าวนี้ได้
4.5. Splay Trees ถ้ายินยอมให้มีการดำเนินการใด ๆ มี worst-case time bound เป็น O(N) และเรายังต้องการให้ได้ amortized time bound เป็น O(log N) แล้วละก็ หมายความว่าเมื่อมีการเข้าถึง node ใด ๆ แล้วเราจะต้องทำการเคลื่อนย้าย node นั้นด้วย มิฉะนั้นแล้วถ้าเป็น nodeที่อยู่ในระดับลึก ๆ ที่เราเข้าถึงแล้วเราก็ยังต้องใช้เวลาในการเข้าถึง node นั้น (ที่อยู่ในระดับลึก) สำหรับการดำเนินการครั้งต่อ ๆ มาเสมอ นั่นคือ ถ้าไม่มีการเคลื่อนย้ายตำแหน่งแล้วการเข้าถึงแต่ละครั้งต้องใช้ O(N) ซึ่งการเข้าถึงอย่างต่อเนื่อง M ครั้ง ก็จะต้องใช้เวลาเป็น O(M × N)
4.5. Splay Trees แนวคิดพื้นฐานของ splay tree คือ หลังจากการเข้าถึงโนดใด ๆ แล้ว ก็จะเคลื่อนย้ายโนดนั้นให้มาอยู่ในตำแหน่งรากด้วยวิธี rotations ของ AVL tree หลาย ๆ ครั้ง การจัดโครงสร้างใหม่นี้จะมีผลข้างเคียงต่อสมดุลของ tree ด้วย อย่างไรก็ตาม splay trees ไม่จำเป็นต้องใช้ข้อมูลของ height หรือข้อมูลการสมดุล ดังนั้นจึงประหยัดเนื้อที่และทำให้การเขียนโปรแกรมง่ายขึ้น
4.5.1. แนวคิดอย่างง่าย (ที่ใช้ไม่ได้) การทำ single rotations, bottom up– กล่าวคือการ rotate ทุกโนดบนเส้นทางการเข้าถึงกับ parent ของมัน พิจารณาสิ่งที่เกิดขึ้นเมื่อเข้าถึง k1 ในรูปข้างล่าง k5 F k4 E k3 k2 A D k1 B C
4.5.1. A Simple Idea (That Does Not Work) F k4 E k3 k2 A D k1 B C k5 F k4 E k3 k2 A D k1 B C
4.5.1. A Simple Idea (That Does Not Work) F k2 k4 k3 A B E C D k5 F k4 E k3 k2 A D k1 B C
4.5.1. A Simple Idea (That Does Not Work) การ rotate ทั้งหมดมีผลให้ k1 ถูกดันขึ้นไปอยู่ที่ราก ดังนั้นการเข้าถึง k1 ในคราวต่อ ๆ มาจึงทำได้ง่าย ๆ โชคไม่ดีที่ ในเวลาเดียวกันนั้น มันได้ดันให้โนดอื่น (k3) ลงไปอยู่ในระดับลึกที่ k1 เคยอยู่ และการเข้าถึงโนดนั้น ก็จะดันให้โนดอื่นลงไปในระดับลึกอีก และก็จะเป็นเช่นนี้ไปเรื่อย ๆ นั่นหมายความว่า การดำเนินการที่ต่อเนื่องกัน M ครั้งด้วยวิธีการนี้ต้องใช้เวลาเป็น (M × N) ซึ่งเป็นสิ่งไม่พึงปรารถนา
4.5.1. A Simple Idea (That Does Not Work) พิจารณา tree ที่เกิดจากการบรรจุค่า 1, 2, 3, . . . , N เข้าใน tree ว่าง นี่ทำให้เกิด tree ที่มีเฉพาะลูกทางด้านซ้าย การสร้างนี้ไม่ใช่สิ่งเลวร้าย เนื่องจากใช้เวลาทั้งสิ้นเพียง O(N) เท่านั้น สิ่งที่ไม่ดีคือ การจะเข้าถึงโนดที่มีค่า 1 ใช้เวลา N -1 หน่วยเวลา และหลังจากการ rotate แล้ว การจะเข้าถึงโนดที่มีค่า 2 จะต้องใช้เวลาเป็น N - 2 หน่วยเวลา นั่นคือเวลาทั้งหมดที่ต้องใช้ในการเข้าถึงทุกค่า คือ 𝑖=1 𝑁−1 𝑖 = Ω( 𝑁 2 )
4.5.2. Splaying ยังคงใช้การ rotate จากข้างล่างขึ้นมาตามเส้นการเข้าถึง ให้ X เป็น node (non root) บนเส้นทางการเข้าถึงที่เราจะทำการ rotate ถ้า parent ของ X เป็น root ของ tree ให้ rotate โนด X และroot นี่เป็นการ rotate สุดท้ายที่เกิดขึ้นบนเส้นทาง ถ้า parent ของ X ไม่ใช่ root ก็หมายความว่า X มีทั้ง parent (P) และ grandparent (G) ทำให้เกิดกรณี 2 กรณี (รวมทั้งกรณีสมมาตรของมันด้วย)
4.5.2. Splaying กรณีแรก คือ กรณี zig-zag (ดู Figure. 4.44) กรณีนี้ X เป็น right child และ P เป็น left child (หรือตรงข้ามกัน) เราจะทำการ double rotation เช่นเดียวกับที่ทำใน AVL double rotation กรณีที่สอง คือ กรณี zig-zig: กรณีนี้ ทั้ง X และ P เป็น left children หรือ right children เราจะทำการปรับเปลี่ยน tree ที่อยู่ด้านซ้ายมือของ Figure 4.45 ไปเป็น tree ที่อยู่ด้านขวามือของรูป
Figure 4.44 Zig-zag : rotate x-p rotate x-g D x B C g p A D x B C Figure 4.44 Zig-zag : rotate x-p rotate x-g g x A D p B C x g D A p C B Figure 4.45 Zig-zig : rotate p-g rotate x-p
4.5.2. Splaying พิจารณาการเข้าถึง k1 ของ tree ข้างล่างนี้ (เป็นรูปเดียวกับที่ใช้ในหัวข้อ4.5.1) k5 F k4 E k3 k2 A D k1 B C
การ splay ครั้งแรกเกิดขึ้นที่ k1 และต้องใช้ zig-zag, ดังนั้นจึงใช้วิธี double rotation โดยใช้ k1, k2, และ k3 k5 F k4 E k3 k2 A D k1 B C
4.5.2. Splaying การ splay ของ k1 ครั้งต่อมาเป็น zig-zig ดังนั้นจึงใช้ zig-zig rotation ด้วย k1, k4, และ k5 k5 F k4 E k3 k2 A D k1 B C
พิจารณาการบรรจุค่า 1, 2, 3, . . . , N เข้าใน tree ว่างใหม่ 4.5.2. Splaying พิจารณาการบรรจุค่า 1, 2, 3, . . . , N เข้าใน tree ว่างใหม่ การบรรจุค่าทั้งหมดใช้เวลา O(N) เช่นเดิม Figure 4.46 แสดงผลของการ splay ที่เกิดขึ้นที่โนด 1 เมื่อเราเข้าถึงโนด 1 (ซึ่งใช้เวลา N -1 หน่วยเวลา) แล้ว การเข้าถึงโนด 2 จะใช้เวลาเพียง N/2 หน่วยเวลาเท่านั้น (แทนที่จะเป็น N - 2 หน่วยเวลา) การเข้าถึงโนด 2 จะนำให้โนดต่าง ๆ มาอยู่ในระดับ N/4 จากราก และจะเกิดขึ้นเช่นนี้ไปจนกระทั่งมี depth ประมาณ log N (ในตัวอย่างมี N = 7 ซึ่งอาจเห็นผลได้ไม่ชัดเจนนัก)
Figure 4.46 ผลการ splay ที่โนด 1
Figure 4.47 ผลการ splay ที่โนด 1 ของ tree ที่มีเฉพาะ left children
Figure 4.48 ผลของการ splay โนด 2 ของ tree ที่แล้ว
Figure 4.49 ผลของการ splay โนด 3 ของ tree ที่แล้ว
Figure 4.51 ผลของการ splay โนด 5 ของ tree ที่แล้ว https://www.cs.usfca.edu/~galles/visualization/SplayTree.html
4.5.2. Splaying เพื่อลบค่า เราต้องเข้าถึง node ที่จะลบซึ่งทำให้ node นั้นเลื่อนขึ้นมาอยู่ที่ root แล้วจึงทำการลบ และจะทำให้ tree ถูกแบ่งเป็น 2 subtrees คือ TL และ TR (left และ right) และใช้การหาค่าที่มากที่สุดใน TL (ซึ่งทำได้ง่าย), และ rotate ค่านี้ไปที่รากของ TL, จะได้ TL ที่รากของมันไม่มี right child เราจบการทำงานด้วยการนำ TR มาเป็น right child ของมัน
4.6 Tree Traversals (Revisited) เนื่องจาก binary search tree มีสารสนเทศการจัดเรียงอยู่ในตัว ดังนั้นการแสดงรายการที่เป็นการจัดเรียงลำดับจึงทำได้ง่าย ดังแสดงใน Figure 4.56 ซึ่งเป็น recursive procedure routine นี้ใช้การท่องใน tree แบบ inorder traversal (ในแบบ recursive) วิธีทั่วไปของ inorder traversal คือการประมวลผลตัว left subtree ก่อน ตามด้วยตัวโนดนั้น ๆ และจึงตามด้วย right subtree (ในแบบ recursive)
public void printTree( ) { if ( isEmpty( ) ) System. out public void printTree( ) { if ( isEmpty( ) ) System.out.println( "Empty tree" ); else printTree( root ); } private void printTree( BinaryNode t ) if ( t != null ) printTree( t.left ); System.out.println( t.element ); printTree( t.right ); Figure 4.56 Routine เพื่อพิมพ์ค่าใน binary search tree เรียงตามลำดับค่า
4.6. Tree Traversals (Revisited) running time ของอัลกอริทึมนี้ คือ O(N) เนื่องจาก มันใช้เวลาคงที่ในการทำงานที่แต่ละโนด และมีการเข้าถึงแต่ละโนดเพียงครั้งเดียว โดยงานที่ทำในแต่ละโนดเป็นการตรวจสอบค่า null เรียกใช้ฟังก์ชันสองครั้ง และการพิมพ์ค่า เนื่องจากเป็นการทำงานด้วยเวลาคงที่ในแต่ละโนดและมีทั้งสิ้น N โนด ดังนั้นจึงมี running time เป็น O(N)
4.6. Tree Traversals (Revisited) ในบางกรณีเราอาจจะจำเป็นต้องทำการประมวลผลตัว subtrees ทั้งสองด้านก่อนจะประมวลผลโนดนั้น ๆ เช่นการคำนวณค่า height ของโนด เราจะต้องรู้ค่า height ของ subtrees ก่อน Figure 4.57 เป็นฟังก์ชันเพื่อการนี้ ฟังก์ชันประกาศให้ height ของ leaf เป็นศูนย์ การท่องไปใน tree แบบนี้ เรียกว่า postorder traversal running time ของอัลกอริทึม คือ O(N), เนื่องจากมีการทำงานที่ใช้เวลาคงที่สำหรับแต่ละโนด
Figure 4.57 ฟังก์ชันเพื่อคำนวณ height ของ tree โดยการใช้ postorder traversal /** Compute height of a subtree * t = the node that roots the tree */ private int height( BinaryNode t ) { if ( t == null) return –1; else return max(height( t.left ), height( t.right )) + 1; }
4.6. Tree Traversals (Revisited) การท่องไปใน tree อีกแบบที่ใช้กัน คือ preorder traversal มีการประมวลผลโนดนั้น ๆ ก่อนการประมวลผล children ของมัน เช่นถ้าต้องการแสดงโนดและ depth ของมัน การท่องไปใน tree แบบที่สี่ที่ไม่ค่อนพบเห็นบ่อยนัก คือ level-order traversal จะทำการประมวลโนดในระดับ depth d ก่อนประมวลผลในระดับ depth d + 1 Level-order traversal แตกต่างจากการท่องไปแบบอื่น ๆ คือ มันไม่เป็นแบบ recursive แต่จะใช้ queue