/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef ds_SplayTree_h #define ds_SplayTree_h #include "ds/LifoAlloc.h" namespace js { /* * Class which represents a splay tree with nodes allocated from a LifoAlloc. * Splay trees are balanced binary search trees for which search, insert and * remove are all amortized O(log n). * * T indicates the type of tree elements, C must have a static * compare(const T&, const T&) method ordering the elements. As for LifoAlloc * objects, T objects stored in the tree will not be explicitly destroyed. */ template class SplayTree { struct Node { T item; Node* left; Node* right; Node* parent; explicit Node(const T& item) : item(item), left(nullptr), right(nullptr), parent(nullptr) {} }; LifoAlloc* alloc; Node* root; Node* freeList; #ifdef DEBUG bool enableCheckCoherency; #endif SplayTree(const SplayTree&) = delete; SplayTree& operator=(const SplayTree&) = delete; public: explicit SplayTree(LifoAlloc* alloc = nullptr) : alloc(alloc), root(nullptr), freeList(nullptr) #ifdef DEBUG , enableCheckCoherency(true) #endif { } void setAllocator(LifoAlloc* alloc) { this->alloc = alloc; } void disableCheckCoherency() { #ifdef DEBUG enableCheckCoherency = false; #endif } bool empty() const { return !root; } T* maybeLookup(const T& v) { if (!root) { return nullptr; } Node* last = lookup(v); return (C::compare(v, last->item) == 0) ? &(last->item) : nullptr; } bool contains(const T& v, T* res) { if (!root) { return false; } Node* last = lookup(v); splay(last); checkCoherency(); if (C::compare(v, last->item) == 0) { *res = last->item; return true; } return false; } MOZ_MUST_USE bool insert(const T& v) { Node* element = allocateNode(v); if (!element) { return false; } if (!root) { root = element; return true; } Node* last = lookup(v); int cmp = C::compare(v, last->item); // Don't tolerate duplicate elements. MOZ_DIAGNOSTIC_ASSERT(cmp); Node*& parentPointer = (cmp < 0) ? last->left : last->right; MOZ_ASSERT(!parentPointer); parentPointer = element; element->parent = last; splay(element); checkCoherency(); return true; } void remove(const T& v) { Node* last = lookup(v); MOZ_ASSERT(last && C::compare(v, last->item) == 0); splay(last); MOZ_ASSERT(last == root); // Find another node which can be swapped in for the root: either the // rightmost child of the root's left, or the leftmost child of the // root's right. Node* swap; Node* swapChild; if (root->left) { swap = root->left; while (swap->right) { swap = swap->right; } swapChild = swap->left; } else if (root->right) { swap = root->right; while (swap->left) { swap = swap->left; } swapChild = swap->right; } else { freeNode(root); root = nullptr; return; } // The selected node has at most one child, in swapChild. Detach it // from the subtree by replacing it with that child. if (swap == swap->parent->left) { swap->parent->left = swapChild; } else { swap->parent->right = swapChild; } if (swapChild) { swapChild->parent = swap->parent; } root->item = swap->item; freeNode(swap); checkCoherency(); } template void forEach(Op op) { forEachInner(op, root); } private: Node* lookup(const T& v) { MOZ_ASSERT(root); Node* node = root; Node* parent; do { parent = node; int c = C::compare(v, node->item); if (c == 0) { return node; } else if (c < 0) { node = node->left; } else { node = node->right; } } while (node); return parent; } Node* allocateNode(const T& v) { Node* node = freeList; if (node) { freeList = node->left; new (node) Node(v); return node; } return alloc->new_(v); } void freeNode(Node* node) { node->left = freeList; freeList = node; } void splay(Node* node) { // Rotate the element until it is at the root of the tree. Performing // the rotations in this fashion preserves the amortized balancing of // the tree. MOZ_ASSERT(node); while (node != root) { Node* parent = node->parent; if (parent == root) { // Zig rotation. rotate(node); MOZ_ASSERT(node == root); return; } Node* grandparent = parent->parent; if ((parent->left == node) == (grandparent->left == parent)) { // Zig-zig rotation. rotate(parent); rotate(node); } else { // Zig-zag rotation. rotate(node); rotate(node); } } } void rotate(Node* node) { // Rearrange nodes so that node becomes the parent of its current // parent, while preserving the sortedness of the tree. Node* parent = node->parent; if (parent->left == node) { // x y // y c ==> a x // a b b c parent->left = node->right; if (node->right) { node->right->parent = parent; } node->right = parent; } else { MOZ_ASSERT(parent->right == node); // x y // a y ==> x c // b c a b parent->right = node->left; if (node->left) { node->left->parent = parent; } node->left = parent; } node->parent = parent->parent; parent->parent = node; if (Node* grandparent = node->parent) { if (grandparent->left == parent) { grandparent->left = node; } else { grandparent->right = node; } } else { root = node; } } template void forEachInner(Op op, Node* node) { if (!node) { return; } forEachInner(op, node->left); op(node->item); forEachInner(op, node->right); } void checkCoherency() const { #ifdef DEBUG if (!enableCheckCoherency) { return; } if (!root) { return; } MOZ_ASSERT(root->parent == nullptr); const Node* node = root; const Node* minimum = nullptr; MOZ_ASSERT_IF(node->left, node->left->parent == node); MOZ_ASSERT_IF(node->right, node->right->parent == node); // This is doing a depth-first search and check that the values are // ordered properly. while (true) { // Go to the left-most child. while (node->left) { MOZ_ASSERT_IF(node->left, node->left->parent == node); MOZ_ASSERT_IF(node->right, node->right->parent == node); node = node->left; } MOZ_ASSERT_IF(minimum, C::compare(minimum->item, node->item) < 0); minimum = node; if (node->right) { // Go once to the right and try again. MOZ_ASSERT_IF(node->left, node->left->parent == node); MOZ_ASSERT_IF(node->right, node->right->parent == node); node = node->right; } else { // We reached a leaf node, move to the first branch to the right of // our current left-most sub-tree. MOZ_ASSERT(!node->left && !node->right); const Node* prev = nullptr; // Visit the parent node, to find the right branch which we have // not visited yet. Either we are coming back from the right // branch, or we are coming back from the left branch with no // right branch to visit. while (node->parent) { prev = node; node = node->parent; // If we came back from the left branch, visit the value. if (node->left == prev) { MOZ_ASSERT_IF(minimum, C::compare(minimum->item, node->item) < 0); minimum = node; } if (node->right != prev && node->right != nullptr) { break; } } if (!node->parent) { MOZ_ASSERT(node == root); // We reached the root node either because we came back from // the right hand side, or because the root node had a // single child. if (node->right == prev || node->right == nullptr) { return; } } // Go to the right node which we have not visited yet. MOZ_ASSERT(node->right != prev && node->right != nullptr); node = node->right; } } #endif } }; } /* namespace js */ #endif /* ds_SplayTree_h */