diff --git a/src/backend/lib/Makefile b/src/backend/lib/Makefile index 98ce3d7..327a1bc 100644 --- a/src/backend/lib/Makefile +++ b/src/backend/lib/Makefile @@ -12,6 +12,6 @@ subdir = src/backend/lib top_builddir = ../../.. include $(top_builddir)/src/Makefile.global -OBJS = ilist.o stringinfo.o +OBJS = ilist.o binaryheap.o stringinfo.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/include/lib/binaryheap.h b/src/include/lib/binaryheap.h new file mode 100644 index 0000000..aebf08f --- /dev/null +++ b/src/include/lib/binaryheap.h @@ -0,0 +1,138 @@ +/* + * binaryheap.h + * + * A simple binary heap implementation + * + * Portions Copyright (c) 2012, PostgreSQL Global Development Group + * + * src/include/lib/binaryheap.h + */ + +#ifndef BINARYHEAP_H +#define BINARYHEAP_H + +/* + * This structure represents a single node in a binaryheap, and just + * holds two pointers. The heap management code doesn't care what is + * stored in a node (in particular, the key or value may be NULL), + * only that the comparator function can compare any two nodes. + */ + +typedef struct binaryheap_node +{ + void *key; + void *value; +} binaryheap_node; + +/* + * For a max-heap, the comparator must return: + * -1 iff a < b + * 0 iff a == b + * +1 iff a > b + * For a min-heap, the conditions are reversed. + */ +typedef int (*binaryheap_comparator)(binaryheap_node *a, binaryheap_node *b); + +/* + * binaryheap + * + * size how many nodes are currently in "nodes" + * space how many nodes can be stored in "nodes" + * comparator comparator to define the heap property + * nodes the first of a list of "space" nodes + */ + +typedef struct binaryheap +{ + size_t size; + size_t space; + binaryheap_comparator compare; + binaryheap_node nodes[1]; +} binaryheap; + +/* + * binaryheap_allocate + * + * Returns a pointer to a newly-allocated heap that has the capacity to + * store the given number of nodes, with the heap property defined by + * the given comparator function. + */ + +binaryheap * +binaryheap_allocate(size_t capacity, binaryheap_comparator compare); + +/* + * binaryheap_free + * + * Releases memory used by the given binaryheap. + */ + +void +binaryheap_free(binaryheap *heap); + +/* + * binaryheap_add_unordered + * + * Adds the given key and value to the end of the heap's list of nodes + * in O(1) without preserving the heap property. This is a convenience + * to add elements quickly to a new heap. To obtain a valid heap, one + * must call binaryheap_build() afterwards. + */ + +void +binaryheap_add_unordered(binaryheap *heap, void *key, void *value); + +/* + * binaryheap_build + * + * Assembles a valid heap in O(n) from the nodes added by + * binaryheap_add_unordered(). Not needed otherwise. + */ + +void +binaryheap_build(binaryheap *heap); + +/* + * binaryheap_add + * + * Adds the given key and value to the heap in O(log n), while + * preserving the heap property. + */ + +void +binaryheap_add(binaryheap *heap, void *key, void *value); + +/* + * binaryheap_first + * + * Returns a pointer to the first (root, topmost) node in the heap + * without modifying the heap. Returns NULL if the heap is empty. + * Always O(1). + */ + +binaryheap_node * +binaryheap_first(binaryheap *heap); + +/* + * binaryheap_remove_first + * + * Removes the first (root, topmost) node in the heap and returns a + * pointer to it after rebalancing the heap. Returns NULL if the heap + * is empty. O(log n) worst case. + */ + +binaryheap_node * +binaryheap_remove_first(binaryheap *heap); + +/* + * binaryheap_replace_first + * + * Change the key and/or value of the first (root, topmost) node and + * ensure that the heap property is preserved. O(1) in the best case, + * or O(log n) if it must fall back to sifting the new node down. + */ + +void +binaryheap_replace_first(binaryheap *heap, void *newkey, void *newval); + +#endif /* BINARYHEAP_H */ diff --git a/src/backend/lib/binaryheap.c b/src/backend/lib/binaryheap.c new file mode 100644 index 0000000..0fa8525 --- /dev/null +++ b/src/backend/lib/binaryheap.c @@ -0,0 +1,342 @@ +/*------------------------------------------------------------------------- + * + * binaryheap.c + * A simple binary heap implementaion + * + * Portions Copyright (c) 2012, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/lib/binaryheap.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include + +#include "lib/binaryheap.h" + +/* XXX + * The following is only to allow testbinheap.c to use this code outside + * the backend; should be removed before committing. */ +#ifdef TESTBINHEAP +#define palloc malloc +#define pfree free +#endif + +static void sift_down(binaryheap *heap, size_t node_off); +static void sift_up(binaryheap *heap, size_t node_off); +static inline void swap_nodes(binaryheap *heap, size_t a, size_t b); + +/* + * binaryheap_allocate + * + * Returns a pointer to a newly-allocated heap that has the capacity to + * store the given number of nodes, with the heap property defined by + * the given comparator function. + */ + +binaryheap * +binaryheap_allocate(size_t capacity, binaryheap_comparator compare) +{ + int sz = sizeof(binaryheap) + sizeof(binaryheap_node) * (capacity-1); + + binaryheap *heap = palloc(sz); + heap->compare = compare; + heap->space = capacity; + heap->size = 0; + return heap; +} + +/* + * binaryheap_free + * + * Releases memory used by the given binaryheap. + */ + +void +binaryheap_free(binaryheap *heap) +{ + pfree(heap); +} + +/* + * These utility functions return the offset of the left child, right + * child, and parent of the node at the given index, respectively. + * + * The heap is represented as an array of nodes, with the root node + * stored at index 0. The left child of node i is at index 2*i+1, and + * the right child at 2*i+2. The parent of node i is at index (i-1)/2. + */ + +static inline int +left_offset(size_t i) +{ + return 2 * i + 1; +} + +static inline int +right_offset(size_t i) +{ + return 2 * i + 2; +} + +static inline int +parent_offset(size_t i) +{ + return floor((i - 1) / 2); +} + +/* + * binaryheap_add_unordered + * + * Adds the given key and value to the end of the heap's list of nodes + * in O(1) without preserving the heap property. This is a convenience + * to add elements quickly to a new heap. To obtain a valid heap, one + * must call binaryheap_build() afterwards. + */ + +void +binaryheap_add_unordered(binaryheap *heap, void *key, void *value) +{ + if (heap->size + 1 == heap->space) + Assert("binary heap is full"); + heap->nodes[heap->size].key = key; + heap->nodes[heap->size++].value = value; +} + +/* + * binaryheap_build + * + * Assembles a valid heap in O(n) from the nodes added by + * binaryheap_add_unordered(). Not needed otherwise. + */ + +void +binaryheap_build(binaryheap *heap) +{ + int i; + + for (i = parent_offset(heap->size - 1); i >= 0; i--) + { + sift_down(heap, i); + } +} + +/* + * binaryheap_add + * + * Adds the given key and value to the heap in O(log n), while + * preserving the heap property. + */ + +void +binaryheap_add(binaryheap *heap, void *key, void *value) +{ + binaryheap_add_unordered(heap, key, value); + sift_up(heap, heap->size - 1); +} + +/* + * binaryheap_first + * + * Returns a pointer to the first (root, topmost) node in the heap + * without modifying the heap. Returns NULL if the heap is empty. + * Always O(1). + */ + +binaryheap_node * +binaryheap_first(binaryheap *heap) +{ + if (!heap->size) + return NULL; + return &heap->nodes[0]; +} + +/* + * binaryheap_remove_first + * + * Removes the first (root, topmost) node in the heap and returns a + * pointer to it after rebalancing the heap. Returns NULL if the heap + * is empty. O(log n) worst case. + */ + +binaryheap_node * +binaryheap_remove_first(binaryheap *heap) +{ + if (heap->size == 0) + return NULL; + + if (heap->size == 1) + { + heap->size--; + return &heap->nodes[0]; + } + + /* + * Swap the root and last nodes, decrease the size of the heap (i.e. + * remove the former root node) and sift the new root node down to + * its correct position. + */ + + swap_nodes(heap, 0, heap->size - 1); + + heap->size--; + sift_down(heap, 0); + return &heap->nodes[heap->size]; +} + +/* + * binaryheap_replace_first + * + * Change the key and/or value of the first (root, topmost) node and + * ensure that the heap property is preserved. O(1) in the best case, + * or O(log n) if it must fall back to sifting the new node down. + */ + +void +binaryheap_replace_first(binaryheap *heap, void *key, void *val) +{ + int ret; + size_t next_off = 0; + + if (key) + heap->nodes[0].key = key; + if (val) + heap->nodes[0].value = val; + + /* + * If the new root node is still larger than the largest of its + * children (of which there may be 0, 1, or 2), then the heap is + * still valid. + */ + + if (heap->size == 1) { + return; + } + if (heap->size == 2) { + next_off = 1; + } + else { + /* Two children: pick the larger one */ + ret = heap->compare(&heap->nodes[2], &heap->nodes[1]); + if (ret == -1) + next_off = 1; + else + next_off = 2; + } + + ret = heap->compare(&heap->nodes[next_off], &heap->nodes[0]); + if (ret == -1) + return; + + /* The child is larger. Swap and sift the new node down. */ + + swap_nodes(heap, 0, next_off); + sift_down(heap, next_off); +} + +/* + * Swap the contents of two nodes. + */ + +static inline void +swap_nodes(binaryheap *heap, size_t a, size_t b) +{ + binaryheap_node swap; + swap.value = heap->nodes[a].value; + swap.key = heap->nodes[a].key; + + heap->nodes[a].value = heap->nodes[b].value; + heap->nodes[a].key = heap->nodes[b].key; + + heap->nodes[b].key = swap.key; + heap->nodes[b].value = swap.value; +} + +/* + * Sift a node up to the highest position it can hold according to the + * comparator. + */ + +static void +sift_up(binaryheap *heap, size_t node_off) +{ + /* manually unrolled tail recursion */ + while (true) + { + size_t parent_off = parent_offset(node_off); + + if (node_off == 0) + break; + + if (heap->compare(&heap->nodes[parent_off], + &heap->nodes[node_off]) < 0) + { + /* heap property violated */ + swap_nodes(heap, node_off, parent_off); + + /* recurse */ + node_off = parent_off; + } + else + break; + } +} + +/* + * Sift a node down from its current position to satisfy the heap + * property. + */ + +static void +sift_down(binaryheap *heap, size_t node_off) +{ + /* manually unrolled tail recursion */ + while (true) + { + size_t left_off = left_offset(node_off); + size_t right_off = right_offset(node_off); + size_t swap_off = 0; + + /* Is the left child larger than the parent? */ + + if (left_off < heap->size && + heap->compare(&heap->nodes[node_off], + &heap->nodes[left_off]) < 0) + { + swap_off = left_off; + } + + /* + * If not (note: only one child can violate the heap property + * after a change), is the right child larger? + */ + + if (right_off < heap->size && + heap->compare(&heap->nodes[node_off], + &heap->nodes[right_off]) < 0) + { + /* swap with the larger child */ + if (!swap_off || + heap->compare(&heap->nodes[left_off], + &heap->nodes[right_off]) < 0) + { + swap_off = right_off; + } + } + + if (!swap_off) + { + /* heap condition fullfilled, abort */ + break; + } + + /* swap node with the child violating the property */ + swap_nodes(heap, swap_off, node_off); + + /* recurse, check child subtree */ + node_off = swap_off; + } +}