Skip to content

Commit

Permalink
allow duplicates with a macro
Browse files Browse the repository at this point in the history
  • Loading branch information
abxh committed Sep 5, 2024
1 parent 3adea8d commit 8958928
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 27 deletions.
63 changes: 46 additions & 17 deletions lib/rbtree.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,15 @@
* @file rbtree.h
* @brief Intrusive red-black tree
*
* Duplicates are not allowed. For supporting duplicate keys, you may store a counter for each node,
* and count up when a node key already exists.
* Keys are sorted in the following manner:
* @li left < key < right
*
* Duplicates are allowed with a macro. Then the keys are sorted in this manner:
* @li left <= key <= right
*
* Note allowing duplicates this way incurs performance penalties as the number of
* duplicates increase. For supporting duplicate keys efficiently, you can store a
* counter for each node, and count up when a node key already exists. Or use a list.
*
* The memory of the nodes are expected to managed seperately.
*
Expand Down Expand Up @@ -106,6 +113,20 @@
#define KEY_IS_STRICTLY_LESS(a, b) ((a) < (b))
#endif

/**
* @def ALLOW_DUPLICATES
* @brief Allow duplicates builtin.
*
* Then the keys are sorted in this manner:
* @li left <= key <= right
*
* @note Allowing duplicates this way incurs performance penalties as the number of
* duplicates increase. For supporting duplicate keys efficiently, you can store a
* counter for each node, and count up when a node key already exists. Or use a list.
*/
#ifdef ALLOW_DUPLICATES
#endif

/// @cond DO_NOT_DOCUMENT
#define RBTREE_TYPE JOIN(RBTREE_NAME, type)
#define RBTREE_NODE_TYPE JOIN(RBTREE_NAME, node_type)
Expand Down Expand Up @@ -156,7 +177,7 @@ static inline void JOIN(internal, JOIN(RBTREE_NAME, node_set_parent_ptr))(RBTREE

// rotate a subtree around a given subtree root node and direction (0: left or 1: right). returns the new subtree root
static inline RBTREE_NODE_TYPE* JOIN(internal, JOIN(RBTREE_NAME, rotate_dir))(RBTREE_NODE_TYPE** rootptr_ptr, RBTREE_NODE_TYPE* P,
const uint8_t dir);
const int dir);

// rebalance tree after insert. see explanation in the sources linked above.
static inline void JOIN(internal, JOIN(RBTREE_NAME, insert_fixup))(RBTREE_NODE_TYPE** rootptr_ptr, RBTREE_NODE_TYPE* N);
Expand Down Expand Up @@ -208,7 +229,7 @@ static inline RBTREE_NODE_TYPE* JOIN(RBTREE_NAME, node_get_parent_ptr)(RBTREE_NO
{
assert(node_ptr != NULL);

return (RBTREE_NODE_TYPE*)(node_ptr->__parent_ptr_with_color & ~1);
return (RBTREE_NODE_TYPE*)(node_ptr->__parent_ptr_with_color & ~(uintptr_t)1);
}

/**
Expand Down Expand Up @@ -264,8 +285,9 @@ static inline bool JOIN(RBTREE_NAME, contains_key)(RBTREE_NODE_TYPE** rootptr_pt
while (node_ptr != NULL) {
const bool is_strictly_less = KEY_IS_STRICTLY_LESS(key, node_ptr->key);
const bool is_strictly_greater = KEY_IS_STRICTLY_LESS(node_ptr->key, key);
const bool is_equal = !is_strictly_less && !is_strictly_greater;

if (!is_strictly_less && !is_strictly_greater) {
if (is_equal) {
return true;
}
else if (is_strictly_less) {
Expand Down Expand Up @@ -294,8 +316,9 @@ static inline RBTREE_NODE_TYPE* JOIN(RBTREE_NAME, search_node)(RBTREE_NODE_TYPE*
while (node_ptr != NULL) {
const bool is_strictly_less = KEY_IS_STRICTLY_LESS(key, node_ptr->key);
const bool is_strictly_greater = KEY_IS_STRICTLY_LESS(node_ptr->key, key);
const bool is_equal = !is_strictly_less && !is_strictly_greater;

if (!is_strictly_less && !is_strictly_greater) {
if (is_equal) {
return node_ptr;
}
else if (is_strictly_less) {
Expand All @@ -309,7 +332,7 @@ static inline RBTREE_NODE_TYPE* JOIN(RBTREE_NAME, search_node)(RBTREE_NODE_TYPE*
}

/**
* @brief Insert a given node with a non-duplicate key in the tree.
* @brief Insert a given node with a key in the tree.
*
* @param[in] rootptr_ptr A pointer to the pointer to the root node.
* @param[in] node_ptr The node pointer.
Expand All @@ -318,19 +341,23 @@ static inline void JOIN(RBTREE_NAME, insert_node)(RBTREE_NODE_TYPE** rootptr_ptr
{
assert(rootptr_ptr != NULL);
assert(node_ptr != NULL);
#ifndef ALLOW_DUPLICATES
assert(RBTREE_CONTAINS_KEY(rootptr_ptr, node_ptr->key) == false);
#endif

RBTREE_NODE_TYPE* parent_ptr = NULL;
RBTREE_NODE_TYPE* current_ptr = *rootptr_ptr;

while (current_ptr != NULL) {
const bool is_strictly_greater = KEY_IS_STRICTLY_LESS(current_ptr->key, node_ptr->key);

parent_ptr = current_ptr;

if (KEY_IS_STRICTLY_LESS(node_ptr->key, current_ptr->key)) {
current_ptr = current_ptr->left_ptr;
if (is_strictly_greater) {
current_ptr = current_ptr->right_ptr;
}
else {
current_ptr = current_ptr->right_ptr;
current_ptr = current_ptr->left_ptr;
}
}

Expand All @@ -342,7 +369,8 @@ static inline void JOIN(RBTREE_NAME, insert_node)(RBTREE_NODE_TYPE** rootptr_ptr
*rootptr_ptr = node_ptr;
}
else {
const uint8_t dir = KEY_IS_STRICTLY_LESS(parent_ptr->key, node_ptr->key);
const int dir = KEY_IS_STRICTLY_LESS(parent_ptr->key, node_ptr->key) ? 1 : 0;

parent_ptr->child_ptrs[dir] = node_ptr;

JOIN(internal, JOIN(RBTREE_NAME, insert_fixup))(rootptr_ptr, node_ptr);
Expand All @@ -367,15 +395,15 @@ static inline RBTREE_NODE_TYPE* JOIN(RBTREE_NAME, delete_node)(RBTREE_NODE_TYPE*
}
else {
RBTREE_NODE_TYPE* const parent_ptr = RBTREE_NODE_GET_PARENT_PTR(node_ptr);
const int dir = RBTREE_CHILD_DIR(node_ptr);
const int dir = RBTREE_CHILD_DIR(node_ptr) ? 1 : 0;

RBTREE_NODE_TRANSPLANT(rootptr_ptr, node_ptr, NULL);

JOIN(internal, JOIN(RBTREE_NAME, delete_fixup))(rootptr_ptr, parent_ptr, dir);
}
}
else if (node_ptr->left_ptr == NULL || node_ptr->right_ptr == NULL) {
const uint8_t dir = node_ptr->left_ptr == NULL;
const int dir = node_ptr->left_ptr == NULL;

assert(RBTREE_NODE_IS_BLACK(node_ptr));
assert(RBTREE_NODE_IS_RED(node_ptr->child_ptrs[dir]));
Expand Down Expand Up @@ -441,7 +469,7 @@ static inline void JOIN(internal, JOIN(RBTREE_NAME, node_set_color_to_red))(RBTR
{
assert(node_ptr != NULL);

node_ptr->__parent_ptr_with_color &= ~1;
node_ptr->__parent_ptr_with_color &= ~(uintptr_t)1;
}
static inline void JOIN(internal, JOIN(RBTREE_NAME, node_set_color_to_black))(RBTREE_NODE_TYPE* node_ptr)
{
Expand All @@ -457,7 +485,7 @@ static inline void JOIN(internal, JOIN(RBTREE_NAME, node_set_color_to_color_of_o

const bool is_black = other_ptr->__parent_ptr_with_color & 1;

node_ptr->__parent_ptr_with_color &= ~1;
node_ptr->__parent_ptr_with_color &= ~(uintptr_t)1;
node_ptr->__parent_ptr_with_color += is_black;
}
static inline void JOIN(internal, JOIN(RBTREE_NAME, node_set_parent_ptr))(RBTREE_NODE_TYPE* node_ptr, RBTREE_NODE_TYPE* parent_ptr)
Expand All @@ -472,7 +500,7 @@ static inline void JOIN(internal, JOIN(RBTREE_NAME, node_set_parent_ptr))(RBTREE

// rotate a subtree around a given subtree root node and direction (0: left or 1: right). returns the new subtree root
static inline RBTREE_NODE_TYPE* JOIN(internal, JOIN(RBTREE_NAME, rotate_dir))(RBTREE_NODE_TYPE** rootptr_ptr, RBTREE_NODE_TYPE* P,
const uint8_t dir)
const int dir)
{
assert(rootptr_ptr != NULL);
assert(P != NULL);
Expand Down Expand Up @@ -540,7 +568,7 @@ static inline void JOIN(internal, JOIN(RBTREE_NAME, insert_fixup))(RBTREE_NODE_T
RBTREE_NODE_SET_COLOR_TO_BLACK(P);
return;
}
const uint8_t dir = RBTREE_CHILD_DIR(P);
const int dir = RBTREE_CHILD_DIR(P);
U = G->child_ptrs[1 - dir];

if (U == NULL || RBTREE_NODE_IS_BLACK(U)) {
Expand Down Expand Up @@ -662,6 +690,7 @@ static inline void JOIN(internal, JOIN(RBTREE_NAME, delete_fixup))(RBTREE_NODE_T
#undef KEY_TYPE
#undef VALUE_TYPE
#undef KEY_IS_STRICTLY_LESS
#undef ALLOW_DUPLICATES

#undef RBTREE_TYPE
#undef RBTREE_NAME
Expand Down
118 changes: 108 additions & 10 deletions tests/rbtree/rbtree.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@
#define KEY_IS_STRICTLY_LESS(a, b) ((a) < (b))
#include "rbtree.h"

#define NAME bstd
#define KEY_TYPE int
#define KEY_IS_STRICTLY_LESS(a, b) ((a) < (b))
#define ALLOW_DUPLICATES
#include "rbtree.h"

typedef struct {
unsigned int value;
bst_node_type node;
} extended_bst_node;

static int max_int(const int a, const int b)
{
return (a >= b) ? a : b;
Expand All @@ -60,13 +71,25 @@ static bool is_valid_binary_search_tree(const float left_key, const bst_node_typ
if (root_ptr == NULL) {
return true;
}
if (!(left_key <= root_ptr->key && root_ptr->key <= right_key)) {
if (!(left_key < root_ptr->key && root_ptr->key < right_key)) { // note this depends on particulars
return false;
}
return is_valid_binary_search_tree(left_key, root_ptr->left_ptr, (float)root_ptr->key) &&
is_valid_binary_search_tree((float)root_ptr->key, root_ptr->right_ptr, right_key);
}

static bool is_valid_binary_search_tree_w_duplicates(const float left_key, const bst_node_type* root_ptr, const float right_key)
{
if (root_ptr == NULL) {
return true;
}
if (!(left_key <= root_ptr->key && root_ptr->key <= right_key)) { // note this depends on particulars
return false;
}
return is_valid_binary_search_tree_w_duplicates(left_key, root_ptr->left_ptr, (float)root_ptr->key) &&
is_valid_binary_search_tree_w_duplicates((float)root_ptr->key, root_ptr->right_ptr, right_key);
}

static bool rbtree_check_for_no_consecutive_reds(const bst_node_type* parent_ptr, const bst_node_type* root_ptr)
{
if (root_ptr == NULL) {
Expand Down Expand Up @@ -107,6 +130,15 @@ static bool is_valid_red_black_tree(const bst_node_type* root_ptr)
return is_valid_bst && is_valid_234 && balanced_black_height;
}

static bool is_valid_red_black_tree_w_duplicates(const bst_node_type* root_ptr)
{
const bool is_valid_bst_w_duplicates = is_valid_binary_search_tree_w_duplicates(-INFINITY, root_ptr, INFINITY);
const bool is_valid_234 = rbtree_check_for_no_consecutive_reds(NULL, root_ptr);
const bool balanced_black_height = rbtree_check_equal_black_height(root_ptr, rbtree_get_black_height_of_some_path(root_ptr));

return is_valid_bst_w_duplicates && is_valid_234 && balanced_black_height;
}

static void preorder_traverse_and_print(const bst_node_type* root_ptr, bool print_children)
{
if (root_ptr == NULL) {
Expand Down Expand Up @@ -201,23 +233,32 @@ int main(void)
assert(count_height(bst) == 4);
}

// N = 8192, 8 * (insert_node * 1024, delete_node * 1024, insert_node * 1024)
const int lim = 1024;

// N = 8192, 8 * (insert_node * 1024, delete_node * 1024, insert_node * 1024)
for (int seed = 0; seed < 8; seed++) {
bst_node_type* bst;
bst_init(&bst);

srand((unsigned int)seed);

bst_node_type* node_buf = calloc((size_t)(lim * 2), sizeof(bst_node_type));
extended_bst_node* node_buf = calloc((size_t)(lim * 2), sizeof(extended_bst_node));
size_t node_count = 0;

for (int i = 0; i < lim; i++) {
const int val = (int)((unsigned int)rand()) % lim;

bst_node_init(&node_buf[node_count], val);
bst_insert_node(&bst, &node_buf[node_count]);
node_count++;
bst_node_type* node_ptr = bst_search_node(&bst, val);

if (!node_ptr) {
node_buf[node_count].value = 0;
bst_node_init(&node_buf[node_count].node, val);
bst_insert_node(&bst, &node_buf[node_count].node);
node_count++;
}
else {
rbtree_node_entry(node_ptr, extended_bst_node, node)->value++;
}

assert(is_valid_red_black_tree(bst));
}
Expand All @@ -226,23 +267,80 @@ int main(void)

for (int i = 0; i < lim; i++) {
const int val = (int)((unsigned int)rand()) % lim;

bst_node_type* node_ptr = bst_search_node(&bst, val);
if (node_ptr) {
bst_delete_node(&bst, node_ptr);
unsigned int* value_ptr = &rbtree_node_entry(node_ptr, extended_bst_node, node)->value;
if (*value_ptr > 1) {
(*value_ptr)--;
}
else {
bst_delete_node(&bst, node_ptr);
}
}
assert(is_valid_red_black_tree(bst));
}

for (int i = 0; i < lim; i++) {
const int val = (int)((unsigned int)rand()) % lim;

bst_node_init(&node_buf[node_count], val);
bst_insert_node(&bst, &node_buf[node_count]);
node_count++;
bst_node_type* node_ptr = bst_search_node(&bst, val);

if (!node_ptr) {
node_buf[node_count].value = 0;
bst_node_init(&node_buf[node_count].node, val);
bst_insert_node(&bst, &node_buf[node_count].node);
node_count++;
}
else {
rbtree_node_entry(node_ptr, extended_bst_node, node)->value++;
}
assert(is_valid_red_black_tree(bst));
}

free(node_buf);
}

for (int seed = 0; seed < 8; seed++) {
bstd_node_type* bstd;
bstd_init(&bstd);

srand((unsigned int)seed);

bstd_node_type* node_buf = calloc((size_t)(lim * 2), sizeof(bstd_node_type));
size_t node_count = 0;

for (int i = 0; i < lim; i++) {
const int val = (int)((unsigned int)rand()) % lim;

bstd_node_init(&node_buf[node_count], val);
bstd_insert_node(&bstd, &node_buf[node_count]);
node_count++;

assert(is_valid_red_black_tree_w_duplicates((bst_node_type*)bstd));
}

assert(count_height((bst_node_type*)bstd) <= 2 * log2(lim));

for (int i = 0; i < lim; i++) {
const int val = (int)((unsigned int)rand()) % lim;
bstd_node_type* node_ptr = bstd_search_node(&bstd, val);
if (node_ptr) {
bstd_delete_node(&bstd, node_ptr);
}
assert(is_valid_red_black_tree_w_duplicates((bst_node_type*)bstd));
}

for (int i = 0; i < lim; i++) {
const int val = (int)((unsigned int)rand()) % lim;

bstd_node_init(&node_buf[node_count], val);
bstd_insert_node(&bstd, &node_buf[node_count]);
node_count++;

assert(is_valid_red_black_tree_w_duplicates((bst_node_type*)bstd));
}

free(node_buf);
}
}

0 comments on commit 8958928

Please sign in to comment.