chapter 10: trees
DESCRIPTION
Chapter 10: Trees. Applications. Implementations. Traversals. Balancing. CS 240. 182. Sample Tree. Tree Abstract Data Type. Root. The tree abstract data type provides a hierarchical structure to the representation of certain types of relationships. Leaf. Node. Leaf. - PowerPoint PPT PresentationTRANSCRIPT
Chapter 10: Trees
APPLICATIONS
IMPLEMENTATIONS
CS 240 1
TRAVERSALS
BALANCING
CS 240 2
Tree Abstract Data Type
Sample Tree
Sample Tree
In addition to constructors and a destructor, the tree ADT needs the following functions:• isEmpty - to determine whether
the tree contains any entries• insert - to insert a new entry at
the appropriate location within the tree
• traverse - some mechanism to explore the tree
The tree abstract data type provides a hierarchical structure to the representation of certain types of relationships.
Root
Leaf Node
Node Node
Leaf
Node LeafLeaf Node
LeafLeaf NodeNode Leaf
Leaf LeafNode
Leaf Leaf
CS 240 3
Binary Tree ADTThe binary tree abstract data type is a structure in which entries are configured to have at most two offspring.The binary aspect of this structure makes it particularly useful in applications where individual choices are needed, making it possible to recursively proceed to the right offspring or to the left offspring of the current node.For example, video games frequently present a player with two options, using a game tree to keep track of the alternatives selected by the player.
Sample Binary TreeSample Binary TreeRoot
Leaf Node
Node
Node Leaf
LeafNodeNode
LeafLeafNode
Leaf Leaf
Node
Node
CS 240 4
Basic Binary Tree Terminology
value
value value
value value value value
value
value value
value
value
value
value
value valueleaf nodesleaf nodes
sibling nodessibling nodes
parent nodeparent node
child nodechild node
CS 240 5
Application: Heap
1000
875 650
325 700 575
400
25
250
350
425 150
275
75
100
By keeping the data in each node greater than or equal to the data in both of its subtrees, the nodes with the highest values are closer to the root and, consequently, more accessible.
CS 240 6
Application: Binary Search Tree
This structure keeps the data in each node's left subtree less than or equal to the node's data, and the data in the node's right subtree greater than or equal to the node's data.
When the data is well distributed, this significantly reduces data search time.
72
41 90
23 50 74
8214
97
79 98
67
31
9938
CS 240 7
Application: Binary Expression TreeThis structure strategically places the binary operators in the upper nodes, and the primitive operands in the leaf nodes, facilitating the quick evaluation of the overall expression.
Example: (A+B)/C+D*((E+F)%(G-H))
+
/ *
+ C D
+A
%
E GF H
-B
CS 240 8
Binary Tree: Array Implementation· Place root in slot 0· Place left child of slot k's node in slot 2
* k + 1· Place right child of slot k's node in slot
2 * k + 2· Locate parent of slot k's node in slot (k
- 1) / 2
00
11
22
33
44
55
66
77
88
99
1010
1111
1212
1313
1414
1515
1616
1717
1818
1919
2020
2121
2222
2323
2424
2525
2626
2727
2828
2929
3030
3131
3232
3333
3434
3535
3636
3737
3838
3939
4040
4141
4242
4343
4444
4545
4646
4747
4848
4949
5050
5151
5252
5353
5454
5555
5656
5757
5858
5959
6060
6161
6262
6363
6464
6565
6666
6767
6868
6969
7070
7171
7272
7373
7474
7575
7676
7777
7878
7979
8080
8181
8282
8383
8484
8585
8686
8787
8888
8989
9090
9191
9292
9393
9494
9595
9696
9797
9898
9999
100100
101101
102102
103103
104104
105105
106106
107107
108108
109109
110110
111111
112112
113113
114114
115115
116116
117117
118118
119119
120120
121121
122122
123123
124124
125125
126126
127127
128128
129129
130130
131131
132132
133133
134134
135135
136136
137137
138138
139139
140140
141141
142142
143143
144144
145145
146146
147147
148148
149149
150150
151151
152152
153153
154154
155155
156156
157157
158158
159159
160160
161161
162162
163163
164164
165165
166166
167167
168168
169169
170170
171171
172172
173173
174174
175175
176176
177177
178178
179179
180180
181181
182182
183183
184184
185185
186186
187187
188188
189189
190190
191191
192192
193193
194194
195195
196196
197197
198198
199199
200200
201201
202202
203203
204204
205205
206206
207207
208208
209209
210210
211211
212212
213213
214214
215215
216216
217217
218218
219219
220220
221221
222222
223223
CS 240 9
Binary Tree: Array Implementation// Class declaration file: BinTree.h
/////////////////////////////////////////////////////////////////////////// This file contains the array implementation of the binary tree ADT. ///////////////////////////////////////////////////////////////////////////
#include <assert.h>#include <iostream>#include <iomanip>
using namespace std;
#ifndef BIN_TREE_H
////////////////////////////////////////////////////////////// DECLARATION SECTION FOR THE BINARY TREE CLASS TEMPLATE //////////////////////////////////////////////////////////////
const int MAX_TREE_NODES = 15;
template <class E> class binary_tree{ public: // Class constructors binary_tree(); binary_tree(const binary_tree<E> &bt);
// Member functions bool isEmpty(); void insert(const E &item); void preorder_traverse(int location); void inorder_traverse(int location); void postorder_traverse(int location); binary_tree<E>& operator = (const binary_tree<E> &bt); void display_array();
CS 240 10
protected: // Data members struct node // The node structure is { // set up so that if the bool vacant; // vacant field is FALSE, E data; // then the data field is }; // irrelevant.
node tree[MAX_TREE_NODES]; // Array of nodes int number_nodes; // Number of nodes in tree
// Member function void insertStartingHere(const E &item, int location);};
///////////////////////////////////////////////////////////////// IMPLEMENTATION SECTION FOR THE BINARY TREE CLASS TEMPLATE /////////////////////////////////////////////////////////////////
// Default constructor. //template <class E>binary_tree<E>::binary_tree(){ number_nodes = 0; for (int i = 0; i < MAX_TREE_NODES; i++) tree[i].vacant = true;}
// Copy constructor. //template <class E>binary_tree<E>::binary_tree(const binary_tree<E> &bt){ number_nodes = bt.number_nodes; for (int i = 0; i < MAX_TREE_NODES; i++) if (bt.tree[i].vacant) tree[i].vacant = true; else tree[i] = bt.tree[i];}
CS 240 11
// Assignment operator. //template <class E>binary_tree<E>& binary_tree<E>::operator = (const binary_tree<E> &bt){ number_nodes = bt.number_nodes; for (int i = 0; i < MAX_TREE_NODES; i++) if (bt.tree[i].vacant) tree[i].vacant = true; else tree[i] = bt.tree[i]; return *this;}
// Empty function. //template <class E>bool binary_tree<E>::isEmpty(){ return (number_nodes == 0);}
// Insert function; inserts via insertStartingHere function. //template <class E>void binary_tree<E>::insert(const E &item){ assert(number_nodes < MAX_TREE_NODES); // Room in tree? insertStartingHere(item, 0); number_nodes++;}
CS 240 12
// Preorder_traverse function; prints tree contents in preorder. //template <class E>void binary_tree<E>::preorder_traverse(int location){ if ((location < MAX_TREE_NODES) && (!tree[location].vacant)) { cout << tree[location].data << '\t'; preorder_traverse(2 * location + 1); preorder_traverse(2 * location + 2); } return;}
// Inorder_traverse function; prints tree contents in inorder. //template <class E>void binary_tree<E>::inorder_traverse(int location){ if ((location < MAX_TREE_NODES) && (!tree[location].vacant)) { inorder_traverse(2 * location + 1); cout << tree[location].data << '\t'; inorder_traverse(2 * location + 2); } return;}
// Postorder_traverse function; prints tree contents in postorder. //template <class E>void binary_tree<E>::postorder_traverse(int location){ if ((location < MAX_TREE_NODES) && (!tree[location].vacant)) { postorder_traverse(2 * location + 1); postorder_traverse(2 * location + 2); cout << tree[location].data << '\t'; } return;}
CS 240 13
// Display_array function; prints tree contents as an //// array, printing the word vacant if a node is vacant. //template <class E>void binary_tree<E>::display_array(){ int index; for (int i = 0; i < MAX_TREE_NODES/5; i++) { for (int j = 0; j < 5; j++) { index = (MAX_TREE_NODES / 5) * j + i; cout << setw(2) << index << " = "; if (tree[index].vacant) cout << setw(6) << "vacant" << setw(3) << ""; else cout << setw(6) << tree[index].data << setw(3) << ""; } cout << endl; }}
// InsertStartingHere function; inserts item into tree using ordering (i.e., //// inserting smaller values to the left and larger values to the right). //template <class E>void binary_tree<E>::insertStartingHere(const E &item, int location){ assert(location < MAX_TREE_NODES); // Must be legitimate array index if (tree[location].vacant) { tree[location].data = item; tree[location].vacant = false; } else if (item < tree[location].data) insertStartingHere(item, 2 * location + 1); else insertStartingHere(item, 2 * location + 2);}
#define BIN_TREE_H#endif
CS 240 14
Sample Driver For Array Implementation// Program file: TreeDriver.cpp
//////////////////////////////////// This program illustrates the //// creation of a binary tree. ////////////////////////////////////
#include <iostream>#include "BinTree.h"
using namespace std;
void print_tree(binary_tree<int> &tree);
// The main function queries the //// user for new tree elements. //void main(){ binary_tree<int> tree; int number;
cout << "Enter a number: "; cin >> number; while (number > 0) { tree.insert(number); tree.display_array(); cout << "Enter a number: "; cin >> number; }
print_tree(tree);}
// The print_tree function outputs the final //// contents of the tree, first by displaying //// the entire array, then by printing out //// the non-vacant tree elements in inorder, //// preorder, and postorder. //void print_tree(binary_tree<int> &tree){ cout << "Array contents:" << endl; tree.display_array(); cout << endl << "Inorder traversal: " << endl; tree.inorder_traverse(0); cout << endl; cout << "Preorder traversal: " << endl; tree.preorder_traverse(0); cout << endl; cout << "Postorder traversal: " << endl; tree.postorder_traverse(0); cout << endl << endl;}
CS 240 15
CS 240 16
Application For Array Implementation: Heaps// Class declaration file: heap.h //// This file contains the array //// implementation of the heap ADT. //#include "BinTree.h"#include <assert.h>#include <iostream>
#ifndef HEAP_H
// DECLARATION SECTION FOR // THE HEAP CLASS TEMPLATE
template <class E>class heap: public binary_tree<E>{ public: // Class constructors heap(); heap(const heap<E> &h);
// Member functions void insert(const E &item); heap<E>& operator = (const heap<E> &h);};
// IMPLEMENTATION SECTION FOR // THE HEAP CLASS TEMPLATE
// Default constructor. //template <class E>heap<E>::heap(): binary_tree<E>(){}
// Copy constructor. //template <class E>heap<E>::heap(const heap<E> &h){ number_nodes = h.number_nodes; for (int i=0; i<MAX_TREE_NODES; ++i) if (h.tree[i].vacant) tree[i].vacant = true; else tree[i] = h.tree[i];}
// Assignment operator. //template <class E>heap<E>& heap<E>::operator = (const heap<E> &h){ number_nodes = h.number_nodes; for (int i=0; i<MAX_TREE_NODES; ++i) if (h.tree[i].vacant) tree[i].vacant = true; else tree[i] = h.tree[i];return *this;}
CS 240 17
// Insert function; inserts into the heap to retain the heap structure //// and to ensure non-fragmentation of the array structure, moving low //// elements down when a high element is inserted. //template <class E>void heap<E>::insert(const E &item){ int location, parent; assert(number_nodes < MAX_TREE_NODES);
// Now walk the new item up the tree, starting at location location = number_nodes; parent = (location-1) / 2; while ((location > 0) && (tree[parent].data < item)) { tree[location] = tree[parent]; location = parent; parent = (location-1) / 2; } tree[location].data = item; tree[location].vacant = false; number_nodes++;}
#define HEAP_H#endif
CS 240 18
Example Driver For The Heap Application
// Program file: heapdriv.cpp //// This program illustrates //// the creation of a heap. //#include <iostream>#include "heap.h"
void print_heap(heap<int> &tree);
// The main function queries the //// user for new heap elements. //void main(){ heap<int> tree; int number;
cout << "Enter a number: "; cin >> number; while (number > 0) { tree.insert(number); tree.display_array(); cout << "Enter a number: "; cin >> number; } print_heap(tree);}
// The print_heap function outputs //// the final contents of the heap, //// first by displaying the entire //// array, then by printing out the //// non-vacant heap elements in in- //// order, preorder, and postorder. //void print_heap(heap<int> &tree){ cout << "Array contents: \n"; tree.display_array(); cout << "\nInorder traversal: \n"; tree.inorder_traverse(0); cout << "\nPreorder traversal: \n"; tree.preorder_traverse(0); cout << "\nPostorder traversal: \n"; tree.postorder_traverse(0); cout << endl << endl;}
CS 240 19
CS 240 20
Binary Tree: Linked List Implementation// Class declaration file: bintree.h
// This file contains the array implementation of the binary tree ADT. //#ifndef BIN_TREE_H#include <stdlib.h>#include <assert.h>#include <fstream>#include <iomanip>
// DECLARATION SECTION FOR LINKED VERSION OF BINARY TREE
template <class E> class binary_tree{ public: // Class constructors and destructor binary_tree(); binary_tree(const binary_tree<E> &bt); ~binary_tree();
// Member functions bool isEmpty() const; void insert(const E &item); void preorderTraverse(ofstream &os) const; void inorderTraverse(ofstream &os) const; void postorderTraverse(ofstream &os) const; binary_tree<E>& operator = (const binary_tree<E> &bt);
CS 240 21
protected: // Data members struct node; typedef node *node_ptr; struct node { E data; node_ptr left; node_ptr right; };
node_ptr root;
// Member functions node_ptr get_node(const E &data);
void insertIntoTree(node_ptr &treeRoot, const E &data); void copyTree(node_ptr treeRoot) const; void destroyTree(node_ptr treeRoot);
void preorderOutput(node_ptr treeRoot, ofstream &os) const; void inorderOutput(node_ptr treeRoot, ofstream &os) const; void postorderOutput(node_ptr treeRoot, ofstream &os) const;};
// IMPLEMENTATION SECTION FOR LINKED VERSION OF BINARY TREE
// Default constructor. //template <class E>binary_tree<E>::binary_tree(){ root = NULL;}
CS 240 22
// Copy constructor. //template <class E>binary_tree<E>::binary_tree(const binary_tree<E> &bt){ root = NULL; copyTree(bt.root)}
// Assignment operator. //template <class E>binary_tree<E>& binary_tree<E>::operator = (const binary_tree<E> &bt){ root = NULL; copyTree(bt.root); return *this;}
// Function copyTree: Starting at the node pointed to by //// parameter treeRoot, this function recursively copies the //// elements of an existing tree into the *this tree. By //// proceeding in a preorder fashion, this function ensures //// that the elements of *this will correspond exactly (in //// value and position) to the elements of the existing tree. //template <class E>void binary_tree<E>::copyTree(node_ptr treeRoot) const{ if (treeRoot != NULL) { insert(treeRoot->data); copyTree(treeRoot->left); copyTree(treeRoot->right); }}
CS 240 23
// Destructor. //template <class E>binary_tree<E>::~binary_tree(){ destroyTree(root); root = NULL;}
// Function destroyTree: returns all memory associated with //// the tree to the system heap, by recursively destroying //// the two subtrees and then deleting the root. //template <class E>void binary_tree<E>::destroyTree(node_ptr treeRoot){ if (treeRoot != NULL) { destroyTree(treeRoot->left); destroyTree(treeRoot->right); delete treeRoot; }}
// Empty function. //template <class E>bool binary_tree<E>::isEmpty() const{ return root == NULL;}
CS 240 24
// Insert function. //template <class E>void binary_tree<E>::insert(const E &item){ insertIntoTree(root, item);}
// Function insertIntoTree: recursively inserts parameter //// item into tree using binary search tree assumption. //template <class E>void binary_tree<E>::insertIntoTree(node_ptr &treeRoot, const E &item){ if (treeRoot == NULL) treeRoot = get_node(item); else if (item < treeRoot->data) insertIntoTree(treeRoot->left, item); else insertIntoTree(treeRoot->right, item);}
CS 240 25
// Function preorderTraverse. //template <class E>void binary_tree<E>::preorderTraverse(ofstream &os) const{ preorderOutput(root, os);}
// Function preorderOutput: Outputs contents of //// the tree into parameterized file by recursively //// traversing the tree in preorder. //template <class E>void binary_tree<E>::preorderOutput(node_ptr treeRoot, ofstream &os) const{ if (treeRoot != NULL) { os << treeRoot->data << endl; preorderOutput(treeRoot->left, os); preorderOutput(treeRoot->right, os); }}
CS 240 26
// Function inorderTraverse. //template <class E>void binary_tree<E>::inorderTraverse(ofstream &os) const{ inorderOutput(root, os);}
// Function inorderOutput: Outputs contents of //// the tree into parameterized file by recursively //// traversing the tree in inorder. //template <class E>void binary_tree<E>::inorderOutput(node_ptr treeRoot, ofstream &os) const{ if (treeRoot != NULL) { inorderOutput(treeRoot->left, os); os << treeRoot->data << endl; inorderOutput(treeRoot->right, os); }}
CS 240 27
// Function postorderTraverse. //template <class E>void binary_tree<E>::postorderTraverse(ofstream &os) const{ postorderOutput(root, os);}
// Function postorderOutput: Outputs contents of //// the tree into parameterized file by recursively //// traversing the tree in postorder. //template <class E>void binary_tree<E>::postorderOutput(node_ptr treeRoot, ofstream &os) const{ if (treeRoot != NULL) { postorderOutput(treeRoot->left, os); postorderOutput(treeRoot->right, os); os << treeRoot->data << endl; }}
CS 240 28
// Function get_node: Dynamically allocates space for a new //// tree node, placing the parameter item's value into the //// new node, and initializing both of its pointers to NULL. //template <class E>typename binary_tree<E>::node_ptr binary_tree<E>::get_node(const E &item){ node_ptr temp = new node;
assert(temp != NULL); temp->data = item; temp->left = NULL; temp->right = NULL; return temp;}
#define BIN_TREE_H#endif
CS 240 29
Example Driver For The Linked List Implementation// Program file: testtree.cpp
// This program illustrates working // with a binary tree. The input // consists of an unordered list of // integers from a file. The // output consists of three files // containing the preorder, inorder, // and postorder traversals of the // binary search tree that is con-// structed from the input integers.
#include <iostream>#include <fstream>#include "BinTree.h"
void main(){ int number; ifstream input_file; ofstream pre_file; ofstream in_file; ofstream post_file; binary_tree<int> tree;
input_file.open("numbers.txt"); pre_file.open("preordered.txt"); in_file.open("inordered.txt"); post_file.open("postordered.txt");
input_file >> number; while (!input_file.eof()) { tree.insert(number); input_file >> number; } input_file.close();
tree.preorderTraverse(pre_file); tree.inorderTraverse(in_file); tree.postorderTraverse(post_file);
pre_file.close(); in_file.close(); post_file.close();
return;}
CS 240 30
File Contents
numbers.txt79
54474313497823247842763174294779146896
preordered.txt
7954474313231424423129494778767468787996
inordered.txt
1314232429314243474749546874767878797996
postordered.txt
1429314224231343474947687476787854967979
79
79
96
54
78
7876
74
68
47
49
4713
23
14 24
42
31
29
43
CS 240 31
Additional Tree Traversal Member Function #1// Public function to “spell out” the values from the root to the tree’s //
// leaf nodes, with the results output on a separate line for each leaf. //template <class E>void BinaryTree<E>::spellFromTop(ofstream &os){ continueSpelling(root, os, "");}
// Protected function to concatenate the char value of the current subtree’s //// root to the str param. until a leaf is reached, whereupon str is output. //template <class E>void BinaryTree<E>::continueSpelling(nodePtr treeRoot, ofstream &os, string str){ if (treeRoot == NULL) return; else { str += char(treeRoot->data); if ((treeRoot->left == NULL) && (treeRoot->right == NULL)) os << str << endl; else { continueSpelling(treeRoot->left, os, str); continueSpelling(treeRoot->right, os, str); } }}
CS 240 32
Applying spellFromTop To A Sample Tree
str: “M”
str: “MI”
str: “MIL”
str: “MICE”
output “MICE”
str: “MIC”
str: “MILL”
output “MILL”
str: “MILK”
output “MILK”
str: “MO”
str: “MOP”
output “MOM”
str: “MOM”
str: “MOPS”
output “MOPS”
M
I
C
E
L
K L
O
M P
S
CS 240 33
Additional Tree Traversal Member Function #2// Public function to output the sequence of left and right offspring that //
// comprise the longest path from the tree’s root to one of its leaf nodes. //template <class E>void BinaryTree<E>::outputLongestPath(ofstream &os){ os << longestPath(root);}
// Protected function to recursively generate a string indicating which //// offspring (left or right) yield the longest path from the root to a leaf. //template <class E>string BinaryTree<E>::longestPath(nodePtr treeRoot){ if (treeRoot == NULL) return ""; else { string leftStr = longestPath(treeRoot->left); string rightStr = longestPath(treeRoot->right); if (leftStr.size() > rightStr.size()) { leftStr.insert(0, 'L'); return leftStr; } else { rightStr.insert(0, 'R'); return rightStr; } }}
CS 240 34
Applying outputLongest To A Sample Tree
leftStr: “”rightStr: “”
leftStr: “”rightStr: “”
leftStr: “L”rightStr: “R”
leftStr: “”rightStr:
“RR”
leftStr: “LRR”rightStr:
“”
leftStr: “”rightStr:
“”
leftStr: “”rightStr:
“”
leftStr: “L”rightStr:
“”
leftStr: “L”rightStr:
“”
leftStr: “LL”rightStr: “RL”
leftStr: “LLRR”rightStr: “RRL”
“”“” “”“”
“R”“R”
“RR”“RR”
“LRR”“LRR”
“”“” “”“”
“L”“L” “L”“L”
“RL”“RL”
Final result: “LLRR”Final result: “LLRR”
CS 240 35
AVL TreesAlthough an average search in an n-node binary search tree is O(logn), the worst case could be as bad as O(n).
So when a new element’s insertion causes an imbalance, “rotate” the tree to restore the balance.
k1
k2
X
ZY
k2
k1
Z
YXAn AVL (Adelson-Velskii and Landis) tree places a balance condition on a binary search tree by requiring the left and right subtrees of each node to have heights differing by at most one.During insertion, this is accomplished by means of single and double rotations.Single rotations: Note that in each of the trees illustrated at right: (any element of X) k1
(any element of Y) k2
(any element of Z)
CS 240 36
Single Rotation Examples
27
14
31
12
30
45
27
14
31
12
30
45
5
27
12
31
5 30
45
14
INSERT 5INSERT 5 ROTATEROTATE
84
75
93
92
98
84
75
93
92
98
99
93
84
98
75
99
92
INSERT 99INSERT 99 ROTATEROTATE
CS 240 37
Double RotationsIf a single rotation doesn’t restore balance, a double rotation will.
k3
k2
DCB
k1
A
k3
k2
DCB
k1
A
H
k3
k2
HGF
k1
E
k3
k2
GF
k1
E
Also note that in the two trees illustrated at right: (any value in E) k1 (any value in F) k2 (any value in G) k3 (any value in H)If a single rotation fails to restore balance after a new insertion, a double rotation may be tried.
Note that in the two trees illustrated at right: (any value in A) k1 (any value in B) k2 (any value in C) k3 (any value in D)
CS 240 38
Double Rotation Example
INSERT 47
INSERT 47
SINGLE ROTATIO
N
SINGLE ROTATIO
N
25
16
49
9 36
64
19
31
41
25
16
49
9 36
64
19
31
41
47
25
16
36
9 31
49
19
41
64
47
STILL UNBALANCED
25
16
49
9 36
64
19
31
41
INSERT 47
INSERT 47
25
16
49
9 36
64
19
31
41
47
DOUBLE ROTATIO
N
DOUBLE ROTATIO
N
25
16
41
9 36
49
19
47
64
31
BALANCED!
CS 240 39
General Trees
26
873
451
826
473
51
826
753
41
826
753
41
82
736
451
826
731
45
86
723
451
86
723
451
There are occasions when non-binary trees are needed
Application Example: Game Trees
86
723
451
826
73
451
826
753
41
826
73
451
826
73
451
CS 240 40
Binary Implementation Of General TreesA common approach for implementing general trees is to use binary trees, with offspring and sibling pointers
A
D
F I NMLK
E
J
B C
HG
A
D
F
I
N
M
L
K
EJ
B
C
H
G
Actual tree
Binary representation, withleft pointers for “first
offspring”and right pointers for “next
sibling”