From 414100d8773cabb4fc185e74377e069916fc88c5 Mon Sep 17 00:00:00 2001 From: MLopez-Ibanez <2620021+MLopez-Ibanez@users.noreply.github.com> Date: Wed, 26 Aug 2026 22:44:35 +0100 Subject: [PATCH 1/2] c/ Replace AVL-tree with Treap in most places. * treap.h: New. * pareto.c (pareto_rank_3d): Replace AVL-tree with Treap. * nondominated.h (find_nondominated_3d_impl_sorted): Likewise. * nondominated_kung.h (kung_merge_dim3): Likewise. * hv3d_priv.h (hv3d_preprocessing): Likewise. --- c/NEWS.md | 6 +- c/hv3d_priv.h | 88 ++++++ c/nondominated.h | 65 ++++- c/nondominated_kung.h | 92 +++++- c/pareto.c | 26 +- c/treap.h | 656 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 926 insertions(+), 7 deletions(-) create mode 100644 c/treap.h diff --git a/c/NEWS.md b/c/NEWS.md index 389c63de6..379cc604b 100644 --- a/c/NEWS.md +++ b/c/NEWS.md @@ -3,7 +3,11 @@ ## 0.20 * radixsort.h, insort.h: New. - + * treap.h: New. + * pareto.c (pareto_rank_3d): Replace AVL-tree with Treap. + * nondominated.h (find_nondominated_3d_impl_sorted): Likewise. + * nondominated_kung.h (kung_merge_dim3): Likewise. + * hv3d_priv.h (hv3d_preprocessing): Likewise. ## 0.19.2 diff --git a/c/hv3d_priv.h b/c/hv3d_priv.h index 4168a0779..fa7d3861a 100644 --- a/c/hv3d_priv.h +++ b/c/hv3d_priv.h @@ -1,6 +1,9 @@ #ifndef _HV3D_PRIV_H #define _HV3D_PRIV_H +#define USE_AVL 0 + +#if USE_AVL == 1 typedef const double avl_item_t; typedef struct avl_node_t { struct avl_node_t *next; @@ -29,6 +32,29 @@ new_avl_node(dlnode_t * restrict p, avl_node_t * restrict node) return node; } +#else +typedef struct TreapItem { + const double * z; + dlnode_t * dlnode; +} TreapItem; + +static inline const double * +treap_item_get_z(TreapItem item) +{ + return item.z; +} + +#include "treap.h" + +static inline void +hv3d_init_treap_node(TreapNode * restrict node, dlnode_t * restrict p) +{ + // Shift by -1 so that the split dimension is [0]. + treap_node_init(node, p->x[1], (TreapItem) { .z = p->x - 1, .dlnode = p }); +} + +#endif + /* Used by hvc3d.c and hv3dplus.c. This implements a variant of the 3D dimension-sweep algorithm by H. T. Kung, @@ -61,6 +87,7 @@ hv3d_preprocessing(dlnode_t * restrict list, size_t n) assert(list+1 == list->next[0]); assert(list+2 == list->prev[0]); +#if USE_AVL == 1 avl_tree_t tree; avl_init_tree(&tree, qsort_cmp_pdouble_asc_y_des_x_nonzero); avl_node_t * tnodes = malloc((n+2) * sizeof(*tnodes)); @@ -131,6 +158,67 @@ hv3d_preprocessing(dlnode_t * restrict list, size_t n) } p = p->next[0]; } +#else + TreapNode *tnodes = malloc((n+2) * sizeof(*tnodes)); + assert(tnodes != NULL); + // At the top we insert the first point, which is never dominated. + dlnode_t * p = (list+1)->next[0]; + hv3d_init_treap_node(tnodes, p); + Treap tree; + treap_init_with_single_node(&tree, tnodes); + set_delimiters(p, list+1, list); + + // After the top node, we insert sentinel 1 (-INF, ref[1]) + hv3d_init_treap_node(tnodes + 1, list); + tnodes->right = tnodes + 1; + tnodes->right->priority = 0; // Push to the bottom + // Before the top node, we insert sentinel 2 (ref[0], -INF) + hv3d_init_treap_node(tnodes + 2, list + 1); + tnodes->left = tnodes + 2; + tnodes->left->priority = 0; // Push to the bottom + set_delimiters(p, tnodes->left->item.dlnode, tnodes->right->item.dlnode); + + TreapNode * node = tnodes + 3; + const dlnode_t * stop = list+2; + _attr_maybe_unused dlnode_t * prev_p = p; + double pk0 = p->x[0], pk1 = p->x[1], _attr_maybe_unused pk2 = p->x[2]; + for (p = p->next[0]; p != stop; p = p->next[0]) { + const double pj0 = p->x[0], pj1 = p->x[1], pj2 = p->x[2]; + if ((pk0 > pj0) | (pk1 > pj1)) { + TreapNode *pred = treap_find_le(&tree, pj1); + assert(pred != NULL); + const double * prev_x = pred->item.dlnode->x; + if (prev_x[0] <= pj0) { + // pj is dominated by a point in the tree. +#ifdef HVC_ONLY + if (all_equal_double(prev_x, p->x, 3)) + pred->item.dlnode->ignore = true; // It will have zero hvc. +#endif + remove_from_z(p); + continue; + } + // pj is NOT dominated + hv3d_init_treap_node(node, p); + TreapNode * prev, *next; + (void) treap_insert_and_displace_get_bounds(&tree, node, &prev, &next); + node++; + // Check if the data structure is properly setup + assert(prev->item.dlnode->x[0] > pj0 && prev->item.dlnode->x[1] < pj1); + assert(next->item.dlnode->x[0] < pj0 && next->item.dlnode->x[1] > pj1); + set_delimiters(p, prev->item.dlnode, next->item.dlnode); + + pk0 = pj0; pk1 = pj1; pk2 = pj2; + prev_p = p; + } else { + // pj is dominated by a previous point. +#ifdef HVC_ONLY + if (pk0 == pj0 && pk1 == pj1 && pk2 == pj2) + prev_p->ignore = true; // It will have zero hvc. +#endif + remove_from_z(p); + } + } +#endif free(tnodes); #undef set_delimiters } diff --git a/c/nondominated.h b/c/nondominated.h index e7098b4f8..2256cd7ce 100644 --- a/c/nondominated.h +++ b/c/nondominated.h @@ -1,5 +1,19 @@ #ifndef NONDOMINATED_H #define NONDOMINATED_H +/***************************************************************************** + + Various algorithm for filtering dominated solutions + + --------------------------------------------------------------------- + + Copyright (C) 2026 + Manuel Lopez-Ibanez + + 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 https://mozilla.org/MPL/2.0/. + +*****************************************************************************/ #include "config.h" #include // memcpy @@ -7,6 +21,9 @@ #include "sort.h" #include "radixsort.h" +#define USE_AVL 0 + +#if USE_AVL == 1 typedef const double avl_item_t; typedef struct avl_node_t { struct avl_node_t *next; @@ -19,6 +36,16 @@ typedef struct avl_node_t { } avl_node_t; #include "avl_tiny.h" +#else +typedef const double * TreapItem; +static inline const double * +treap_item_get_z(TreapItem item) +{ + return item; +} + +#include "treap.h" +#endif enum objs_agree_t { AGREE_MINIMISE = -1, AGREE_NONE = 0, AGREE_MAXIMISE = 1 }; @@ -335,6 +362,7 @@ find_nondominated_3d_impl_sorted(const double ** restrict rows, size_t size, const bool find_dominated) { ASSUME(size > 1); +#if USE_AVL == 1 /* FIXME: The AVL-tree is the bottleneck of this algorithm. A Treap [R. Seidel and C. R. Aragon. Randomized search trees. Algorithmica, 16:464–497, 1996] may be far more efficient by allowing to remove a @@ -402,6 +430,42 @@ find_nondominated_3d_impl_sorted(const double ** restrict rows, size_t size, : printf_point("insert before point: ", point, 3, "\n")); (++node)->item = pj; avl_insert_before(&tree, nodeaux, node); +#else + TreapNode *tnodes = malloc(size * sizeof(*tnodes)); + assert(tnodes != NULL); + TreapNode * node = tnodes; + double pk0 = rows[0][0], pk1 = rows[0][1], pk2 = rows[0][2]; + treap_node_init(node, pk0, rows[0]); + Treap tree; + treap_init_with_single_node(&tree, node); + node++; + + // In this context, size means "no dominated solution found". + size_t new_size = size; + bool prev_dominated = false; + for (size_t j = 1; j < size; ++j) { + const double * restrict pj = rows[j]; + DEBUG2(printf_point("pj = [ ", pj, 3, " ], ")); + const double pj0 = pj[0], pj1 = pj[1], pj2 = pj[2]; + if ((pk0 > pj0) | (pk1 > pj1)) { + // Check if pj is dominated by a point in the tree. + /* In a valid 2-D frontier, x increases and y decreases. Therefore + the only existing point that can dominate pj is the frontier + predecessor with the largest key <= pj0. */ + TreapNode *pred = treap_find_le(&tree, pj0); + if (pred != NULL && pred->item[1] <= pj1) + goto j_is_dominated; + /* pj is not dominated by an existing frontier point. + + Insert it and detach every existing point dominated by it. + + The returned treap contains exactly those displaced nodes, but + we do not need to traverse it here because those nodes will + never again participate in dominance queries. */ + treap_node_init(node, pj0, pj); + (void) treap_insert_and_displace(&tree, node); + node++; +#endif // Fall-through to j_is_NOT_dominated. } // Handle duplicates and points that are dominated by the immediate previous one. else if (!keep_weakly // Don't keep duplicates. @@ -430,7 +494,6 @@ find_nondominated_3d_impl_sorted(const double ** restrict rows, size_t size, rows[j] = NULL; new_size--; } - early_end: free(tnodes); return new_size; diff --git a/c/nondominated_kung.h b/c/nondominated_kung.h index c0f8f3220..534d45d3d 100644 --- a/c/nondominated_kung.h +++ b/c/nondominated_kung.h @@ -134,7 +134,24 @@ maxima_partition(const double * restrict * restrict u, size_t size, double v) DEBUG1(for (size_t j = low; j < size; j++) assert(v < u[j][0])); return low; } +#undef USE_AVL +#define USE_AVL 0 +#if USE_AVL == 1 +typedef const double avl_item_t; +typedef struct avl_node_t { + struct avl_node_t *next; + struct avl_node_t *prev; + struct avl_node_t *parent; + struct avl_node_t *left; + struct avl_node_t *right; + avl_item_t *item; + unsigned char depth; +} avl_node_t; + +#include "avl_tiny.h" +#endif +#if USE_AVL == 1 /** Returns NULL if point is dominated by a different point in the tree. */ @@ -165,6 +182,51 @@ dominated_by_tree_3d(const avl_tree_t * restrict tree, return nodeaux; // point is not dominated } +#else +static inline bool +dominated_by_tree_3d(Treap *tree, const double *point) +{ + DEBUG1(treap_validate_tree(tree->root)); + // Find the rightmost point with prev_node->key <= point[1]. + TreapNode *prev_node = treap_find_le(tree, point[1]); + if (prev_node != NULL) { + const double * prev = prev_node->item - 1; + if (prev[2] <= point[2]) { + assert(prev[0] <= point[0]); + assert(prev[1] <= point[1]); + return true; // p is dominated + } + } + return false; // p is not dominated +} + +// FIXME: How to merge this function and the one above? +static inline bool +insert_if_not_dominated_by_tree_3d(Treap *tree, TreapNode *node, const double *point) +{ + DEBUG1(treap_validate_tree(tree->root)); + // Find the rightmost point with prev->key <= point[1]. + TreapNode **prev_link = treap_find_le_link(tree, point[1]); + if (prev_link != NULL) { + const double * prev = (*prev_link)->item - 1; + if (prev[2] <= point[2]) { + assert(prev[0] <= point[0]); + assert(prev[1] <= point[1]); + return false; // p is dominated + } else if (prev[1] == point[1]) { + // p dominates prev. Remove the already found node. + treap_erase_at(prev_link); + DEBUG1(treap_validate_tree(tree->root)); + } + } + treap_node_init(node, point[1], point + 1); + // FIXME: This will call split_lt, but we just called find_le above, so can + // we avoid one of them? + (void)treap_insert_and_displace(tree, node); + return true; // p was inserted. +} +#endif + /** ALGORITHM 5.1. This algorithm accepts two sets R and S of 3-dimensional vectors with r_size and s_size elements, respectively, and finds all the @@ -187,6 +249,7 @@ kung_merge_dim3(const double ** restrict r, size_t r_size, DEBUG1(for (size_t j = 0; j < k; j++) assert(s[j][0] < r0)); DEBUG1(for (size_t j = k; j < s_size; j++) assert(r0 <= s[j][0])); +#if USE_AVL == 1 avl_tree_t tree; avl_init_tree(&tree, qsort_cmp_pdouble_asc_y_asc_z); // FIXME: Use a workspace to allocate this once and re-alloc only if a larger number is needed. @@ -198,20 +261,31 @@ kung_merge_dim3(const double ** restrict r, size_t r_size, const double sentinel[] = { INFINITY, INFINITY, -INFINITY}; (++node)->item = sentinel; avl_insert_after(&tree, node - 1, node); - +#else + TreapNode *tnodes = malloc(r_size * sizeof(*tnodes)); + assert(tnodes != NULL); + Treap tree; + TreapNode * node = tnodes; + treap_node_init(node, r[0][1], r[0] + 1); + treap_init_with_single_node(&tree, node); + node++; +#endif + DEBUG2(printf_point("insert in tree: r=[ ", r[0], 3, " ]\n")); size_t i = 1, new_size = s_size; do { const double * restrict v = s[k]; - DEBUG2_PRINT("i = %zu, j = %zu, s_size = %zu", i, k, s_size); + DEBUG2_PRINT("i = %zu, j = %zu, s_size = %zu, ", i, k, s_size); DEBUG2(printf_point("v = [ ", v, 3, " ]\n")); while (i < r_size) { // Add to the tree all points in R that could dominate v. const double * restrict u = r[i]; - if (u[0] > v[0]) { + if (u[0] > v[0]) break; - } + +#if USE_AVL == 1 avl_node_t * nodeaux = dominated_by_tree_3d(&tree, u); if (nodeaux != NULL) { // u is NOT dominated by a point in the tree. + DEBUG2(printf_point("!dominated_by_tree_3d: u=[ ", u, 3, " ]\n")); const double * restrict point = nodeaux->item; assert(u[1] <= point[1]); // Delete everything in the tree that is dominated by u. @@ -233,6 +307,16 @@ kung_merge_dim3(const double ** restrict r, size_t r_size, i++; } if (dominated_by_tree_3d(&tree, v) == NULL) { +#else + if (insert_if_not_dominated_by_tree_3d(&tree, node, u)) { + // u is NOT dominated by a point in the tree. + DEBUG2(printf_point("!dominated_by_tree_3d: u=[ ", u, 3, " ]\n")); + node++; + } + i++; + } + if (dominated_by_tree_3d(&tree, v)) { +#endif DEBUG2(printf_point("dominated_by_tree_3d: v=[ ", v, 3, " ]\n")); s[k] = NULL; // dominated new_size--; diff --git a/c/pareto.c b/c/pareto.c index 139382b22..c6342e73c 100644 --- a/c/pareto.c +++ b/c/pareto.c @@ -20,22 +20,34 @@ pareto_rank_3d(int * restrict rank, const double * restrict points, size_t size) const bool keep_weakly = true; const double ** p = generate_row_pointers_asc_rev_3d(points, size); +#if USE_AVL == 1 avl_tree_t tree; avl_init_tree(&tree, qsort_cmp_pdouble_asc_x_nonzero); avl_node_t * tnodes = malloc((size+1) * sizeof(*tnodes)); const double sentinel[] = { INFINITY, -INFINITY }; tnodes->item = sentinel; +#else + TreapNode *tnodes = malloc(size * sizeof(*tnodes)); + assert(tnodes != NULL); + Treap tree; +#endif int front = 0; while (true) { ASSUME(size >= 2); const double * restrict pk = p[0]; +#if USE_AVL == 1 avl_node_t * node = tnodes + 1; node->item = pk; avl_insert_top(&tree, node); // Insert sentinel. avl_insert_after(&tree, node, tnodes); - +#else + TreapNode * node = tnodes; + treap_node_init(node, pk[0], pk); + treap_init_with_single_node(&tree, node); + node++; +#endif // In this context, size means "no dominated solution found". size_t n_nondom = size, j = 1; const double * last_dom = NULL; @@ -43,6 +55,7 @@ pareto_rank_3d(int * restrict rank, const double * restrict points, size_t size) const double * restrict pj = p[j]; bool dominated; if (pk[0] > pj[0] || pk[1] > pj[1]) { +#if USE_AVL == 1 avl_node_t * nodeaux; int res = avl_search_closest(&tree, pj, &nodeaux); assert(res != 0); @@ -78,6 +91,15 @@ pareto_rank_3d(int * restrict rank, const double * restrict points, size_t size) (++node)->item = pj; avl_insert_before(&tree, nodeaux, node); } +#else + TreapNode *pred = treap_find_le(&tree, pj[0]); + dominated = (pred != NULL && pred->item[1] <= pj[1]); + if (!dominated) { + treap_node_init(node, pj[0], pj); + (void) treap_insert_and_displace(&tree, node); + node++; + } +#endif } else { // Handle duplicates and points that are dominated by the immediate // previous one. @@ -134,7 +156,9 @@ pareto_rank_3d(int * restrict rank, const double * restrict points, size_t size) k, p[k][0], p[k][1], p[k][2]); front++; +#if USE_AVL == 1 avl_clear_tree(&tree); +#endif } } diff --git a/c/treap.h b/c/treap.h new file mode 100644 index 000000000..1375a9793 --- /dev/null +++ b/c/treap.h @@ -0,0 +1,656 @@ +/***************************************************************************** + + treap.h: Randomised binary search tree (Treap) for a 2D mutually nondominated + frontier. + + --------------------------------------------------------------------- + Copyright (C) 2026 + Manuel Lopez-Ibanez + + 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 https://mozilla.org/MPL/2.0/. + + --------------------------------------------------------------------- + + [1] R. Seidel and C. R. Aragon. Randomized search trees. Algorithmica, + 16:464-497, 1996 + +******************************************************************************/ +#ifndef TREAP_H +#define TREAP_H + +/* + + Randomized binary search tree for a 2-D mutually non-dominated + minimization frontier. + + key is ordered by increasing item[0] + + Frontier invariant: + + x (item[0]) increases strictly from left to right + y (item[1]) decreases strictly from left to right + + Consequently, for a new point (x,y), the old frontier points + dominated by the new point form a contiguous range in key order. + This allows the whole dominated range to be detached with + split/merge operations instead of deleting its nodes individually. + + Nodes are externally owned. The treap does not allocate or free them. + + Expected complexity: + + search O(log n) + split O(log n) + merge O(log n) + insert O(log n) + insert+displace O(log n) + + Worst-case complexity of a treap operation is O(n), as for any + randomized BST. +*/ +#include +#include +#include +#include + +typedef struct TreapNode TreapNode; +struct TreapNode { + double key; + uint64_t priority; // Parents should have a higher priority than children. + TreapNode *left; + TreapNode *right; + TreapItem item; +}; + +typedef struct { + TreapNode *root; + uint64_t rng_state; +} Treap; + + +/** + PRNG is xorshift64*, which is not cryptographically safe but it is fast. +*/ +static inline uint64_t +treap_random(Treap *tree) +{ + uint64_t x = tree->rng_state; + assert(x != 0); // State must never be zero. + + x ^= x >> 12; + x ^= x << 25; + x ^= x >> 27; + + tree->rng_state = x; + return x * UINT64_C(0x2545F4914F6CDD1D); +} + + +/** + Treap Seed generation + + Use the address of the Treap object as process-dependent entropy, then + thoroughly mix it with SplitMix64's mixing function. + + This is not intended as cryptographic randomness. Its purpose is simply + to give separately allocated treaps normally different PRNG streams, + without requiring global mutable state or an OS RNG call. +*/ +static inline uint64_t +treap_seed(const Treap *tree) +{ + // Cast to uintptr_t to use the pointer value as an integer. + uint64_t x = (uint64_t)(uintptr_t)tree; + /* Mix the address. The addition is useful even though a fixed constant + would ultimately be mixed anyway. */ + x += UINT64_C(0x9e3779b97f4a7c15); + + x = (x ^ (x >> 30)) * UINT64_C(0xbf58476d1ce4e5b9); + x = (x ^ (x >> 27)) * UINT64_C(0x94d049bb133111eb); + x ^= x >> 31; + // xorshift64* cannot leave the zero state. + return x ? x : UINT64_C(0x9e3779b97f4a7c15); +} + + +/** + (Re)-Initializes the tree structure. Nothing is free()d. + + Complexity: O(1) +*/ +static inline void +treap_init(Treap *tree) +{ + tree->root = NULL; + tree->rng_state = treap_seed(tree); +} + +/* + Initialize a node before inserting it. + + The node does not receive a priority until insertion because the + priority should come from the destination treap's RNG. +*/ +static inline void +treap_node_init(TreapNode *node, double key, TreapItem item) +{ + assert(node != NULL); + node->key = key; + node->left = NULL; + node->right = NULL; + node->item = item; +} + +static inline void +treap_init_with_single_node(Treap *tree, TreapNode * node) +{ + assert(node->left == NULL); + assert(node->right == NULL); + treap_init(tree); + tree->root = node; + node->priority = treap_random(tree); +} + +static inline void +treap_split_lt_with_max(TreapNode *root, double value, + TreapNode **left, TreapNode **right, + TreapNode **left_max) +{ + if (root == NULL) { + *left = NULL; + *right = NULL; + if (left_max) + *left_max = NULL; + return; + } + + if (root->key < value) { + *left = root; + if (left_max) { + TreapNode *sub_left_max; + treap_split_lt_with_max(root->right, value, &root->right, right, + &sub_left_max); + /* If anything remained in root->right, its maximum is the maximum of + the whole left result. Otherwise root is the max. */ + *left_max = sub_left_max != NULL ? sub_left_max : root; + } else { + treap_split_lt_with_max(root->right, value, &root->right, right, NULL); + } + } else { + *right = root; + treap_split_lt_with_max(root->left, value, left, &root->left, left_max); + } +} + +/** + Split by key. + + On return: + + *left contains every node with key < value + *right contains every node with key >= value + + The frontier and treap invariants are preserved. + + Expected complexity: O(log n). +*/ +static inline void +treap_split_lt(TreapNode *root, double value, + TreapNode **left, TreapNode **right) +{ + treap_split_lt_with_max(root, value, left, right, NULL); +} + + +/** + Split by value. + + On return: + + *left contains every node with key <= value + *right contains every node with key > value + + The frontier and treap invariants are preserved. + + Expected complexity: O(log n). +*/ +static inline void +treap_split_le(TreapNode *root, double value, + TreapNode **left, TreapNode **right) +{ + if (root == NULL) { + *left = NULL; + *right = NULL; + return; + } + + if (root->key <= value) { + // root belongs to the left result. + *left = root; + treap_split_le(root->right, value, &root->right, right); + } else { + // root belongs to the right result. + *right = root; + treap_split_le(root->left, value, left, &root->left); + } +} + +static inline void +treap_split_z1_ge_with_min(TreapNode *root, double y, + TreapNode **high_y, TreapNode **low_y, + TreapNode **low_y_min) +{ + if (root == NULL) { + *high_y = NULL; + *low_y = NULL; + if (low_y_min) + *low_y_min = NULL; + return; + } + + if (treap_item_get_z(root->item)[1] >= y) { + *high_y = root; + treap_split_z1_ge_with_min(root->right, y, &root->right, low_y, low_y_min); + } else { + *low_y = root; + if (low_y_min) { + TreapNode *sub_low_min; + treap_split_z1_ge_with_min(root->left, y, high_y, &root->left, &sub_low_min); + /* If root has a left part remaining, its minimum is the minimum of + that part. Otherwise root is the minimum. */ + if (low_y_min) + *low_y_min = sub_low_min != NULL ? sub_low_min : root; + } else { + treap_split_z1_ge_with_min(root->left, y, high_y, &root->left, NULL); + } + } +} + +/** + Split a frontier by y. + + PRECONDITION: + + root is a valid mutually non-dominated frontier. + + Since y strictly decreases as x increases: + + *high_y contains every node with item[1] >= y + *low_y contains every node with item[1] < y + + Therefore *high_y is a prefix in key order. + + This is the operation used to detach all nodes dominated by a new + frontier point. + + Expected complexity: O(log n). +*/ +static inline void +treap_split_z1_ge(TreapNode *root, double y, + TreapNode **high_y, TreapNode **low_y) +{ + treap_split_z1_ge_with_min(root, y, high_y, low_y, NULL); +} + +static TreapNode * +treap_rotate_left(TreapNode *node) +{ + TreapNode *right = node->right; + node->right = right->left; + right->left = node; + return right; +} + +static TreapNode * +treap_rotate_right(TreapNode *node) +{ + TreapNode *left = node->left; + node->left = left->right; + left->right = node; + return left; +} + +static TreapNode * +treap_insert_node_and_rotate(TreapNode * root, TreapNode *node) +{ + if (!root) + return node; + + if (node->key < root->key) { + root->left = treap_insert_node_and_rotate(root->left, node); + assert(root->left != NULL); + if (root->left->priority > root->priority) + root = treap_rotate_right(root); + } else { + root->right = treap_insert_node_and_rotate(root->right, node); + assert(root->right != NULL); + if (root->right->priority > root->priority) + root = treap_rotate_left(root); + } + return root; +} + +/** + Merge two treaps. + + PRECONDITION: + + Every key in left is less than every key in right. + + Returns the merged treap. + + Expected complexity: O(log n). +*/ +static inline TreapNode * +treap_merge(TreapNode *left, TreapNode *right) +{ + if (!left) + return right; + + if (!right) + return left; + + if (left->priority > right->priority) { + left->right = treap_merge(left->right, right); + return left; + } + + right->left = treap_merge(left, right->left); + return right; +} + +static inline void +treap_validate_tree(TreapNode * node) +{ + if (!node) + return; + + if (node->left) { + assert(node->left->key < node->key); + assert(node->priority > node->left->priority); + treap_validate_tree(node->left); + } + if (node->right) { + assert(node->right->key > node->key); + assert(node->priority > node->right->priority); + treap_validate_tree(node->right); + } +} +/** + Find the node with the largest key such that key <= value. + + Returns NULL if no such node exists. + + Expected complexity: O(log n). +*/ +static inline TreapNode * +treap_find_le(const Treap *tree, double value) +{ + TreapNode *node = tree->root, *best = NULL; + + while (node != NULL) { + if (node->key <= value) { + best = node; + node = node->right; + } else { + node = node->left; + } + } + return best; +} + +static inline TreapNode ** +treap_find_le_link(Treap *tree, double value) +{ + TreapNode **link = &tree->root, **best = NULL; + + while (*link != NULL) { + TreapNode *node = *link; + + if (node->key <= value) { + best = link; + link = &node->right; + } else { + link = &node->left; + } + } + + return best; +} + +/** + Find the node with the largest key such that key < value. + + Returns NULL if no such node exists. + + Expected complexity: O(log n). +*/ +static inline TreapNode * +treap_find_lt(const Treap *tree, double value) +{ + TreapNode *node = tree->root, *best = NULL; + + while (node != NULL) { + if (node->key < value) { + best = node; + node = node->right; + } else { + node = node->left; + } + } + return best; +} +/** + Find the node with the smallest key such that key >= value. + + Returns NULL if no such node exists. + + Expected complexity: O(log n). +*/ +static inline TreapNode * +treap_find_ge(const Treap *tree, double value) +{ + TreapNode *node = tree->root, *best = NULL; + + while (node != NULL) { + if (node->key >= value) { + best = node; + node = node->left; + } else { + node = node->right; + } + } + return best; +} + +/** + Find the node with the smallest key such that key > value. + + Returns NULL if no such node exists. + + Expected complexity: O(log n). +*/ +static inline TreapNode * +treap_find_gt(const Treap *tree, double value) +{ + TreapNode *node = tree->root, *best = NULL; + + while (node != NULL) { + if (node->key > value) { + best = node; + node = node->left; + } else { + node = node->right; + } + } + return best; +} + + +/** + Find a node with exactly key == value. + + Returns NULL if absent. + + Expected complexity: O(log n). + */ +static inline TreapNode * +treap_find(const Treap *tree, double value) +{ + TreapNode *node = tree->root; + + while (node) { + if (value < node->key) + node = node->left; + else if (value > node->key) + node = node->right; + else + return node; + } + return NULL; +} + +/** + Insert a single node. + + PRECONDITIONS: + + - node is initialized with treap_node_init(). + - node->key does not already exist in the treap. + - node->left == NULL. + - node->right == NULL. + - node->item[0] == node->key. + + This function does not check whether the resulting set is a valid + Pareto frontier. Use treap_insert_and_displace() when inserting + a new non-dominated point into an existing frontier. +*/ +static inline void +treap_insert_node(Treap *tree, TreapNode *node) +{ + assert(tree != NULL); + assert(node != NULL); + assert(node->left == NULL); + assert(node->right == NULL); + // node->key is not already in the tree. + assert(treap_find(tree, node->key) == NULL); + DEBUG1(treap_validate_tree(tree->root)); + + node->priority = treap_random(tree); + +#if 0 + TreapNode *left; + TreapNode *right; + /* Since keys are guaranteed unique by the caller, there is no need to + search for an existing key before doing the split. */ + treap_split_lt(tree->root, node->key, &left, &right); + tree->root = treap_merge(treap_merge(left, node), right); +#else + tree->root = treap_insert_node_and_rotate(tree->root, node); +#endif + DEBUG1(treap_validate_tree(tree->root)); +} + +static inline TreapNode * +treap_erase_at(TreapNode **link) +{ + assert(link != NULL); + assert(*link != NULL); + + TreapNode *node = *link; + *link = treap_merge(node->left, node->right); + node->left = NULL; + node->right = NULL; + return node; +} + +/** + Inserts node into tree and detaches all nodes dominated by node. + + On return: + + *prev = node's predecessor in the resulting tree + *next = node's successor in the resulting tree + + Returns the root of a separate treap containing the displaced nodes. + + Expected complexity: O(log n). + + FIXME: This function performs 4 tree traversals. It may be possible to do + it faster while rotating nodes as needed. +*/ +static inline TreapNode * +treap_insert_and_displace_get_bounds(Treap *tree, TreapNode *node, + TreapNode **prev, TreapNode **next) +{ + assert(tree != NULL); + assert(node != NULL); + assert(node->left == NULL); + assert(node->right == NULL); + + const double x = node->key; + const double y = treap_item_get_z(node->item)[1]; + TreapNode *left, *right; + if (prev) { + TreapNode *left_max; + treap_split_lt_with_max(tree->root, x, &left, &right, &left_max); + *prev = left_max; + } else { + treap_split_lt(tree->root, x, &left, &right); + } + + /* On the frontier, z[1] decreases with increasing key. Therefore the + nodes in right with z[1] >= y are exactly the nodes dominated by + node, and they form a contiguous prefix. */ + TreapNode *displaced, *keep; + if (next) { + TreapNode *keep_min; + treap_split_z1_ge_with_min(right, y, &displaced, &keep, &keep_min); + *next = keep_min; + } else { + treap_split_z1_ge(right, y, &displaced, &keep); + } + + // Insert the new node between left and keep. + node->priority = treap_random(tree); + node->left = NULL; + node->right = NULL; + // Key ordering is: left < node < keep + tree->root = treap_merge(treap_merge(left, node), keep); + DEBUG1(treap_validate_tree(tree->root)); + return displaced; +} + +/** + Insert a new point into a mutually non-dominated frontier and detach all + existing nodes dominated by it. + + PRECONDITIONS: + + - tree is a valid mutually non-dominated frontier. + - node is not already in tree. + - node->key is unique in tree. + - no node in tree weakly dominates node. + - node->left == NULL. + - node->right == NULL. + - node->key == node->item[0]. + + POSTCONDITIONS: + + - node belongs to tree. + - the returned root is a separate treap containing exactly the nodes + removed from tree. + - every returned node is dominated by node. + - no node other than those dominated by node is removed. + + Expected complexity: O(log n). +*/ +static inline TreapNode * +treap_insert_and_displace(Treap *tree, TreapNode *node) +{ + return treap_insert_and_displace_get_bounds(tree, node, NULL, NULL); +} + +#endif /* TREAP_H */ From 3381a7ab22359894d929b76d9c23c137dab32dde Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 17:05:27 +0000 Subject: [PATCH 2/2] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/lorenzwalthert/precommit: v0.4.3.9029 → v0.4.3.9030](https://github.com/lorenzwalthert/precommit/compare/v0.4.3.9029...v0.4.3.9030) - [github.com/tox-dev/tox-ini-fmt: 1.7.2 → 1.9.0](https://github.com/tox-dev/tox-ini-fmt/compare/1.7.2...1.9.0) - [github.com/tox-dev/pyproject-fmt: v2.25.2 → v2.29.4](https://github.com/tox-dev/pyproject-fmt/compare/v2.25.2...v2.29.4) - [github.com/astral-sh/ruff-pre-commit: v0.15.21 → v0.16.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.21...v0.16.7) --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 99eccc218..98bf2694b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,7 @@ ci: repos: - repo: https://github.com/lorenzwalthert/precommit - rev: v0.4.3.9029 + rev: v0.4.3.9030 hooks: - id: parsable-R - id: no-browser-statement @@ -49,19 +49,19 @@ repos: exclude: '(\.Rd|python/doc/source/reference/.*|test-doctest-.*)' - repo: https://github.com/tox-dev/tox-ini-fmt - rev: 1.7.2 + rev: 1.9.0 hooks: - id: tox-ini-fmt - repo: https://github.com/tox-dev/pyproject-fmt - rev: v2.25.2 + rev: v2.29.4 hooks: - id: pyproject-fmt additional_dependencies: ["tox>=4.12.1"] - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.15.21 + rev: v0.16.7 hooks: # Run the formatter. - id: ruff-format