1919 */
2020typedef uint64_t (* priority_queue_get_priority_fn_t )(void * element );
2121
22+ /**
23+ * How to get/set an element's current 1-based index within the heap's backing array.
24+ * Returns a pointer to a size_t slot stored inside the element itself. When provided, the
25+ * queue keeps this slot in sync on every move, allowing priority_queue_delete to locate the
26+ * element in O(1) and repair the heap in O(log n) instead of scanning in O(n). Pass NULL to
27+ * priority_queue_initialize to keep the legacy linear-scan delete.
28+ * @param element
29+ * @returns pointer to the element's index slot
30+ */
31+ typedef size_t * (* priority_queue_index_ptr_fn_t )(void * element );
32+
2233/* We assume that priority is expressed in terms of a 64 bit unsigned integral */
2334struct priority_queue {
2435 priority_queue_get_priority_fn_t get_priority_fn ;
36+ priority_queue_index_ptr_fn_t get_index_fn ; /* NULL => O(n) delete; non-NULL => O(log n) delete */
2537 bool use_lock ;
2638 lock_t lock ;
2739 uint64_t highest_priority ;
@@ -49,6 +61,46 @@ priority_queue_update_highest_priority(struct priority_queue *priority_queue, co
4961 priority_queue -> highest_priority = priority ;
5062}
5163
64+ /**
65+ * Records an element's current slot inside the element itself, when index tracking is enabled.
66+ * No-op (single branch) for queues initialized without an index accessor.
67+ * @param priority_queue the priority queue
68+ * @param index the 1-based slot whose occupant should remember its position
69+ */
70+ static inline void
71+ priority_queue_record_index (struct priority_queue * priority_queue , size_t index )
72+ {
73+ if (priority_queue -> get_index_fn != NULL )
74+ * priority_queue -> get_index_fn (priority_queue -> items [index ]) = index ;
75+ }
76+
77+ /**
78+ * Marks an element as no longer enqueued (slot 0), when index tracking is enabled
79+ * @param priority_queue the priority queue
80+ * @param element the departing element
81+ */
82+ static inline void
83+ priority_queue_clear_index (struct priority_queue * priority_queue , void * element )
84+ {
85+ if (priority_queue -> get_index_fn != NULL ) * priority_queue -> get_index_fn (element ) = 0 ;
86+ }
87+
88+ /**
89+ * Swaps two heap slots, keeping any tracked indices in sync
90+ * @param priority_queue the priority queue
91+ * @param a 1-based slot
92+ * @param b 1-based slot
93+ */
94+ static inline void
95+ priority_queue_swap (struct priority_queue * priority_queue , size_t a , size_t b )
96+ {
97+ void * temp = priority_queue -> items [a ];
98+ priority_queue -> items [a ] = priority_queue -> items [b ];
99+ priority_queue -> items [b ] = temp ;
100+ priority_queue_record_index (priority_queue , a );
101+ priority_queue_record_index (priority_queue , b );
102+ }
103+
52104/**
53105 * Adds a value to the end of the binary heap
54106 * @param priority_queue the priority queue
@@ -67,6 +119,7 @@ priority_queue_append(struct priority_queue *priority_queue, void *new_item)
67119 if (unlikely (priority_queue -> size > priority_queue -> capacity )) panic ("PQ overflow" );
68120 if (unlikely (priority_queue -> size == priority_queue -> capacity )) goto err_enospc ;
69121 priority_queue -> items [++ priority_queue -> size ] = new_item ;
122+ priority_queue_record_index (priority_queue , priority_queue -> size );
70123
71124 rc = 0 ;
72125done :
@@ -90,6 +143,32 @@ priority_queue_is_empty(struct priority_queue *priority_queue)
90143 return priority_queue -> size == 0 ;
91144}
92145
146+ /**
147+ * Shifts the value at start_index upwards to restore the heap structure property, keeping any
148+ * tracked indices and the memoized highest priority in sync
149+ * @param priority_queue the priority queue
150+ * @param start_index the 1-based slot to percolate up from
151+ */
152+ static inline void
153+ priority_queue_percolate_up_from (struct priority_queue * priority_queue , int start_index )
154+ {
155+ assert (priority_queue != NULL );
156+ assert (priority_queue -> get_priority_fn != NULL );
157+ assert (!priority_queue -> use_lock || lock_is_locked (& priority_queue -> lock ));
158+
159+ for (int i = start_index ; i / 2 != 0
160+ && priority_queue -> get_priority_fn (priority_queue -> items [i ])
161+ < priority_queue -> get_priority_fn (priority_queue -> items [i / 2 ]);
162+ i /= 2 ) {
163+ assert (priority_queue -> get_priority_fn (priority_queue -> items [i ]) != ULONG_MAX );
164+ priority_queue_swap (priority_queue , i / 2 , i );
165+ /* If percolated to highest priority, update highest priority */
166+ if (i / 2 == 1 )
167+ priority_queue_update_highest_priority (priority_queue , priority_queue -> get_priority_fn (
168+ priority_queue -> items [1 ]));
169+ }
170+ }
171+
93172/**
94173 * Shifts an appended value upwards to restore heap structure property
95174 * @param priority_queue the priority queue
@@ -108,19 +187,7 @@ priority_queue_percolate_up(struct priority_queue *priority_queue)
108187 return ;
109188 }
110189
111- for (int i = priority_queue -> size ; i / 2 != 0
112- && priority_queue -> get_priority_fn (priority_queue -> items [i ])
113- < priority_queue -> get_priority_fn (priority_queue -> items [i / 2 ]);
114- i /= 2 ) {
115- assert (priority_queue -> get_priority_fn (priority_queue -> items [i ]) != ULONG_MAX );
116- void * temp = priority_queue -> items [i / 2 ];
117- priority_queue -> items [i / 2 ] = priority_queue -> items [i ];
118- priority_queue -> items [i ] = temp ;
119- /* If percolated to highest priority, update highest priority */
120- if (i / 2 == 1 )
121- priority_queue_update_highest_priority (priority_queue , priority_queue -> get_priority_fn (
122- priority_queue -> items [1 ]));
123- }
190+ priority_queue_percolate_up_from (priority_queue , priority_queue -> size );
124191}
125192
126193/**
@@ -180,9 +247,7 @@ priority_queue_percolate_down(struct priority_queue *priority_queue, int parent_
180247 <= priority_queue -> get_priority_fn (priority_queue -> items [smallest_child_index ]))
181248 break ;
182249 /* Otherwise, swap and continue down the tree */
183- void * temp = priority_queue -> items [smallest_child_index ];
184- priority_queue -> items [smallest_child_index ] = priority_queue -> items [parent_index ];
185- priority_queue -> items [parent_index ] = temp ;
250+ priority_queue_swap (priority_queue , smallest_child_index , parent_index );
186251
187252 parent_index = smallest_child_index ;
188253 left_child_index = 2 * parent_index ;
@@ -225,9 +290,11 @@ priority_queue_dequeue_if_earlier_nolock(struct priority_queue *priority_queue,
225290 if (priority_queue_is_empty (priority_queue ) || priority_queue -> highest_priority >= target_deadline )
226291 goto err_enoent ;
227292
228- * dequeued_element = priority_queue -> items [1 ];
293+ * dequeued_element = priority_queue -> items [1 ];
294+ priority_queue_clear_index (priority_queue , * dequeued_element );
229295 priority_queue -> items [1 ] = priority_queue -> items [priority_queue -> size ];
230296 priority_queue -> items [priority_queue -> size -- ] = NULL ;
297+ if (priority_queue -> size >= 1 ) priority_queue_record_index (priority_queue , 1 );
231298
232299 priority_queue_percolate_down (priority_queue , 1 );
233300 return_code = 0 ;
@@ -264,10 +331,13 @@ priority_queue_dequeue_if_earlier(struct priority_queue *priority_queue, void **
264331 * @param capacity the number of elements to store in the data structure
265332 * @param use_lock indicates that we want a concurrent data structure
266333 * @param get_priority_fn pointer to a function that returns the priority of an element
334+ * @param get_index_fn pointer to a function exposing an element's in-struct index slot, enabling
335+ * O(log n) priority_queue_delete; pass NULL to keep the legacy O(n) linear-scan delete
267336 * @return priority queue
268337 */
269338static inline struct priority_queue *
270- priority_queue_initialize (size_t capacity , bool use_lock , priority_queue_get_priority_fn_t get_priority_fn )
339+ priority_queue_initialize (size_t capacity , bool use_lock , priority_queue_get_priority_fn_t get_priority_fn ,
340+ priority_queue_index_ptr_fn_t get_index_fn )
271341{
272342 assert (get_priority_fn != NULL );
273343
@@ -280,6 +350,7 @@ priority_queue_initialize(size_t capacity, bool use_lock, priority_queue_get_pri
280350 priority_queue -> size = 0 ;
281351 priority_queue -> capacity = capacity ;
282352 priority_queue -> get_priority_fn = get_priority_fn ;
353+ priority_queue -> get_index_fn = get_index_fn ;
283354 priority_queue -> use_lock = use_lock ;
284355
285356 if (use_lock ) lock_init (& priority_queue -> lock );
@@ -408,16 +479,41 @@ priority_queue_delete_nolock(struct priority_queue *priority_queue, void *value)
408479 assert (value != NULL );
409480 assert (!priority_queue -> use_lock || lock_is_locked (& priority_queue -> lock ));
410481
411- for (int i = 1 ; i <= priority_queue -> size ; i ++ ) {
412- if (priority_queue -> items [i ] == value ) {
413- priority_queue -> items [i ] = priority_queue -> items [priority_queue -> size ];
414- priority_queue -> items [priority_queue -> size -- ] = NULL ;
482+ int i ;
483+ if (priority_queue -> get_index_fn != NULL ) {
484+ /* O(log n): the element remembers its own slot. Validate it still points back to value
485+ * (guards against a value that was never enqueued or already removed). */
486+ i = (int )* priority_queue -> get_index_fn (value );
487+ if (i < 1 || (size_t )i > priority_queue -> size || priority_queue -> items [i ] != value ) return -1 ;
488+ } else {
489+ /* O(n) fallback: linear scan for the element */
490+ for (i = 1 ; (size_t )i <= priority_queue -> size && priority_queue -> items [i ] != value ; i ++ ) {}
491+ if ((size_t )i > priority_queue -> size ) return -1 ;
492+ }
493+
494+ priority_queue_clear_index (priority_queue , value );
495+
496+ /* Fill the hole with the last element and shrink */
497+ priority_queue -> items [i ] = priority_queue -> items [priority_queue -> size ];
498+ priority_queue -> items [priority_queue -> size -- ] = NULL ;
499+
500+ if ((size_t )i <= priority_queue -> size ) {
501+ priority_queue_record_index (priority_queue , i );
502+ /* Restore the heap property from the hole. Unlike a pop, the replacement can be smaller
503+ * than the hole's parent, so it may need to rise rather than sink. */
504+ if (i > 1
505+ && priority_queue -> get_priority_fn (priority_queue -> items [i ])
506+ < priority_queue -> get_priority_fn (priority_queue -> items [i / 2 ])) {
507+ priority_queue_percolate_up_from (priority_queue , i );
508+ } else {
415509 priority_queue_percolate_down (priority_queue , i );
416- return 0 ;
417510 }
511+ } else if (priority_queue -> size == 0 ) {
512+ /* Removed the final element */
513+ priority_queue_update_highest_priority (priority_queue , ULONG_MAX );
418514 }
419515
420- return -1 ;
516+ return 0 ;
421517}
422518
423519/**
0 commit comments