mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 12:09:14 +00:00
8345314: Add a red–black tree as a utility data structure
Reviewed-by: aboldtch, jsjolen, stuefe
This commit is contained in:
parent
a937f6db30
commit
2efb6aaadb
341
src/hotspot/share/utilities/rbTree.hpp
Normal file
341
src/hotspot/share/utilities/rbTree.hpp
Normal file
@ -0,0 +1,341 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SHARE_UTILITIES_RBTREE_HPP
|
||||
#define SHARE_UTILITIES_RBTREE_HPP
|
||||
|
||||
#include "nmt/memTag.hpp"
|
||||
#include "runtime/os.hpp"
|
||||
#include "utilities/globalDefinitions.hpp"
|
||||
#include <type_traits>
|
||||
|
||||
// COMPARATOR must have a static function `cmp(a,b)` which returns:
|
||||
// - an int < 0 when a < b
|
||||
// - an int == 0 when a == b
|
||||
// - an int > 0 when a > b
|
||||
// ALLOCATOR must check for oom and exit, as RBTree currently does not handle the
|
||||
// allocation failing.
|
||||
// Key needs to be of a type that is trivially destructible.
|
||||
// The tree will call a value's destructor when its node is removed.
|
||||
// Nodes are address stable and will not change during its lifetime.
|
||||
|
||||
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
|
||||
class RBTree {
|
||||
friend class RBTreeTest;
|
||||
|
||||
private:
|
||||
ALLOCATOR _allocator;
|
||||
size_t _num_nodes;
|
||||
|
||||
public:
|
||||
class RBNode {
|
||||
friend RBTree;
|
||||
friend class RBTreeTest;
|
||||
|
||||
private:
|
||||
uintptr_t _parent; // LSB encodes color information. 0 = RED, 1 = BLACK
|
||||
RBNode* _left;
|
||||
RBNode* _right;
|
||||
|
||||
const K _key;
|
||||
V _value;
|
||||
|
||||
DEBUG_ONLY(bool _visited);
|
||||
|
||||
public:
|
||||
const K& key() const { return _key; }
|
||||
V& val() { return _value; }
|
||||
const V& val() const { return _value; }
|
||||
|
||||
private:
|
||||
bool is_black() const { return (_parent & 0x1) != 0; }
|
||||
bool is_red() const { return (_parent & 0x1) == 0; }
|
||||
|
||||
void set_black() { _parent |= 0x1; }
|
||||
void set_red() { _parent &= ~0x1; }
|
||||
|
||||
RBNode* parent() const { return (RBNode*)(_parent & ~0x1); }
|
||||
void set_parent(RBNode* new_parent) { _parent = (_parent & 0x1) | (uintptr_t)new_parent; }
|
||||
|
||||
RBNode(const K& key, const V& val DEBUG_ONLY(COMMA bool visited))
|
||||
: _parent(0), _left(nullptr), _right(nullptr),
|
||||
_key(key), _value(val) DEBUG_ONLY(COMMA _visited(visited)) {}
|
||||
|
||||
bool is_right_child() const {
|
||||
return parent() != nullptr && parent()->_right == this;
|
||||
}
|
||||
|
||||
bool is_left_child() const {
|
||||
return parent() != nullptr && parent()->_left == this;
|
||||
}
|
||||
|
||||
void replace_child(RBNode* old_child, RBNode* new_child);
|
||||
|
||||
// This node down, right child up
|
||||
// Returns right child (now parent)
|
||||
RBNode* rotate_left();
|
||||
|
||||
// This node down, left child up
|
||||
// Returns left child (now parent)
|
||||
RBNode* rotate_right();
|
||||
|
||||
RBNode* prev();
|
||||
|
||||
RBNode* next();
|
||||
|
||||
#ifdef ASSERT
|
||||
void verify(size_t& num_nodes, size_t& black_nodes_until_leaf,
|
||||
size_t& shortest_leaf_path, size_t& longest_leaf_path,
|
||||
size_t& tree_depth, bool expect_visited);
|
||||
#endif // ASSERT
|
||||
};
|
||||
|
||||
private:
|
||||
RBNode* _root;
|
||||
DEBUG_ONLY(bool _expected_visited);
|
||||
|
||||
RBNode* allocate_node(const K& key, const V& val) {
|
||||
void* node_place = _allocator.allocate(sizeof(RBNode));
|
||||
assert(node_place != nullptr, "rb-tree allocator must exit on failure");
|
||||
_num_nodes++;
|
||||
return new (node_place) RBNode(key, val DEBUG_ONLY(COMMA _expected_visited));
|
||||
}
|
||||
|
||||
void free_node(RBNode* node) {
|
||||
node->_value.~V();
|
||||
_allocator.free(node);
|
||||
_num_nodes--;
|
||||
}
|
||||
|
||||
// True if node is black (nil nodes count as black)
|
||||
static inline bool is_black(const RBNode* node) {
|
||||
return node == nullptr || node->is_black();
|
||||
}
|
||||
|
||||
static inline bool is_red(const RBNode* node) {
|
||||
return node != nullptr && node->is_red();
|
||||
}
|
||||
|
||||
|
||||
// If the node with key k already exist, the value is updated instead.
|
||||
RBNode* insert_node(const K& key, const V& val);
|
||||
|
||||
void fix_insert_violations(RBNode* node);
|
||||
|
||||
void remove_black_leaf(RBNode* node);
|
||||
|
||||
// Assumption: node has at most one child. Two children is handled in `remove()`
|
||||
void remove_from_tree(RBNode* node);
|
||||
|
||||
public:
|
||||
NONCOPYABLE(RBTree);
|
||||
|
||||
RBTree() : _allocator(), _num_nodes(0), _root(nullptr) DEBUG_ONLY(COMMA _expected_visited(false)) {
|
||||
static_assert(std::is_trivially_destructible<K>::value, "key type must be trivially destructable");
|
||||
}
|
||||
~RBTree() { this->remove_all(); }
|
||||
|
||||
size_t size() { return _num_nodes; }
|
||||
|
||||
// Inserts a node with the given k/v into the tree,
|
||||
// if the key already exist, the value is updated instead.
|
||||
void upsert(const K& key, const V& val) {
|
||||
RBNode* node = insert_node(key, val);
|
||||
fix_insert_violations(node);
|
||||
}
|
||||
|
||||
// Removes the node with the given key from the tree if it exists.
|
||||
// Returns true if the node was successfully removed, false otherwise.
|
||||
bool remove(const K& key) {
|
||||
RBNode* node = find_node(key);
|
||||
if (node == nullptr){
|
||||
return false;
|
||||
}
|
||||
remove(node);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Removes the given node from the tree. node must be a valid node
|
||||
void remove(RBNode* node);
|
||||
|
||||
// Removes all existing nodes from the tree.
|
||||
void remove_all() {
|
||||
RBNode* to_delete[64];
|
||||
int stack_idx = 0;
|
||||
to_delete[stack_idx++] = _root;
|
||||
|
||||
while (stack_idx > 0) {
|
||||
RBNode* head = to_delete[--stack_idx];
|
||||
if (head == nullptr) continue;
|
||||
to_delete[stack_idx++] = head->_left;
|
||||
to_delete[stack_idx++] = head->_right;
|
||||
free_node(head);
|
||||
}
|
||||
_num_nodes = 0;
|
||||
_root = nullptr;
|
||||
}
|
||||
|
||||
// Finds the node with the closest key <= the given key
|
||||
const RBNode* closest_leq(const K& key) const {
|
||||
RBNode* candidate = nullptr;
|
||||
RBNode* pos = _root;
|
||||
while (pos != nullptr) {
|
||||
const int cmp_r = COMPARATOR::cmp(pos->key(), key);
|
||||
if (cmp_r == 0) { // Exact match
|
||||
candidate = pos;
|
||||
break; // Can't become better than that.
|
||||
}
|
||||
if (cmp_r < 0) {
|
||||
// Found a match, try to find a better one.
|
||||
candidate = pos;
|
||||
pos = pos->_right;
|
||||
} else {
|
||||
pos = pos->_left;
|
||||
}
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
|
||||
// Finds the node with the closest key > the given key
|
||||
const RBNode* closest_gt(const K& key) const {
|
||||
RBNode* candidate = nullptr;
|
||||
RBNode* pos = _root;
|
||||
while (pos != nullptr) {
|
||||
const int cmp_r = COMPARATOR::cmp(pos->key(), key);
|
||||
if (cmp_r > 0) {
|
||||
// Found a match, try to find a better one.
|
||||
candidate = pos;
|
||||
pos = pos->_left;
|
||||
} else {
|
||||
pos = pos->_right;
|
||||
}
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
|
||||
// Finds the node with the closest key >= the given key
|
||||
const RBNode* closest_geq(const K& key) const {
|
||||
RBNode* candidate = nullptr;
|
||||
RBNode* pos = _root;
|
||||
while (pos != nullptr) {
|
||||
const int cmp_r = COMPARATOR::cmp(pos->key(), key);
|
||||
if (cmp_r == 0) { // Exact match
|
||||
candidate = pos;
|
||||
break; // Can't become better than that.
|
||||
}
|
||||
if (cmp_r > 0) {
|
||||
// Found a match, try to find a better one.
|
||||
candidate = pos;
|
||||
pos = pos->_left;
|
||||
} else {
|
||||
pos = pos->_right;
|
||||
}
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
|
||||
RBNode* closest_leq(const K& key) {
|
||||
return const_cast<RBNode*>(
|
||||
static_cast<const RBTree<K, V, COMPARATOR, ALLOCATOR>*>(this)->closest_leq(key));
|
||||
}
|
||||
|
||||
RBNode* closest_gt(const K& key) {
|
||||
return const_cast<RBNode*>(
|
||||
static_cast<const RBTree<K, V, COMPARATOR, ALLOCATOR>*>(this)->closest_gt(key));
|
||||
}
|
||||
|
||||
RBNode* closest_geq(const K& key) {
|
||||
return const_cast<RBNode*>(
|
||||
static_cast<const RBTree<K, V, COMPARATOR, ALLOCATOR>*>(this)->closest_geq(key));
|
||||
}
|
||||
|
||||
struct Range {
|
||||
RBNode* start;
|
||||
RBNode* end;
|
||||
Range(RBNode* start, RBNode* end)
|
||||
: start(start), end(end) {}
|
||||
};
|
||||
|
||||
// Return the range [start, end)
|
||||
// where start->key() <= addr < end->key().
|
||||
// Failure to find the range leads to start and/or end being null.
|
||||
Range find_enclosing_range(K key) {
|
||||
RBNode* start = closest_leq(key);
|
||||
RBNode* end = closest_gt(key);
|
||||
return Range(start, end);
|
||||
}
|
||||
|
||||
// Finds the node associated with the key
|
||||
const RBNode* find_node(const K& key) const;
|
||||
|
||||
RBNode* find_node(const K& key) {
|
||||
return const_cast<RBNode*>(
|
||||
static_cast<const RBTree<K, V, COMPARATOR, ALLOCATOR>*>(this)->find_node(key));
|
||||
}
|
||||
|
||||
// Finds the value associated with the key
|
||||
V* find(const K& key) {
|
||||
RBNode* node = find_node(key);
|
||||
return node == nullptr ? nullptr : &node->val();
|
||||
}
|
||||
|
||||
const V* find(const K& key) const {
|
||||
const RBNode* node = find_node(key);
|
||||
return node == nullptr ? nullptr : &node->val();
|
||||
}
|
||||
|
||||
// Visit all RBNodes in ascending order, calling f on each node.
|
||||
template <typename F>
|
||||
void visit_in_order(F f) const;
|
||||
|
||||
// Visit all RBNodes in ascending order whose keys are in range [from, to), calling f on each node.
|
||||
template <typename F>
|
||||
void visit_range_in_order(const K& from, const K& to, F f);
|
||||
|
||||
#ifdef ASSERT
|
||||
// Verifies that the tree is correct and holds rb-properties
|
||||
void verify_self();
|
||||
#endif // ASSERT
|
||||
|
||||
};
|
||||
|
||||
template <MemTag mem_tag>
|
||||
class RBTreeCHeapAllocator {
|
||||
public:
|
||||
void* allocate(size_t sz) {
|
||||
void* allocation = os::malloc(sz, mem_tag);
|
||||
if (allocation == nullptr) {
|
||||
vm_exit_out_of_memory(sz, OOM_MALLOC_ERROR,
|
||||
"red-black tree failed allocation");
|
||||
}
|
||||
return allocation;
|
||||
}
|
||||
|
||||
void free(void* ptr) { os::free(ptr); }
|
||||
};
|
||||
|
||||
template <typename K, typename V, typename COMPARATOR, MemTag mem_tag>
|
||||
using RBTreeCHeap = RBTree<K, V, COMPARATOR, RBTreeCHeapAllocator<mem_tag>>;
|
||||
|
||||
#endif // SHARE_UTILITIES_RBTREE_HPP
|
||||
541
src/hotspot/share/utilities/rbTree.inline.hpp
Normal file
541
src/hotspot/share/utilities/rbTree.inline.hpp
Normal file
@ -0,0 +1,541 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SHARE_UTILITIES_RBTREE_INLINE_HPP
|
||||
#define SHARE_UTILITIES_RBTREE_INLINE_HPP
|
||||
|
||||
#include "utilities/debug.hpp"
|
||||
#include "utilities/globalDefinitions.hpp"
|
||||
#include "utilities/powerOfTwo.hpp"
|
||||
#include "utilities/rbTree.hpp"
|
||||
|
||||
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
|
||||
inline void RBTree<K, V, COMPARATOR, ALLOCATOR>::RBNode::replace_child(
|
||||
RBNode* old_child, RBNode* new_child) {
|
||||
if (_left == old_child) {
|
||||
_left = new_child;
|
||||
} else if (_right == old_child) {
|
||||
_right = new_child;
|
||||
} else {
|
||||
ShouldNotReachHere();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
|
||||
inline typename RBTree<K, V, COMPARATOR, ALLOCATOR>::RBNode*
|
||||
RBTree<K, V, COMPARATOR, ALLOCATOR>::RBNode::rotate_left() {
|
||||
// This node down, right child up
|
||||
RBNode* old_right = _right;
|
||||
|
||||
_right = old_right->_left;
|
||||
if (_right != nullptr) {
|
||||
_right->set_parent(this);
|
||||
}
|
||||
|
||||
old_right->set_parent(parent());
|
||||
if (parent() != nullptr) {
|
||||
parent()->replace_child(this, old_right);
|
||||
}
|
||||
|
||||
old_right->_left = this;
|
||||
set_parent(old_right);
|
||||
|
||||
return old_right;
|
||||
}
|
||||
|
||||
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
|
||||
inline typename RBTree<K, V, COMPARATOR, ALLOCATOR>::RBNode*
|
||||
RBTree<K, V, COMPARATOR, ALLOCATOR>::RBNode::rotate_right() {
|
||||
// This node down, left child up
|
||||
RBNode* old_left = _left;
|
||||
|
||||
_left = old_left->_right;
|
||||
if (_left != nullptr) {
|
||||
_left->set_parent(this);
|
||||
}
|
||||
|
||||
old_left->set_parent(parent());
|
||||
if (parent() != nullptr) {
|
||||
parent()->replace_child(this, old_left);
|
||||
}
|
||||
|
||||
old_left->_right = this;
|
||||
set_parent(old_left);
|
||||
|
||||
return old_left;
|
||||
}
|
||||
|
||||
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
|
||||
inline typename RBTree<K, V, COMPARATOR, ALLOCATOR>::RBNode*
|
||||
RBTree<K, V, COMPARATOR, ALLOCATOR>::RBNode::prev() {
|
||||
RBNode* node = this;
|
||||
if (_left != nullptr) { // right subtree exists
|
||||
node = _left;
|
||||
while (node->_right != nullptr) {
|
||||
node = node->_right;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
while (node != nullptr && node->is_left_child()) {
|
||||
node = node->parent();
|
||||
}
|
||||
return node->parent();
|
||||
}
|
||||
|
||||
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
|
||||
inline typename RBTree<K, V, COMPARATOR, ALLOCATOR>::RBNode*
|
||||
RBTree<K, V, COMPARATOR, ALLOCATOR>::RBNode::next() {
|
||||
RBNode* node = this;
|
||||
if (_right != nullptr) { // right subtree exists
|
||||
node = _right;
|
||||
while (node->_left != nullptr) {
|
||||
node = node->_left;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
while (node != nullptr && node->is_right_child()) {
|
||||
node = node->parent();
|
||||
}
|
||||
return node->parent();
|
||||
}
|
||||
|
||||
#ifdef ASSERT
|
||||
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
|
||||
inline void RBTree<K, V, COMPARATOR, ALLOCATOR>::RBNode::verify(
|
||||
size_t& num_nodes, size_t& black_nodes_until_leaf, size_t& shortest_leaf_path, size_t& longest_leaf_path,
|
||||
size_t& tree_depth, bool expect_visited) {
|
||||
assert(expect_visited != _visited, "node already visited");
|
||||
_visited = !_visited;
|
||||
|
||||
size_t num_black_nodes_left = 0;
|
||||
size_t shortest_leaf_path_left = 0;
|
||||
size_t longest_leaf_path_left = 0;
|
||||
size_t tree_depth_left = 0;
|
||||
|
||||
if (_left != nullptr) {
|
||||
if (_right == nullptr) {
|
||||
assert(is_black() && _left->is_red(), "if one child it must be red and node black");
|
||||
}
|
||||
assert(COMPARATOR::cmp(_left->key(), _key) < 0, "left node must be less than parent");
|
||||
assert(is_black() || _left->is_black(), "2 red nodes in a row");
|
||||
assert(_left->parent() == this, "pointer mismatch");
|
||||
_left->verify(num_nodes, num_black_nodes_left, shortest_leaf_path_left,
|
||||
longest_leaf_path_left, tree_depth_left, expect_visited);
|
||||
}
|
||||
|
||||
size_t num_black_nodes_right = 0;
|
||||
size_t shortest_leaf_path_right = 0;
|
||||
size_t longest_leaf_path_right = 0;
|
||||
size_t tree_depth_right = 0;
|
||||
|
||||
if (_right != nullptr) {
|
||||
if (_left == nullptr) {
|
||||
assert(is_black() && _right->is_red(), "if one child it must be red and node black");
|
||||
}
|
||||
assert(COMPARATOR::cmp(_right->key(), _key) > 0, "right node must be greater than parent");
|
||||
assert(is_black() || _left->is_black(), "2 red nodes in a row");
|
||||
assert(_right->parent() == this, "pointer mismatch");
|
||||
_right->verify(num_nodes, num_black_nodes_right, shortest_leaf_path_right,
|
||||
longest_leaf_path_right, tree_depth_right, expect_visited);
|
||||
}
|
||||
|
||||
shortest_leaf_path = MAX2(longest_leaf_path_left, longest_leaf_path_right);
|
||||
longest_leaf_path = MAX2(longest_leaf_path_left, longest_leaf_path_right);
|
||||
|
||||
assert(shortest_leaf_path <= longest_leaf_path && longest_leaf_path <= shortest_leaf_path * 2,
|
||||
"tree imbalanced, shortest path: %zu longest: %zu", shortest_leaf_path, longest_leaf_path);
|
||||
assert(num_black_nodes_left == num_black_nodes_right,
|
||||
"number of black nodes in left/right subtree should match");
|
||||
|
||||
num_nodes++;
|
||||
tree_depth = 1 + MAX2(tree_depth_left, tree_depth_right);
|
||||
|
||||
shortest_leaf_path++;
|
||||
longest_leaf_path++;
|
||||
|
||||
black_nodes_until_leaf = num_black_nodes_left;
|
||||
if (is_black()) {
|
||||
black_nodes_until_leaf++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // ASSERT
|
||||
|
||||
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
|
||||
inline const typename RBTree<K, V, COMPARATOR, ALLOCATOR>::RBNode*
|
||||
RBTree<K, V, COMPARATOR, ALLOCATOR>::find_node(const K& key) const {
|
||||
RBNode* curr = _root;
|
||||
while (curr != nullptr) {
|
||||
const int key_cmp_k = COMPARATOR::cmp(key, curr->key());
|
||||
|
||||
if (key_cmp_k == 0) {
|
||||
return curr;
|
||||
} else if (key_cmp_k < 0) {
|
||||
curr = curr->_left;
|
||||
} else {
|
||||
curr = curr->_right;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
|
||||
inline typename RBTree<K, V, COMPARATOR, ALLOCATOR>::RBNode*
|
||||
RBTree<K, V, COMPARATOR, ALLOCATOR>::insert_node(const K& key, const V& val) {
|
||||
RBNode* curr = _root;
|
||||
if (curr == nullptr) { // Tree is empty
|
||||
_root = allocate_node(key, val);
|
||||
return _root;
|
||||
}
|
||||
|
||||
RBNode* parent = nullptr;
|
||||
while (curr != nullptr) {
|
||||
const int key_cmp_k = COMPARATOR::cmp(key, curr->key());
|
||||
|
||||
if (key_cmp_k == 0) {
|
||||
curr->_value = val;
|
||||
return curr;
|
||||
}
|
||||
|
||||
parent = curr;
|
||||
if (key_cmp_k < 0) {
|
||||
curr = curr->_left;
|
||||
} else {
|
||||
curr = curr->_right;
|
||||
}
|
||||
}
|
||||
|
||||
// Create and insert new node
|
||||
RBNode* node = allocate_node(key, val);
|
||||
node->set_parent(parent);
|
||||
|
||||
const int key_cmp_k = COMPARATOR::cmp(key, parent->key());
|
||||
if (key_cmp_k < 0) {
|
||||
parent->_left = node;
|
||||
} else {
|
||||
parent->_right = node;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
|
||||
inline void RBTree<K, V, COMPARATOR, ALLOCATOR>::fix_insert_violations(RBNode* node) {
|
||||
if (node->is_black()) { // node's value was updated
|
||||
return; // Tree is already correct
|
||||
}
|
||||
|
||||
RBNode* parent = node->parent();
|
||||
while (parent != nullptr && parent->is_red()) {
|
||||
// Node and parent are both red, creating a red-violation
|
||||
|
||||
RBNode* grandparent = parent->parent();
|
||||
if (grandparent == nullptr) { // Parent is the tree root
|
||||
assert(parent == _root, "parent must be root");
|
||||
parent->set_black(); // Color parent black to eliminate the red-violation
|
||||
return;
|
||||
}
|
||||
|
||||
RBNode* uncle = parent->is_left_child() ? grandparent->_right : grandparent->_left;
|
||||
if (is_black(uncle)) { // Parent is red, uncle is black
|
||||
// Rotate the parent to the position of the grandparent
|
||||
if (parent->is_left_child()) {
|
||||
if (node->is_right_child()) { // Node is an "inner" node
|
||||
// Rotate and swap node and parent to make it an "outer" node
|
||||
parent->rotate_left();
|
||||
parent = node;
|
||||
}
|
||||
grandparent->rotate_right(); // Rotate the parent to the position of the grandparent
|
||||
} else if (parent->is_right_child()) {
|
||||
if (node->is_left_child()) { // Node is an "inner" node
|
||||
// Rotate and swap node and parent to make it an "outer" node
|
||||
parent->rotate_right();
|
||||
parent = node;
|
||||
}
|
||||
grandparent->rotate_left(); // Rotate the parent to the position of the grandparent
|
||||
}
|
||||
|
||||
// Swap parent and grandparent colors to eliminate the red-violation
|
||||
parent->set_black();
|
||||
grandparent->set_red();
|
||||
|
||||
if (_root == grandparent) {
|
||||
_root = parent;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Parent and uncle are both red
|
||||
// Paint both black, paint grandparent red to not create a black-violation
|
||||
parent->set_black();
|
||||
uncle->set_black();
|
||||
grandparent->set_red();
|
||||
|
||||
// Move up two levels to check for new potential red-violation
|
||||
node = grandparent;
|
||||
parent = grandparent->parent();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
|
||||
inline void RBTree<K, V, COMPARATOR, ALLOCATOR>::remove_black_leaf(RBNode* node) {
|
||||
// Black node removed, balancing needed
|
||||
RBNode* parent = node->parent();
|
||||
while (parent != nullptr) {
|
||||
// Sibling must exist. If it did not, node would need to be red to not break
|
||||
// tree properties, and could be trivially removed before reaching here
|
||||
RBNode* sibling = node->is_left_child() ? parent->_right : parent->_left;
|
||||
if (is_red(sibling)) { // Sibling red, parent and nephews must be black
|
||||
assert(is_black(parent), "parent must be black");
|
||||
assert(is_black(sibling->_left), "nephew must be black");
|
||||
assert(is_black(sibling->_right), "nephew must be black");
|
||||
// Swap parent and sibling colors
|
||||
parent->set_red();
|
||||
sibling->set_black();
|
||||
|
||||
// Rotate parent down and sibling up
|
||||
if (node->is_left_child()) {
|
||||
parent->rotate_left();
|
||||
sibling = parent->_right;
|
||||
} else {
|
||||
parent->rotate_right();
|
||||
sibling = parent->_left;
|
||||
}
|
||||
|
||||
if (_root == parent) {
|
||||
_root = parent->parent();
|
||||
}
|
||||
// Further balancing needed
|
||||
}
|
||||
|
||||
RBNode* close_nephew = node->is_left_child() ? sibling->_left : sibling->_right;
|
||||
RBNode* distant_nephew = node->is_left_child() ? sibling->_right : sibling->_left;
|
||||
if (is_red(distant_nephew) || is_red(close_nephew)) {
|
||||
if (is_black(distant_nephew)) { // close red, distant black
|
||||
// Rotate sibling down and inner nephew up
|
||||
if (node->is_left_child()) {
|
||||
sibling->rotate_right();
|
||||
} else {
|
||||
sibling->rotate_left();
|
||||
}
|
||||
|
||||
distant_nephew = sibling;
|
||||
sibling = close_nephew;
|
||||
|
||||
distant_nephew->set_red();
|
||||
sibling->set_black();
|
||||
}
|
||||
|
||||
// Distant nephew red
|
||||
// Rotate parent down and sibling up
|
||||
if (node->is_left_child()) {
|
||||
parent->rotate_left();
|
||||
} else {
|
||||
parent->rotate_right();
|
||||
}
|
||||
if (_root == parent) {
|
||||
_root = sibling;
|
||||
}
|
||||
|
||||
// Swap parent and sibling colors
|
||||
if (parent->is_black()) {
|
||||
sibling->set_black();
|
||||
} else {
|
||||
sibling->set_red();
|
||||
}
|
||||
parent->set_black();
|
||||
|
||||
// Color distant nephew black to restore black balance
|
||||
distant_nephew->set_black();
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_red(parent)) { // parent red, sibling and nephews black
|
||||
// Swap parent and sibling colors to restore black balance
|
||||
sibling->set_red();
|
||||
parent->set_black();
|
||||
return;
|
||||
}
|
||||
|
||||
// Parent, sibling, and both nephews black
|
||||
// Color sibling red and move up one level
|
||||
sibling->set_red();
|
||||
node = parent;
|
||||
parent = node->parent();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
|
||||
inline void RBTree<K, V, COMPARATOR, ALLOCATOR>::remove_from_tree(RBNode* node) {
|
||||
RBNode* parent = node->parent();
|
||||
RBNode* left = node->_left;
|
||||
RBNode* right = node->_right;
|
||||
if (left != nullptr) { // node has a left only-child
|
||||
// node must be black, and child red, otherwise a black-violation would
|
||||
// exist Remove node and color the child black.
|
||||
assert(right == nullptr, "right must be nullptr");
|
||||
assert(is_black(node), "node must be black");
|
||||
assert(is_red(left), "child must be red");
|
||||
left->set_black();
|
||||
left->set_parent(parent);
|
||||
if (parent == nullptr) {
|
||||
assert(node == _root, "node must be root");
|
||||
_root = left;
|
||||
} else {
|
||||
parent->replace_child(node, left);
|
||||
}
|
||||
} else if (right != nullptr) { // node has a right only-child
|
||||
// node must be black, and child red, otherwise a black-violation would
|
||||
// exist Remove node and color the child black.
|
||||
assert(left == nullptr, "left must be nullptr");
|
||||
assert(is_black(node), "node must be black");
|
||||
assert(is_red(right), "child must be red");
|
||||
right->set_black();
|
||||
right->set_parent(parent);
|
||||
if (parent == nullptr) {
|
||||
assert(node == _root, "node must be root");
|
||||
_root = right;
|
||||
} else {
|
||||
parent->replace_child(node, right);
|
||||
}
|
||||
} else { // node has no children
|
||||
if (node == _root) { // Tree empty
|
||||
_root = nullptr;
|
||||
} else {
|
||||
if (is_black(node)) {
|
||||
// Removed node is black, creating a black imbalance
|
||||
remove_black_leaf(node);
|
||||
}
|
||||
parent->replace_child(node, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
|
||||
inline void RBTree<K, V, COMPARATOR, ALLOCATOR>::remove(RBNode* node) {
|
||||
assert(node != nullptr, "must be");
|
||||
|
||||
if (node->_left != nullptr && node->_right != nullptr) { // node has two children
|
||||
// Swap place with the in-order successor and delete there instead
|
||||
RBNode* curr = node->_right;
|
||||
while (curr->_left != nullptr) {
|
||||
curr = curr->_left;
|
||||
}
|
||||
|
||||
if (_root == node) _root = curr;
|
||||
|
||||
swap(curr->_left, node->_left);
|
||||
swap(curr->_parent, node->_parent); // Swaps parent and color
|
||||
|
||||
// If node is curr's parent, parent and right pointers become invalid
|
||||
if (node->_right == curr) {
|
||||
node->_right = curr->_right;
|
||||
node->set_parent(curr);
|
||||
curr->_right = node;
|
||||
} else {
|
||||
swap(curr->_right, node->_right);
|
||||
node->parent()->replace_child(curr, node);
|
||||
curr->_right->set_parent(curr);
|
||||
}
|
||||
|
||||
if (curr->parent() != nullptr) curr->parent()->replace_child(node, curr);
|
||||
curr->_left->set_parent(curr);
|
||||
|
||||
|
||||
if (node->_left != nullptr) node->_left->set_parent(node);
|
||||
if (node->_right != nullptr) node->_right->set_parent(node);
|
||||
}
|
||||
|
||||
remove_from_tree(node);
|
||||
free_node(node);
|
||||
}
|
||||
|
||||
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
|
||||
template <typename F>
|
||||
inline void RBTree<K, V, COMPARATOR, ALLOCATOR>::visit_in_order(F f) const {
|
||||
RBNode* to_visit[64];
|
||||
int stack_idx = 0;
|
||||
RBNode* head = _root;
|
||||
while (stack_idx > 0 || head != nullptr) {
|
||||
while (head != nullptr) {
|
||||
to_visit[stack_idx++] = head;
|
||||
head = head->_left;
|
||||
}
|
||||
head = to_visit[--stack_idx];
|
||||
f(head);
|
||||
head = head->_right;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
|
||||
template <typename F>
|
||||
inline void RBTree<K, V, COMPARATOR, ALLOCATOR>::visit_range_in_order(const K& from, const K& to, F f) {
|
||||
assert(COMPARATOR::cmp(from, to) <= 0, "from must be less or equal to to");
|
||||
RBNode* curr = closest_geq(from);
|
||||
if (curr == nullptr) return;
|
||||
RBNode* end = closest_geq(to);
|
||||
|
||||
while (curr != nullptr && curr != end) {
|
||||
f(curr);
|
||||
curr = curr->next();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ASSERT
|
||||
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
|
||||
inline void RBTree<K, V, COMPARATOR, ALLOCATOR>::verify_self() {
|
||||
if (_root == nullptr) {
|
||||
assert(_num_nodes == 0, "rbtree has nodes but no root");
|
||||
return;
|
||||
}
|
||||
|
||||
assert(_root->parent() == nullptr, "root of rbtree has a parent");
|
||||
|
||||
size_t num_nodes = 0;
|
||||
size_t black_depth = 0;
|
||||
size_t tree_depth = 0;
|
||||
size_t shortest_leaf_path = 0;
|
||||
size_t longest_leaf_path = 0;
|
||||
_expected_visited = !_expected_visited;
|
||||
|
||||
_root->verify(num_nodes, black_depth, shortest_leaf_path, longest_leaf_path, tree_depth, _expected_visited);
|
||||
|
||||
const unsigned int maximum_depth = log2i(size() + 1) * 2;
|
||||
|
||||
assert(shortest_leaf_path <= longest_leaf_path && longest_leaf_path <= shortest_leaf_path * 2,
|
||||
"tree imbalanced, shortest path: %zu longest: %zu",
|
||||
shortest_leaf_path, longest_leaf_path);
|
||||
assert(tree_depth <= maximum_depth, "rbtree is too deep");
|
||||
assert(size() == num_nodes,
|
||||
"unexpected number of nodes in rbtree. expected: %zu"
|
||||
", actual: %zu", size(), num_nodes);
|
||||
}
|
||||
#endif // ASSERT
|
||||
|
||||
#endif // SHARE_UTILITIES_RBTREE_INLINE_HPP
|
||||
573
test/hotspot/gtest/utilities/test_rbtree.cpp
Normal file
573
test/hotspot/gtest/utilities/test_rbtree.cpp
Normal file
@ -0,0 +1,573 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "memory/resourceArea.hpp"
|
||||
#include "runtime/os.hpp"
|
||||
#include "testutils.hpp"
|
||||
#include "unittest.hpp"
|
||||
#include "utilities/growableArray.hpp"
|
||||
#include "utilities/rbTree.hpp"
|
||||
#include "utilities/rbTree.inline.hpp"
|
||||
|
||||
|
||||
class RBTreeTest : public testing::Test {
|
||||
public:
|
||||
struct Cmp {
|
||||
static int cmp(int a, int b) {
|
||||
return a - b;
|
||||
}
|
||||
};
|
||||
|
||||
struct CmpInverse {
|
||||
static int cmp(int a, int b) {
|
||||
return b - a;
|
||||
}
|
||||
};
|
||||
|
||||
struct FCmp {
|
||||
static int cmp(float a, float b) {
|
||||
if (a < b) return -1;
|
||||
if (a == b) return 0;
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
// Bump-pointer style allocator that can't free
|
||||
template <size_t AreaSize>
|
||||
struct ArrayAllocator {
|
||||
uint8_t area[AreaSize];
|
||||
size_t offset = 0;
|
||||
|
||||
void* allocate(size_t sz) {
|
||||
if (offset + sz > AreaSize) {
|
||||
vm_exit_out_of_memory(sz, OOM_MALLOC_ERROR,
|
||||
"red-black tree failed allocation");
|
||||
}
|
||||
void* place = &area[offset];
|
||||
offset += sz;
|
||||
return place;
|
||||
}
|
||||
|
||||
void free(void* ptr) { }
|
||||
};
|
||||
|
||||
#ifdef ASSERT
|
||||
template<typename K, typename V, typename CMP, typename ALLOC>
|
||||
void verify_it(RBTree<K, V, CMP, ALLOC>& t) {
|
||||
t.verify_self();
|
||||
}
|
||||
#endif // ASSERT
|
||||
|
||||
using RBTreeInt = RBTreeCHeap<int, int, Cmp, mtOther>;
|
||||
|
||||
public:
|
||||
void inserting_duplicates_results_in_one_value() {
|
||||
constexpr int up_to = 10;
|
||||
GrowableArrayCHeap<int, mtTest> nums_seen(up_to, up_to, 0);
|
||||
RBTreeInt rbtree;
|
||||
|
||||
for (int i = 0; i < up_to; i++) {
|
||||
rbtree.upsert(i, i);
|
||||
rbtree.upsert(i, i);
|
||||
rbtree.upsert(i, i);
|
||||
rbtree.upsert(i, i);
|
||||
rbtree.upsert(i, i);
|
||||
}
|
||||
|
||||
rbtree.visit_in_order([&](RBTreeInt::RBNode* node) {
|
||||
nums_seen.at(node->key())++;
|
||||
});
|
||||
for (int i = 0; i < up_to; i++) {
|
||||
EXPECT_EQ(1, nums_seen.at(i));
|
||||
}
|
||||
}
|
||||
|
||||
void rbtree_ought_not_leak() {
|
||||
struct LeakCheckedAllocator {
|
||||
int allocations;
|
||||
|
||||
LeakCheckedAllocator()
|
||||
: allocations(0) {
|
||||
}
|
||||
|
||||
void* allocate(size_t sz) {
|
||||
void* allocation = os::malloc(sz, mtTest);
|
||||
if (allocation == nullptr) {
|
||||
vm_exit_out_of_memory(sz, OOM_MALLOC_ERROR, "rbtree failed allocation");
|
||||
}
|
||||
++allocations;
|
||||
return allocation;
|
||||
}
|
||||
|
||||
void free(void* ptr) {
|
||||
--allocations;
|
||||
os::free(ptr);
|
||||
}
|
||||
};
|
||||
|
||||
constexpr int up_to = 10;
|
||||
{
|
||||
RBTree<int, int, Cmp, LeakCheckedAllocator> rbtree;
|
||||
for (int i = 0; i < up_to; i++) {
|
||||
rbtree.upsert(i, i);
|
||||
}
|
||||
EXPECT_EQ(up_to, rbtree._allocator.allocations);
|
||||
for (int i = 0; i < up_to; i++) {
|
||||
rbtree.remove(i);
|
||||
}
|
||||
EXPECT_EQ(0, rbtree._allocator.allocations);
|
||||
EXPECT_EQ(nullptr, rbtree._root);
|
||||
}
|
||||
|
||||
{
|
||||
RBTree<int, int, Cmp, LeakCheckedAllocator> rbtree;
|
||||
for (int i = 0; i < up_to; i++) {
|
||||
rbtree.upsert(i, i);
|
||||
}
|
||||
rbtree.remove_all();
|
||||
EXPECT_EQ(0, rbtree._allocator.allocations);
|
||||
EXPECT_EQ(nullptr, rbtree._root);
|
||||
}
|
||||
}
|
||||
|
||||
void test_find() {
|
||||
struct Empty {};
|
||||
RBTreeCHeap<float, Empty, FCmp, mtOther> rbtree;
|
||||
using Node = RBTreeCHeap<float, Empty, FCmp, mtOther>::RBNode;
|
||||
|
||||
Node* n = nullptr;
|
||||
auto test = [&](float f) {
|
||||
EXPECT_EQ(nullptr, rbtree.find(f));
|
||||
rbtree.upsert(f, Empty{});
|
||||
const Node* n = rbtree.find_node(f);
|
||||
EXPECT_NE(nullptr, n);
|
||||
EXPECT_EQ(f, n->key());
|
||||
};
|
||||
|
||||
test(1.0f);
|
||||
test(5.0f);
|
||||
test(0.0f);
|
||||
}
|
||||
|
||||
void test_visitors() {
|
||||
{ // Tests with 'default' ordering (ascending)
|
||||
RBTreeInt rbtree;
|
||||
using Node = RBTreeInt::RBNode;
|
||||
|
||||
rbtree.visit_range_in_order(0, 100, [&](Node* x) {
|
||||
EXPECT_TRUE(false) << "Empty rbtree has no nodes to visit";
|
||||
});
|
||||
|
||||
// Single-element set
|
||||
rbtree.upsert(1, 0);
|
||||
int count = 0;
|
||||
rbtree.visit_range_in_order(0, 100, [&](Node* x) {
|
||||
count++;
|
||||
});
|
||||
EXPECT_EQ(1, count);
|
||||
|
||||
count = 0;
|
||||
rbtree.visit_in_order([&](Node* x) {
|
||||
count++;
|
||||
});
|
||||
EXPECT_EQ(1, count);
|
||||
|
||||
// Add an element outside of the range that should not be visited on the right side and
|
||||
// one on the left side.
|
||||
rbtree.upsert(101, 0);
|
||||
rbtree.upsert(-1, 0);
|
||||
count = 0;
|
||||
rbtree.visit_range_in_order(0, 100, [&](Node* x) {
|
||||
count++;
|
||||
});
|
||||
EXPECT_EQ(1, count);
|
||||
|
||||
count = 0;
|
||||
rbtree.visit_in_order([&](Node* x) {
|
||||
count++;
|
||||
});
|
||||
EXPECT_EQ(3, count);
|
||||
|
||||
// Visiting empty range [0, 0) == {}
|
||||
rbtree.upsert(0, 0); // This node should not be visited.
|
||||
rbtree.visit_range_in_order(0, 0, [&](Node* x) {
|
||||
EXPECT_TRUE(false) << "Empty visiting range should not visit any node";
|
||||
});
|
||||
|
||||
rbtree.remove_all();
|
||||
for (int i = 0; i < 11; i++) {
|
||||
rbtree.upsert(i, 0);
|
||||
}
|
||||
|
||||
ResourceMark rm;
|
||||
GrowableArray<int> seen;
|
||||
rbtree.visit_range_in_order(0, 10, [&](Node* x) {
|
||||
seen.push(x->key());
|
||||
});
|
||||
EXPECT_EQ(10, seen.length());
|
||||
for (int i = 0; i < 10; i++) {
|
||||
EXPECT_EQ(i, seen.at(i));
|
||||
}
|
||||
|
||||
seen.clear();
|
||||
rbtree.visit_in_order([&](Node* x) {
|
||||
seen.push(x->key());
|
||||
});
|
||||
EXPECT_EQ(11, seen.length());
|
||||
for (int i = 0; i < 10; i++) {
|
||||
EXPECT_EQ(i, seen.at(i));
|
||||
}
|
||||
|
||||
seen.clear();
|
||||
rbtree.visit_range_in_order(10, 12, [&](Node* x) {
|
||||
seen.push(x->key());
|
||||
});
|
||||
EXPECT_EQ(1, seen.length());
|
||||
EXPECT_EQ(10, seen.at(0));
|
||||
}
|
||||
{ // Test with descending ordering
|
||||
RBTreeCHeap<int, int, CmpInverse, mtOther> rbtree;
|
||||
using Node = RBTreeCHeap<int, int, CmpInverse, mtOther>::RBNode;
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
rbtree.upsert(i, 0);
|
||||
}
|
||||
ResourceMark rm;
|
||||
GrowableArray<int> seen;
|
||||
rbtree.visit_range_in_order(9, -1, [&](Node* x) {
|
||||
seen.push(x->key());
|
||||
});
|
||||
EXPECT_EQ(10, seen.length());
|
||||
for (int i = 0; i < 10; i++) {
|
||||
EXPECT_EQ(10-i-1, seen.at(i));
|
||||
}
|
||||
seen.clear();
|
||||
|
||||
rbtree.visit_in_order([&](Node* x) {
|
||||
seen.push(x->key());
|
||||
});
|
||||
EXPECT_EQ(10, seen.length());
|
||||
for (int i = 0; i < 10; i++) {
|
||||
EXPECT_EQ(10 - i - 1, seen.at(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void test_closest_leq() {
|
||||
using Node = RBTreeInt::RBNode;
|
||||
{
|
||||
RBTreeInt rbtree;
|
||||
Node* n = rbtree.closest_leq(0);
|
||||
EXPECT_EQ(nullptr, n);
|
||||
|
||||
rbtree.upsert(0, 0);
|
||||
n = rbtree.closest_leq(0);
|
||||
EXPECT_EQ(0, n->key());
|
||||
|
||||
rbtree.upsert(-1, -1);
|
||||
n = rbtree.closest_leq(0);
|
||||
EXPECT_EQ(0, n->key());
|
||||
|
||||
rbtree.upsert(6, 0);
|
||||
n = rbtree.closest_leq(6);
|
||||
EXPECT_EQ(6, n->key());
|
||||
|
||||
n = rbtree.closest_leq(-2);
|
||||
EXPECT_EQ(nullptr, n);
|
||||
}
|
||||
}
|
||||
|
||||
void test_node_prev() {
|
||||
RBTreeInt _tree;
|
||||
using Node = RBTreeInt::RBNode;
|
||||
constexpr int num_nodes = 100;
|
||||
|
||||
for (int i = num_nodes; i > 0; i--) {
|
||||
_tree.upsert(i, i);
|
||||
}
|
||||
|
||||
Node* node = _tree.find_node(num_nodes);
|
||||
int count = num_nodes;
|
||||
while (node != nullptr) {
|
||||
EXPECT_EQ(count, node->val());
|
||||
node = node->prev();
|
||||
count--;
|
||||
}
|
||||
|
||||
EXPECT_EQ(count, 0);
|
||||
}
|
||||
|
||||
void test_node_next() {
|
||||
RBTreeInt _tree;
|
||||
using Node = RBTreeInt::RBNode;
|
||||
constexpr int num_nodes = 100;
|
||||
|
||||
for (int i = 0; i < num_nodes; i++) {
|
||||
_tree.upsert(i, i);
|
||||
}
|
||||
|
||||
Node* node = _tree.find_node(0);
|
||||
int count = 0;
|
||||
while (node != nullptr) {
|
||||
EXPECT_EQ(count, node->val());
|
||||
node = node->next();
|
||||
count++;
|
||||
}
|
||||
|
||||
EXPECT_EQ(count, num_nodes);
|
||||
}
|
||||
|
||||
void test_stable_nodes() {
|
||||
using Node = RBTreeInt::RBNode;
|
||||
RBTreeInt rbtree;
|
||||
ResourceMark rm;
|
||||
GrowableArray<Node*> a(10000);
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
rbtree.upsert(i, i);
|
||||
a.push(rbtree.find_node(i));
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2000; i++) {
|
||||
int r = os::random() % 10000;
|
||||
Node* to_delete = rbtree.find_node(r);
|
||||
if (to_delete != nullptr && to_delete->_left != nullptr &&
|
||||
to_delete->_right != nullptr) {
|
||||
rbtree.remove(to_delete);
|
||||
}
|
||||
}
|
||||
|
||||
// After deleting, nodes should have been moved around but kept their values
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
const Node* n = rbtree.find_node(i);
|
||||
if (n != nullptr) {
|
||||
EXPECT_EQ(a.at(i), n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void test_stable_nodes_addresses() {
|
||||
using Tree = RBTreeCHeap<int, void*, Cmp, mtOther>;
|
||||
using Node = Tree::RBNode;
|
||||
Tree rbtree;
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
rbtree.upsert(i, nullptr);
|
||||
Node* inserted_node = rbtree.find_node(i);
|
||||
inserted_node->val() = inserted_node;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2000; i++) {
|
||||
int r = os::random() % 10000;
|
||||
Node* to_delete = rbtree.find_node(r);
|
||||
if (to_delete != nullptr && to_delete->_left != nullptr &&
|
||||
to_delete->_right != nullptr) {
|
||||
rbtree.remove(to_delete);
|
||||
}
|
||||
}
|
||||
|
||||
// After deleting, values should have remained consistant
|
||||
rbtree.visit_in_order([&](Node* node) {
|
||||
EXPECT_EQ(node, node->val());
|
||||
});
|
||||
}
|
||||
|
||||
#ifdef ASSERT
|
||||
void test_fill_verify() {
|
||||
RBTreeInt rbtree;
|
||||
|
||||
ResourceMark rm;
|
||||
GrowableArray<int> allocations;
|
||||
|
||||
int size = 10000;
|
||||
// Create random values
|
||||
for (int i = 0; i < size; i++) {
|
||||
int r = os::random() % size;
|
||||
allocations.append(r);
|
||||
}
|
||||
|
||||
// Insert ~half of the values
|
||||
for (int i = 0; i < size; i++) {
|
||||
int r = os::random();
|
||||
if (r % 2 == 0) {
|
||||
rbtree.upsert(allocations.at(i), allocations.at(i));
|
||||
}
|
||||
if (i % 100 == 0) {
|
||||
verify_it(rbtree);
|
||||
}
|
||||
}
|
||||
|
||||
// Insert and remove randomly
|
||||
for (int i = 0; i < size; i++) {
|
||||
int r = os::random();
|
||||
if (r % 2 == 0) {
|
||||
rbtree.upsert(allocations.at(i), allocations.at(i));
|
||||
} else {
|
||||
rbtree.remove(allocations.at(i));
|
||||
}
|
||||
if (i % 100 == 0) {
|
||||
verify_it(rbtree);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all elements
|
||||
for (int i = 0; i < size; i++) {
|
||||
rbtree.remove(allocations.at(i));
|
||||
}
|
||||
|
||||
verify_it(rbtree);
|
||||
EXPECT_EQ(rbtree.size(), 0UL);
|
||||
}
|
||||
|
||||
void test_nodes_visited_once() {
|
||||
constexpr size_t memory_size = 65536;
|
||||
using Tree = RBTree<int, int, Cmp, ArrayAllocator<memory_size>>;
|
||||
using Node = Tree::RBNode;
|
||||
|
||||
Tree tree;
|
||||
|
||||
int num_nodes = memory_size / sizeof(Node);
|
||||
for (int i = 0; i < num_nodes; i++) {
|
||||
tree.upsert(i, i);
|
||||
}
|
||||
|
||||
Node* start = tree.find_node(0);
|
||||
|
||||
Node* node = start;
|
||||
for (int i = 0; i < num_nodes; i++) {
|
||||
EXPECT_EQ(tree._expected_visited, node->_visited);
|
||||
node += 1;
|
||||
}
|
||||
|
||||
verify_it(tree);
|
||||
|
||||
node = start;
|
||||
for (int i = 0; i < num_nodes; i++) {
|
||||
EXPECT_EQ(tree._expected_visited, node->_visited);
|
||||
node += 1;
|
||||
}
|
||||
|
||||
}
|
||||
#endif // ASSERT
|
||||
|
||||
};
|
||||
|
||||
TEST_VM_F(RBTreeTest, InsertingDuplicatesResultsInOneValue) {
|
||||
this->inserting_duplicates_results_in_one_value();
|
||||
}
|
||||
|
||||
TEST_VM_F(RBTreeTest, RBTreeOughtNotLeak) {
|
||||
this->rbtree_ought_not_leak();
|
||||
}
|
||||
|
||||
TEST_VM_F(RBTreeTest, TestFind) {
|
||||
this->test_find();
|
||||
}
|
||||
|
||||
TEST_VM_F(RBTreeTest, TestVisitors) {
|
||||
this->test_visitors();
|
||||
}
|
||||
|
||||
TEST_VM_F(RBTreeTest, TestClosestLeq) {
|
||||
this->test_closest_leq();
|
||||
}
|
||||
|
||||
TEST_VM_F(RBTreeTest, NodePrev) {
|
||||
this->test_node_prev();
|
||||
}
|
||||
|
||||
TEST_VM_F(RBTreeTest, NodeNext) {
|
||||
this->test_node_next();
|
||||
}
|
||||
|
||||
TEST_VM_F(RBTreeTest, NodeStableTest) {
|
||||
this->test_stable_nodes();
|
||||
}
|
||||
|
||||
TEST_VM_F(RBTreeTest, NodeStableAddressTest) {
|
||||
this->test_stable_nodes_addresses();
|
||||
}
|
||||
|
||||
#ifdef ASSERT
|
||||
TEST_VM_F(RBTreeTest, FillAndVerify) {
|
||||
this->test_fill_verify();
|
||||
}
|
||||
|
||||
TEST_VM_F(RBTreeTest, NodesVisitedOnce) {
|
||||
this->test_nodes_visited_once();
|
||||
}
|
||||
|
||||
TEST_VM_F(RBTreeTest, InsertRemoveVerify) {
|
||||
constexpr int num_nodes = 100;
|
||||
for (int n_t1 = 0; n_t1 < num_nodes; n_t1++) {
|
||||
for (int n_t2 = 0; n_t2 < n_t1; n_t2++) {
|
||||
RBTreeInt tree;
|
||||
for (int i = 0; i < n_t1; i++) {
|
||||
tree.upsert(i, i);
|
||||
}
|
||||
for (int i = 0; i < n_t2; i++) {
|
||||
tree.remove(i);
|
||||
}
|
||||
verify_it(tree);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_VM_F(RBTreeTest, VerifyItThroughStressTest) {
|
||||
{ // Repeatedly verify a tree of moderate size
|
||||
RBTreeInt rbtree;
|
||||
constexpr int ten_thousand = 10000;
|
||||
for (int i = 0; i < ten_thousand; i++) {
|
||||
int r = os::random();
|
||||
if (r % 2 == 0) {
|
||||
rbtree.upsert(i, i);
|
||||
} else {
|
||||
rbtree.remove(i);
|
||||
}
|
||||
if (i % 100 == 0) {
|
||||
verify_it(rbtree);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < ten_thousand; i++) {
|
||||
int r = os::random();
|
||||
if (r % 2 == 0) {
|
||||
rbtree.upsert(i, i);
|
||||
} else {
|
||||
rbtree.remove(i);
|
||||
}
|
||||
if (i % 100 == 0) {
|
||||
verify_it(rbtree);
|
||||
}
|
||||
}
|
||||
}
|
||||
{ // Make a very large tree and verify at the end
|
||||
struct Nothing {};
|
||||
RBTreeCHeap<int, Nothing, Cmp, mtOther> rbtree;
|
||||
constexpr int one_hundred_thousand = 100000;
|
||||
for (int i = 0; i < one_hundred_thousand; i++) {
|
||||
rbtree.upsert(i, Nothing());
|
||||
}
|
||||
verify_it(rbtree);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ASSERT
|
||||
Loading…
x
Reference in New Issue
Block a user