Multiway Tree
Multiway Tree
1. Orchards, Trees, and Binary Trees 2. Lexicographic Search Trees: Tries 3. External Searching: B-Trees 4. Red-Black Trees
402
Denitions:
A (free) tree is any set of points (called vertices) and any set of pairs of distinct vertices (called edges or branches) such that (1) there is a sequence of edges (a path) from any vertex to any other, and (2) there are no circuits, that is, no paths starting from a vertex and returning to the same vertex. A rooted tree is a tree in which one vertex, called the root, is distinguished. An ordered tree is a rooted tree in which the children of each vertex are assigned an order. A forest is a set of trees. We usually assume that all trees in a forest are rooted. An orchard (also called an ordered forest) is an ordered set of ordered trees.
On the Classication of Species Transp. 2, Sect. 11.1, Orchards, Trees, and Binary Trees
403
Various kinds of trees Transp. 3, Sect. 11.1, Orchards, Trees, and Binary Trees 404
Rooted trees with four or fewer vertices (Root is at the top of tree.)
Rotated form:
first_child (left) links: black next_sibling (right) links: color
Implementations of Ordered Trees Transp. 4, Sect. 11.1, Orchards, Trees, and Binary Trees
405
Recursive Denitions
DEFINITION A rooted tree consists of a single vertex v , called the root of the tree, together with a forest F , whose trees are called the subtrees of the root. A forest F is a (possibly empty) set of rooted trees. DEFINITION An ordered tree T consists of a single vertex v , called the root of the tree, together with an orchard O , whose trees are called the subtrees of the root v . We may denote the ordered tree with the ordered pair T = {v, O }. An orchard O is either the empty set , or consists of an ordered tree T , called the rst tree of the orchard, together with another orchard O (which contains the remaining trees of the orchard). We may denote the orchard with the ordered pair O = (T , O ).
First tree v O O
Recursive Denitions Transp. 5, Sect. 11.1, Orchards, Trees, and Binary Trees
406
Delete root
Deleting and adjoining a root Transp. 6, Sect. 11.1, Orchards, Trees, and Binary Trees
407
Ordered tree
Orchard
Ordered tree
DEFINITION A binary tree B is either the empty set or consists of a root vertex v with two binary trees B1 and B2 . We may denote the binary tree with the ordered triple B = [v, B1 , B2 ].
THEOREM 11.1 Let S be any nite set of vertices. There is a one-to-one correspondence f from the set of orchards whose set of vertices is S to the set of binary trees whose set of vertices is S .
Proof. Dene
Dene
f () = .
The Formal Correspondence Transp. 7, Sect. 11.1, Orchards, Trees, and Binary Trees
408
Rotations:
1. Draw the orchard so that the rst child of each vertex is immediately below the vertex. 2. Draw a vertical link from each vertex to its rst child, and draw a horizontal link from each vertex to its next sibling. 3. Remove the remaining original links. 4. Rotate the diagram 45 degrees clockwise, so that the vertical links appear as left links and the horizontal links as right links.
Rotate 45
Orchard
Summary:
Orchards and binary trees correspond by any of:
rst child and next sibling links,
Rotations from orchards to binary trees Transp. 8, Sect. 11.1, Orchards, Trees, and Binary Trees
409
aa
ab
ac
ba
ca
aba
abc
baa
bab
bac
cab
abba
baba
caba
abaca
caaba
Lexicographic Search Trees: Tries Transp. 9, Sect. 11.2, Lexicographic Search Trees: Tries
410
// //
C++ Trie Declarations Transp. 10, Sect. 11.2, Lexicographic Search Trees: Tries
411
Searching a Trie
Error code Trie :: trie search(const Key &target, Record &x) const /* Post: If the search is successful, a code of success is returned, and the output parameter x is set as a copy of the Tries record that holds target. Otherwise, a code of not present is returned. Uses: Methods of class Key. */
{
int position = 0; char next char; Trie node *location = root; while (location != NULL && (next char = target. key letter(position)) != ) { // Terminate search for a NULL location or a blank in the target. location = location->branch[alphabetic order(next char)]; // Move down the appropriate branch of the trie. position ++ ; // Move to the next character of the target. } if (location != NULL && location->data != NULL) { x = *(location->data); return success; } else return not present;
Searching a Trie Transp. 11, Sect. 11.2, Lexicographic Search Trees: Tries
412
Error code result = success; if (root == NULL) root = new Trie node; // Create a new empty Trie. int position = 0; // indexes letters of new entry char next char; Trie node *location = root; // moves through the Trie while (location != NULL && (next char = new entry. key letter(position)) != ) { int next position = alphabetic order(next char); if (location->branch[next position] == NULL) location->branch[next position] = new Trie node; location = location->branch[next position]; position ++ ; } // At this point, we have tested for all nonblank characters of new entry. if (location->data != NULL) result = duplicate error; else location->data = new Record(new entry); return result;
The number of steps required to search a trie or insert into it is proportional to the number of characters making up a key, not to a logarithm of the number of keys as in other tree-based searches.
Insertion into a Trie Transp. 12, Sect. 11.2, Lexicographic Search Trees: Tries Data Structures and Program Design In C++
413
Multiway Search Trees Transp. 13, Sect. 11.3, External Searching: B-Trees
414
Balanced Multiway Trees (B-Trees) Transp. 14, Sect. 11.3, External Searching: B-Trees
415
In contrast to binary search trees, B-trees are not allowed to grow at their leaves; instead, they are forced to grow at the root. General insertion method: 1. Search the tree for the new key. This search (if the key is truly new) will terminate in failure at a leaf. 2. Insert the new key into to the leaf node. If the node was not previously full, then the insertion is nished. 3. When a key is added to a full node, then the node splits into two nodes, side by side on the same level, except that the median key is not put into either of the two new nodes. 4. When a node splits, move up one level, insert the median key into this parent node, and repeat the splitting process if necessary. 5. When a key is added to a full root, then the root splits in two and the median key sent upward becomes a new root. This is the only time when the B-tree grows in height.
Insertion into a B-Tree Transp. 15, Sect. 11.3, External Searching: B-Trees
416
Growth of a B-Tree
1.
a , g , f, b :
2.
b f
k:
3.
d, h, m :
4.
j:
k m
5.
e, s, i, r :
6.
x:
d e
7.
c, l, n, t, u :
m n
8.
p:
417
Node declaration:
template < class Record, int order > struct B node { // data members: int count; Record data[order 1]; B node< Record, order > *branch[order]; // constructor: B node( ); };
B-Tree Declarations in C++ Transp. 17, Sect. 11.3, External Searching: B-Trees
418
Conventions:
count gives the number of records in the B node.
For 1 position count 1 , branch[position] points to the subtree with keys strictly between those in the subtrees pointed to by data[position 1] and data[position].
branch[count] points to the subtree with keys greater than that of data[count 1].
Searching in a B-Tree
Public method:
template < class Record, int order > Error code B tree< Record, order > :: search tree(Record &target) /* Post: If there is an entry in the B-tree whose key matches that in target, the parameter target is replaced by the corresponding Record from the B-tree and a code of success is returned. Otherwise a code of not present is returned. Uses: recursive search tree */
{ }
Searching in a B-Tree Transp. 18, Sect. 11.3, External Searching: B-Trees Data Structures and Program Design In C++
419
Recursive function:
template < class Record, int order > Error code B tree< Record, order > :: recursive search tree( B node< Record, order > *current, Record &target) /* Pre: current is either NULL or points to a subtree of the B tree. Post: If the Key of target is not in the subtree, a code of not present is returned. Otherwise, a code of success is returned and target is set to the corresponding Record of the subtree. Uses: recursive search tree recursively and search node */
{
Error code result = not present; int position; if (current != NULL) { result = search node(current, target, position); if (result == not present) result = recursive search tree(current->branch[position], target); else target = current->data[position]; } return result;
This function has been written recursively to exhibit the similarity of its structure to that of the insertion function. The recursion is tail recursion and can easily be replaced by iteration.
Recursive function for searching Transp. 19, Sect. 11.3, External Searching: B-Trees
420
Searching a Node
This function determines if the target is present in the current node, and, if not, nds which of the count + 1 branches will contain the target key.
template < class Record, int order > Error code B tree< Record, order > :: search node( B node< Record, order > *current, const Record &target, int &position) /* Pre: current points to a node of a B tree. Post: If the Key of target is found in *current, then a code of success is returned, the parameter position is set to the index of target, and the corresponding Record is copied to target. Otherwise, a code of not present is returned, and position is set to the branch index on which to continue the search. Uses: Methods of class Record. */
{
position = 0; while (position < current->count && target > current->data[position]) position ++ ; // Perform a sequential search through the keys. if (position < current->count && target == current->data[position]) return success; else return not present;
For B-trees of large order, this function should be modied to use binary search instead of sequential search. Another possibility is to use a linked binary search tree instead of a sequential array of entries for each node.
421
new_entry
current
median
right_branch
*current
*current splits
a b c d a b c d
Insertion: Parameters and push down Transp. 21, Sect. 11.3, External Searching: B-Trees
422
Record median; B node< Record, order > *right branch, *new root; Error code result = push down(root, new entry, median, right branch); if (result == overow) { // The whole tree grows in height. // Make a brand new root for the whole B-tree. new root = new B node< Record, order >; new root->count = 1; new root->data[0] = median; new root->branch[0] = root; new root->branch[1] = right branch; root = new root; result = success;
} return result; }
Public Insertion Method Transp. 22, Sect. 11.3, External Searching: B-Trees
423
template < class Record, int order > Error code B tree< Record, order > :: push down( B node< Record, order > *current, const Record &new entry, Record &median, B node< Record, order > * &right branch) /* Pre: current is either NULL or points to a node of a B tree. Post: If an entry with a Key matching that of new entry is in the subtree to which current points, a code of duplicate error is returned. Otherwise, new entry is inserted into the subtree: If this causes the height of the subtree to grow, a code of overow is returned, and the Record median is extracted to be reinserted higher in the B-tree, together with the subtree right branch on its right. If the height does not grow, a code of success is returned. Uses: Functions push down (called recursively), search node, split node, and push in. */
{
Error code result; int position; if (current == NULL) { // Since we cannot insert in an empty tree, the recursion terminates. median = new entry; right branch = NULL; result = overow; }
Recursive Insertion into a Subtree Transp. 23, Sect. 11.3, External Searching: B-Trees
424
else { // Search the current node. if (search node(current, new entry, position) == success) result = duplicate error; else { Record extra entry; B node< Record, order > *extra branch; result = push down(current->branch[position], new entry, extra entry, extra branch); if (result == overow) { // Record extra entry now must be added to current if (current->count < order 1) { result = success; push in(current, extra entry, extra branch, position); } else split node( //
} } } return result; }
current, extra entry, extra branch, position, right branch, median); Record median and its right branch will go up to a higher node.
Recursive Insertion, Continued Transp. 24, Sect. 11.3, External Searching: B-Trees
425
Before: current 0
5 entry right_branch
b
1
d
2
h
3
j
4 5 6
position == 2
A
After: current 0
C
0
E
1
I
2
K
3 4 5
b
1
d
2
f
3
h
4
j
5 6
template < class Record, int order > void B tree< Record, order > :: push in(B node< Record, order > *current, const Record &entry, B node< Record, order > *right branch, int position) /* Pre: current points to a node of a B tree. The node *current is not full and entry belongs in *current at index position. Post: entry has been inserted along with its right-hand branch right branch into *current at index position. */
{
for (int i = current->count; i > position; i ) { // Shift all later data to the right. current->data[i] = current->data[i 1]; current->branch[i + 1] = current->branch[i]; } current->data[position] = entry; current->branch[position + 1] = right branch; current->count ++ ;
Data Structures and Program Design In C++
Inserting a Key into a Node Transp. 25, Sect. 11.3, External Searching: B-Trees
426
b
0 mid = 2; 1
d
2
h
3
j
4
extra_entry extra_branch
b
0 1
d
2
f
3
j
4
extra_entry extra_branch
mid = 3;
b
0 1
d
2
h
1
j
2 0
b
1
d
2
f
3
j
1
b
0 1
d
2
f
3
h
1
j
2 0
b
1
d
2
f
3
h
1
j
2
f
right_half 0 1
b
0 1
d
2 0
h
1
j
2 0
b
1
d
2 0
h
1
j
2
Example of Splitting a Full Node Transp. 26, Sect. 11.3, External Searching: B-Trees
427
template < class Record, int order > void B tree< Record, order > :: split node( B node< Record, order > *current, // node to be split const Record &extra entry, // new entry to insert B node< Record, order > *extra branch, // subtree on right of extra entry int position, // index in node where extra entry goes B node< Record, order > * &right half, // new node for right half of entries Record &median) // median entry (in neither half) /* Pre: current points to a node of a B tree. The node *current is full, but if there were room, the record extra entry with its right-hand pointer extra branch would belong in *current at position position, 0 position < order . Post: The node *current with extra entry and pointer extra branch at position position are divided into nodes *current and *right half separated by a Record median. Uses: Methods of struct B node, function push in. */
Function split node, Specications Transp. 27, Sect. 11.3, External Searching: B-Trees
428
right half = new B node< Record, order >; int mid = order/2; // The entries from mid on will go to right half. if (position <= mid) { // First case: extra entry belongs in left half. for (int i = mid; i < order 1; i ++ ) { // Move entries to right half. right half->data[i mid] = current->data[i]; right half->branch[i + 1 mid] = current->branch[i + 1]; } current->count = mid; right half->count = order 1 mid; push in(current, extra entry, extra branch, position); } else { // Second case: extra entry belongs in right half. mid ++ ; // Temporarily leave the median in left half. for (int i = mid; i < order 1; i ++ ) { // Move entries to right half. right half->data[i mid] = current->data[i]; right half->branch[i + 1 mid] = current->branch[i + 1]; } current->count = mid; right half->count = order 1 mid; push in(right half, extra entry, extra branch, position mid); } median = current->data[current->count 1]; // Remove median from left half. right half->branch[0] = current->branch[current->count]; current->count ;
Function split node, Specications Transp. 28, Sect. 11.3, External Searching: B-Trees
429
Deletion from a B-Tree Transp. 29, Sect. 11.3, External Searching: B-Trees
430
1. Delete h, r :
j
Promote s and delete from leaf.
m r
g g
h i
s t
t u
u x
2. Delete p :
j
Pull s down; pull t up.
m s
p s
3. Delete d :
Combine:
Combine:
a b
a b
Deletion from a B-tree Transp. 30, Sect. 11.3, External Searching: B-Trees
431
Error code result; result = recursive remove(root, target); if (root != NULL && root->count == 0) { // root is now empty. B node< Record, order > *old root = root; root = root->branch[0]; delete old root; } return result;
Public Deletion Method Transp. 31, Sect. 11.3, External Searching: B-Trees
432
Recursive Deletion
template < class Record, int order > Error code B tree< Record, order > :: recursive remove( B node< Record, order > *current, const Record &target) /* Pre: current is either NULL or points to the root node of a subtree of a B tree. Post: If a Record with Key matching that of target belongs to the subtree, a code of success is returned and the corresponding node is removed from the subtree so that the properties of a B-tree are maintained. Otherwise, a code of not present is returned. Uses: Functions search node, copy in predecessor, recursive remove (recursively), remove data, and restore. */ { Error code result; int position; if (current == NULL) result = not present; else { if (search node(current, target, position) == success) { // The target is in the current node. result = success; if (current->branch[position] != NULL) { // not at a leaf node copy in predecessor(current, position); recursive remove(current->branch[position], current->data[position]); } else remove data(current, position); // Remove from a leaf node. } else result = recursive remove(current->branch[position], target); if (current->branch[position] != NULL) if (current->branch[position]->count < (order 1)/2) restore(current, position); } return result; }
Recursive Deletion Transp. 32, Sect. 11.3, External Searching: B-Trees 433
Auxiliary Functions
Remove data from a leaf:
template < class Record, int order > void B tree< Record, order > :: remove data(B node< Record, order > *current, int position) /* Pre: current points to a leaf node in a B-tree with an entry at position. Post: This entry is removed from *current. */ { for (int i = position; i < current->count 1; i ++ ) current->data[i] = current->data[i + 1]; current->count ; }
move_right
t a
u b c
w d t
combine
t a z b
v c
w d
u a b c
w d a
u b
w c d
Restore Minimum Number of Entries Transp. 34, Sect. 11.3, External Searching: B-Trees
435
if (position == current->count) // case: rightmost branch if (current->branch[position 1]->count > (order 1)/2) move right(current, position 1); else combine(current, position); else if (position == 0) // case: leftmost branch if (current->branch[1]->count > (order 1)/2) move left(current, 1); else combine(current, 1); else // remaining cases: intermediate branches if (current->branch[position 1]->count > (order 1)/2) move right(current, position 1); else if (current->branch[position + 1]->count > (order 1)/2) move left(current, position + 1); else combine(current, position);
Function to Restore Minimum Node Entries Transp. 35, Sect. 11.3, External Searching: B-Trees
436
B node< Record, order > *left branch = current->branch[position 1], *right branch = current->branch[position]; left branch->data[left branch->count] = current->data[position 1]; // Take entry from the parent. left branch->branch[ ++ left branch->count] = right branch->branch[0]; current->data[position 1] = right branch->data[0]; // Add the right-hand entry to the parent. right branch->count ; for (int i = 0; i < right branch->count; i ++ ) { // Move right-hand entries to ll the hole. right branch->data[i] = right branch->data[i + 1]; right branch->branch[i] = right branch->branch[i + 1]; } right branch->branch[right branch->count] = right branch->branch[right branch->count + 1];
Function move left Transp. 36, Sect. 11.3, External Searching: B-Trees
437
B node< Record, order > *right branch = current->branch[position + 1], *left branch = current->branch[position]; right branch->branch[right branch->count + 1] = right branch->branch[right branch->count]; for (int i = right branch->count ; i > 0; i ) { // Make room for new entry. right branch->data[i] = right branch->data[i 1]; right branch->branch[i] = right branch->branch[i 1]; } right branch->count ++ ; right branch->data[0] = current->data[position]; // Take entry from parent. right branch->branch[0] = left branch->branch[left branch->count ]; current->data[position] = left branch->data[left branch->count];
Function move right Transp. 37, Sect. 11.3, External Searching: B-Trees
438
Function combine
template < class Record, int order > void B tree< Record, order > :: combine(B node< Record, order > *current, int position) /* Pre: current points to a node in a B-tree with entries in the branches position and position 1, with too few to move entries. Post: The nodes at branches position 1 and position have been combined into one node, which also includes the entry formerly in current at index position 1. */
{
int i; B node< Record, order > *left branch = current->branch[position 1], *right branch = current->branch[position]; left branch->data[left branch->count] = current->data[position 1]; left branch->branch[ ++ left branch->count] = right branch->branch[0]; for (i = 0; i < right branch->count; i ++ ) { left branch->data[left branch->count] = right branch->data[i]; left branch->branch[ ++ left branch->count] = right branch->branch[i + 1]; } current->count ; for (i = position 1; i < current->count; i ++ ) { current->data[i] = current->data[i + 1]; current->branch[i + 1] = current->branch[i + 2]; } delete right branch;
}
Function combine Transp. 38, Sect. 11.3, External Searching: B-Trees 439
w u s o q m j k h f d e
A B-tree of order 4 as a binary search tree Transp. 39, Sect. 11.4, Red-Black Trees 440
g
1999 Prentice-Hall, Inc., Upper Saddle River, N.J. 07458
i b
Data Structures and Program Design In C++
becomes T1 T1 T2 T3
or
A node with one entry remains unchanged. A red-black tree is a binary search tree, with links colored red or black, obtained from a B-tree of order 4 by the above conversions. Searching and traversal of a red-black tree are exactly the same as for an ordinary binary search tree. Insertion and deletion, require care to maintain the underlying B-tree structure.
Red-Black Trees as B-Trees of Order 4 Transp. 40, Sect. 11.4, Red-Black Trees 441
The Black Condition Every simple path from the root to an empty subtree goes through the same number of black nodes.
To guarantee that no more than three nodes are connected by red links as one B-tree node, and that nodes with three entries are a balanced binary tree, we require:
The Red Condition If a node is red, then its parent exists and is black.
We can now dene: DEFINITION A red-black tree is a binary search tree in which each node has either the color red or black and that satises the black and red conditions.
Red-Black Trees as Binary Search Trees Transp. 41, Sect. 11.4, Red-Black Trees Data Structures and Program Design In C++
442
THEOREM 11.2 The height of a red-black tree containing n nodes is no more than 2 lg n .
Searching a red-black tree with n nodes is O(log n) in every case. The time for insertion is also O(log n) . [To show this, we rst need to devise the insertion algorithm.] An AVL tree, in its worst case, has height about 1.44 lg n and, on average, has an even smaller height. Hence red-black trees do not achieve as good a balance as AVL trees. Red-black trees are not necessarily slower than AVL trees, since AVL trees may require many more rotations to maintain balance than red-black trees require.
443
The red-black tree class is derived from the binary search tree class. We begin by incorporating colors into the nodes that will make up red-black trees:
enum Color {red, black}; template < class Record > struct RB node: public Binary node< Record > { Color color; RB node(const Record &new entry) {color = red; data = new entry; left = right = NULL; } RB node( ) {color = red; left = right = NULL; } void set color(Color c) {color = c; } Color get color( ) const {return color; } };
Note the inline denitions for the constructors and other methods of a red-black node.
444
To invoke get color and set color via pointers, we must add virtual functions to the base struct Binary node. The modied node specication is:
template < class Entry > struct Binary node { Entry data; Binary node< Entry > *left; Binary node< Entry > *right; virtual Color get color( ) const { return red; } virtual void set color(Color c) { } Binary node( ) { left = right = NULL; } Binary node(const Entry &x) {data = x; left = right = NULL; } };
With this modication, we can reuse all the methods and functions for manipulating binary search trees and their nodes.
445
Red-Black Insertion
We begin with the standard recursive algorithm for insertion into a binary search tree. The new entry will be in a new leaf node. The black condition requires that the new node must be red. If the parent of the new red node is black, then the insertion is nished, but if the parent is red, then we have introduced a violation of the red condition into the tree. The major work of the insertion algorithm is to remove a violation of the red condition, and we shall nd several different cases that we shall need to process separately. We postpone this work as long as we can. When we make a node red, we do not check the conditions or repair the tree. Instead, we return from the recursive call with a status indicator showing that the node just processed is red. After this return, we are processing the parent node. If the parent is black, then the conditions for a red-black tree are satised and the process terminates. If the parent is red, then we set the status variable to show two red nodes together, linked as left child or as right child. Return from the recursive call. We are now processing the grandparent node. Since the root is black and the parent is red, this grandparent must exist. By the red condition, this grandparent is black since the parent was red. At the recursive level of the grandparent node, we transform the tree to restore the red-black conditions, using cases depending on the relative positions of the grandparent, parent, and child nodes. See following diagram.
Red-Black Insertion Transp. 45, Sect. 11.4, Red-Black Trees 446
child
grandparent parent aunt parent child T1 T2 T3 Double rotate right T1 T2 T3 T4 T4 aunt child grandparent
grandparent parent
aunt
aunt
T1
child
T2
T3
T1
child
T2
T3
447
The color of the current root (of the subtree) has not changed; the tree now satises the conditions for a red-black tree. red node: The current root has changed from black to red; modication may or may not be needed to restore the red-black properties.
okay:
The current root and its right child are now both red; a color ip or rotation is needed. left red: The current root and its left child are now both red; a color ip or rotation is needed. duplicate: The entry being inserted duplicates another entry; this is an error. */
right red:
448
RB code status = rb insert(root, new entry); switch (status) { // Convert private RB code to public Error code. case red node: // Always split the root node to keep it black. root->set color(black); /* Doing so prevents two red nodes at the top of the tree and a resulting attempt to rotate using a parent node that does not exist. */ case okay: return success; case duplicate: return duplicate error; case right red: case left red: cout < < "WARNING: Program error in RB tree::insert" < < endl; return internal error;
}
449
RB code status, child status; if (current == NULL) { current = new RB node< Record >(new entry); status = red node; } else if (new entry == current->data) return duplicate; else if (new entry < current->data) { child status = rb insert(current->left, new entry); status = modify left(current, child status); } else { child status = rb insert(current->right, new entry); status = modify right(current, child status); } return status;
}
Recursive Insertion Function Transp. 49, Sect. 11.4, Red-Black Trees 450
452