/* tree.c: BINARY TREE IMPLEMENTATION * * $Id$ * Copyright (C) 2014-2020 Ravenbrook Limited. See end of file for license. * * Simple binary trees with utilities, for use as building blocks. * Keep it simple, like Rings (see ring.h). * * The performance requirements on tree implementation will depend on * how each individual function is applied in the MPS. * * .note.stack: It's important that the MPS have a bounded stack size, * and this is a problem for tree algorithms. Basically, we have to * avoid recursion. . */ #include "tree.h" #include "mpm.h" SRCID(tree, "$Id$"); Bool TreeCheck(Tree tree) { if (tree != TreeEMPTY) { CHECKL(tree != NULL); CHECKL(tree->left == TreeEMPTY || tree->left != NULL); CHECKL(tree->right == TreeEMPTY || tree->right != NULL); } return TRUE; } Bool TreeCheckLeaf(Tree tree) { CHECKL(TreeCheck(tree)); CHECKL(tree != TreeEMPTY); CHECKL(tree->left == TreeEMPTY); CHECKL(tree->right == TreeEMPTY); return TRUE; } /* TreeDebugCount -- count and check order of tree * * This function may be called from a debugger or temporarily inserted * during development to check a tree's integrity. It may not be called * from the production MPS because it uses indefinite stack depth. * See .note.stack. */ static Count TreeDebugCountBetween(Tree node, TreeCompareFunction compare, TreeKeyFunction key, TreeKey min, TreeKey max) { if (node == TreeEMPTY) return 0; AVERT(Tree, node); AVER(min == NULL || compare(node, min) != CompareGREATER); AVER(max == NULL || compare(node, max) != CompareLESS); return TreeDebugCountBetween(TreeLeft(node), compare, key, min, key(node)) + 1 + TreeDebugCountBetween(TreeRight(node), compare, key, key(node), max); } Count TreeDebugCount(Tree tree, TreeCompareFunction compare, TreeKeyFunction key) { AVERT(Tree, tree); return TreeDebugCountBetween(tree, compare, key, NULL, NULL); } /* TreeFind -- search for a node matching the key * * If a matching node is found, sets *treeReturn to that node and returns * CompareEQUAL. Otherwise returns values useful for inserting a node with * the key. If the tree is empty, returns CompareEQUAL and sets *treeReturn * to NULL. Otherwise, sets *treeReturn to a potential parent for the new * node and returns CompareLESS if the new node should be its left child, * or CompareGREATER for its right. */ Compare TreeFind(Tree *treeReturn, Tree root, TreeKey key, TreeCompareFunction compare) { Tree node, parent; Compare cmp = CompareEQUAL; AVERT_CRITICAL(Tree, root); AVER_CRITICAL(treeReturn != NULL); AVER_CRITICAL(FUNCHECK(compare)); /* key is arbitrary */ parent = NULL; node = root; while (node != TreeEMPTY) { parent = node; cmp = compare(node, key); switch (cmp) { case CompareLESS: node = node->left; break; case CompareEQUAL: *treeReturn = node; return cmp; case CompareGREATER: node = node->right; break; default: NOTREACHED; *treeReturn = NULL; return cmp; } } *treeReturn = parent; return cmp; } /* TreeFindNext -- search for node containing key, or next node * * If there is a node that is greater than key, set *treeReturn to that * node and return TRUE. * * Otherwise, key is greater than all nodes in the tree, so leave * *treeReturn unchanged and return FALSE. */ Bool TreeFindNext(Tree *treeReturn, Tree root, TreeKey key, TreeCompareFunction compare) { Tree node, best = NULL; Bool result = FALSE; AVERT(Tree, root); AVER(treeReturn != NULL); AVER(FUNCHECK(compare)); /* key is arbitrary */ node = root; while (node != TreeEMPTY) { Compare cmp = compare(node, key); switch (cmp) { case CompareLESS: best = node; result = TRUE; node = node->left; break; case CompareEQUAL: case CompareGREATER: node = node->right; break; default: NOTREACHED; return FALSE; } } *treeReturn = best; return result; } /* TreeInsert -- insert a node into a tree * * If the key doesn't exist in the tree, inserts a node as a leaf of the * tree, returning the resulting tree in *treeReturn, and returns TRUE. * Otherwise, *treeReturn points to the existing matching node, the tree * is not modified, and returns FALSE. */ Bool TreeInsert(Tree *treeReturn, Tree root, Tree node, TreeKey key, TreeCompareFunction compare) { Tree parent; Compare cmp; AVER(treeReturn != NULL); AVERT(Tree, root); AVER(TreeCheckLeaf(node)); AVER(FUNCHECK(compare)); /* key is arbitrary */ cmp = TreeFind(&parent, root, key, compare); switch (cmp) { case CompareLESS: parent->left = node; break; case CompareEQUAL: if (parent != NULL) { *treeReturn = parent; return FALSE; } AVER(root == TreeEMPTY); root = node; break; case CompareGREATER: parent->right = node; break; default: NOTREACHED; *treeReturn = NULL; return FALSE; } *treeReturn = root; return TRUE; } #if 0 /* This code is currently not in use in the MPS */ /* TreeTraverseMorris -- traverse tree inorder in constant space * * The tree may not be accessed or modified during the traversal, and * the traversal must complete in order to repair the tree. * * The visitor should return FALSE to terminate the traversal early, * in which case FALSE is returned. * * TreeTraverse is generally superior if comparisons are cheap, but * TreeTraverseMorris does not require any comparison function. * * * * Joseph M. Morris (1979). "Traversing Binary Trees Simply and Cheaply". * Information Processing Letters 9:5 pp. 197-200. */ Bool TreeTraverseMorris(Tree tree, TreeVisitor visit, void *closure) { Tree node; Bool visiting = TRUE; AVERT(Tree, tree); AVER(FUNCHECK(visit)); /* closure arbitrary */ node = tree; while (node != TreeEMPTY) { if (node->left == TreeEMPTY) { if (visiting) visiting = visit(node, closure); node = node->right; } else { Tree pre = node->left; for (;;) { if (pre->right == TreeEMPTY) { pre->right = node; node = node->left; break; } if (pre->right == node) { pre->right = TreeEMPTY; if (visiting) visiting = visit(node, closure); else if (node == tree) return FALSE; node = node->right; break; } pre = pre->right; } } } return visiting; } #endif /* not currently in use */ /* TreeTraverse -- traverse tree in-order using pointer reversal * * The tree may not be accessed or modified during the traversal, and * the traversal must complete in order to repair the tree. * * The visitor should return FALSE to terminate the traversal early, * in which case FALSE is returned. * * TreeTraverseMorris is an alternative when no cheap comparison is available. */ static Tree stepDownLeft(Tree node, Tree *parentIO) { Tree parent = *parentIO; Tree child = TreeLeft(node); TreeSetLeft(node, parent); *parentIO = node; return child; } static Tree stepDownRight(Tree node, Tree *parentIO) { Tree parent = *parentIO; Tree child = TreeRight(node); TreeSetRight(node, parent); *parentIO = node; return child; } static Tree stepUpRight(Tree node, Tree *parentIO) { Tree parent = *parentIO; Tree grandparent = TreeLeft(parent); TreeSetLeft(parent, node); *parentIO = grandparent; return parent; } static Tree stepUpLeft(Tree node, Tree *parentIO) { Tree parent = *parentIO; Tree grandparent = TreeRight(parent); TreeSetRight(parent, node); *parentIO = grandparent; return parent; } Bool TreeTraverse(Tree tree, TreeCompareFunction compare, TreeKeyFunction key, TreeVisitor visit, void *closure) { Tree parent, node; AVERT(Tree, tree); AVER(FUNCHECK(visit)); /* closure arbitrary */ parent = TreeEMPTY; node = tree; if (node == TreeEMPTY) return TRUE; down: if (TreeHasLeft(node)) { node = stepDownLeft(node, &parent); AVER(compare(parent, key(node)) == CompareLESS); goto down; } if (!visit(node, closure)) goto abort; if (TreeHasRight(node)) { node = stepDownRight(node, &parent); AVER(compare(parent, key(node)) != CompareLESS); goto down; } up: if (parent == TreeEMPTY) return TRUE; if (compare(parent, key(node)) != CompareLESS) { node = stepUpLeft(node, &parent); goto up; } node = stepUpRight(node, &parent); if (!visit(node, closure)) goto abort; if (!TreeHasRight(node)) goto up; node = stepDownRight(node, &parent); goto down; abort: if (parent == TreeEMPTY) return FALSE; if (compare(parent, key(node)) != CompareLESS) node = stepUpLeft(node, &parent); else node = stepUpRight(node, &parent); goto abort; } /* TreeRotateLeft -- Rotate right child edge of node * * Rotates node, right child of node, and left child of right * child of node, leftwards in the order stated. Preserves tree * ordering. */ void TreeRotateLeft(Tree *treeIO) { Tree tree, right; AVER(treeIO != NULL); tree = *treeIO; AVERT(Tree, tree); right = TreeRight(tree); AVERT(Tree, right); TreeSetRight(tree, TreeLeft(right)); TreeSetLeft(right, tree); *treeIO = right; } /* TreeRotateRight -- Rotate left child edge of node * * Rotates node, left child of node, and right child of left * child of node, leftwards in the order stated. Preserves tree * ordering. */ void TreeRotateRight(Tree *treeIO) { Tree tree, left; AVER(treeIO != NULL); tree = *treeIO; AVERT(Tree, tree); left = TreeLeft(tree); AVERT(Tree, left); TreeSetLeft(*treeIO, TreeRight(left)); TreeSetRight(left, *treeIO); *treeIO = left; } /* TreeReverseLeftSpine -- reverse the pointers on the left spine * * Descends the left spine of a tree, updating each node's left child * to point to its parent instead. The root's left child is set to * TreeEMPTY. Returns the leftmost child, or TreeEMPTY if the tree * was empty. */ Tree TreeReverseLeftSpine(Tree tree) { Tree node, parent; AVERT(Tree, tree); parent = TreeEMPTY; node = tree; while (node != TreeEMPTY) { Tree child = TreeLeft(node); TreeSetLeft(node, parent); parent = node; node = child; } return parent; } /* TreeReverseRightSpine -- reverse the pointers on the right spine * * Descends the right spine of a tree, updating each node's right child * to point to its parent instead. The root's right child is set to * TreeEMPTY. Returns the rightmost child or TreeEMPTY if the tree * was empty. */ Tree TreeReverseRightSpine(Tree tree) { Tree node, parent; AVERT(Tree, tree); parent = TreeEMPTY; node = tree; while (node != TreeEMPTY) { Tree child = TreeRight(node); TreeSetRight(node, parent); parent = node; node = child; } return parent; } /* TreeToVine -- unbalance a tree into a single right spine */ Count TreeToVine(Tree *link) { Count count = 0; AVER(link != NULL); AVERT(Tree, *link); while (*link != TreeEMPTY) { while (TreeHasLeft(*link)) TreeRotateRight(link); link = &((*link)->right); ++count; } return count; } /* TreeBalance -- rebalance a tree * * Linear time, constant space rebalance. * * Quentin F. Stout and Bette L. Warren, * "Tree Rebalancing in Optimal Time and Space", * Communications of the ACM, Vol. 29, No. 9 (September 1986), p. 902-908 */ void TreeBalance(Tree *treeIO) { Count depth; AVER(treeIO != NULL); AVERT(Tree, *treeIO); depth = TreeToVine(treeIO); if (depth > 2) { Count n = depth - 1; do { Count m = n / 2, i; Tree *link = treeIO; for (i = 0; i < m; ++i) { TreeRotateLeft(link); link = &((*link)->right); } n = n - m - 1; } while (n > 1); } } /* TreeTraverseAndDelete -- traverse a tree while deleting nodes * * The visitor function must return TRUE to delete the current node, * or FALSE to keep it. * * . */ void TreeTraverseAndDelete(Tree *treeIO, TreeVisitor visitor, void *closure) { Tree *treeref = treeIO; AVER(treeIO != NULL); AVERT(Tree, *treeIO); AVER(FUNCHECK(visitor)); /* closure arbitrary */ TreeToVine(treeIO); while (*treeref != TreeEMPTY) { Tree tree = *treeref; /* Current node. */ Tree *nextref = &tree->right; /* Location of pointer to next node. */ Tree next = *nextref; /* Next node. */ if ((*visitor)(tree, closure)) { /* Delete current node. */ *treeref = next; } else { /* Keep current node. */ treeref = nextref; } } TreeBalance(treeIO); } /* C. COPYRIGHT AND LICENSE * * Copyright (C) 2014-2020 Ravenbrook Limited . * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */