blob: 852dd4497bbf1b6963201a2079475e4d9677cdcf [file] [log] [blame]
/* gheap.c
*
* Copyright (C) 2014 Christian Hergert <christian@hergert.me>
*
* This file is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This file 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <string.h>
#include "gheap.h"
/**
* SECTION:gheap
* @title: Heaps
* @short_description: efficient priority queues using min/max heaps
*
* Heaps are similar to a partially sorted tree but implemented as an
* array. They allow for efficient O(1) lookup of the highest priority
* item as it will always be the first item of the array.
*
* To create a new heap use g_heap_new().
*
* To add items to the heap, use g_heap_insert_val() or
* g_heap_insert_vals() to insert in bulk.
*
* To access an item in the heap, use g_heap_index().
*
* To remove an arbitrary item from the heap, use g_heap_extract_index().
*
* To remove the highest priority item in the heap, use g_heap_extract().
*
* To free a heap, use g_heap_unref().
*
* Here is an example that stores integers in a #GHeap:
* |[<!-- language="C" -->
* static int
* cmpint (gconstpointer a,
* gconstpointer b)
* {
* return *(const gint *)a - *(const gint *)b;
* }
*
* int
* main (gint argc,
* gchar *argv[])
* {
* GHeap *heap;
* gint i;
* gint v;
*
* heap = g_heap_new (sizeof (gint), cmpint);
*
* for (i = 0; i < 10000; i++)
* g_heap_insert_val (heap, i);
* for (i = 0; i < 10000; i++)
* g_heap_extract (heap, &v);
*
* g_heap_unref (heap);
* }
* ]|
*/
#define MIN_HEAP_SIZE 16
/*
* Based upon Mastering Algorithms in C by Kyle Loudon.
* Section 10 - Heaps and Priority Queues.
*/
typedef struct _GHeapReal GHeapReal;
struct _GHeapReal
{
gchar *data;
gsize len;
volatile gint ref_count;
guint element_size;
gsize allocated_len;
GCompareFunc compare;
gchar tmp[0];
};
#define heap_parent(npos) (((npos)-1)/2)
#define heap_left(npos) (((npos)*2)+1)
#define heap_right(npos) (((npos)*2)+2)
#define heap_index(h,i) ((h)->data + (i * (h)->element_size))
#define heap_compare(h,a,b) ((h)->compare(heap_index(h,a), heap_index(h,b)))
#define heap_swap(h,a,b) \
G_STMT_START { \
memcpy ((h)->tmp, heap_index (h, a), (h)->element_size); \
memcpy (heap_index (h, a), heap_index (h, b), (h)->element_size); \
memcpy (heap_index (h, b), (h)->tmp, (h)->element_size); \
} G_STMT_END
GHeap *
g_heap_new (guint element_size,
GCompareFunc compare_func)
{
GHeapReal *real;
g_return_val_if_fail (element_size, NULL);
g_return_val_if_fail (compare_func, NULL);
real = g_malloc_n (1, sizeof (GHeapReal) + element_size);
real->data = NULL;
real->len = 0;
real->ref_count = 1;
real->element_size = element_size;
real->allocated_len = 0;
real->compare = compare_func;
return (GHeap *)real;
}
GHeap *
g_heap_ref (GHeap *heap)
{
GHeapReal *real = (GHeapReal *)heap;
g_return_val_if_fail (heap, NULL);
g_return_val_if_fail (real->ref_count, NULL);
g_atomic_int_inc (&real->ref_count);
return heap;
}
static void
g_heap_real_free (GHeapReal *real)
{
g_assert (real);
g_assert_cmpint (real->ref_count, ==, 0);
g_free (real->data);
g_free (real);
}
void
g_heap_unref (GHeap *heap)
{
GHeapReal *real = (GHeapReal *)heap;
g_return_if_fail (heap);
g_return_if_fail (real->ref_count);
if (g_atomic_int_dec_and_test (&real->ref_count)) {
g_heap_real_free (real);
}
}
static void
g_heap_real_grow (GHeapReal *real)
{
g_assert (real);
g_assert_cmpint (real->allocated_len, <, G_MAXSIZE / 2);
real->allocated_len = MAX (MIN_HEAP_SIZE, (real->allocated_len * 2));
real->data = g_realloc_n (real->data,
real->allocated_len,
real->element_size);
}
static void
g_heap_real_shrink (GHeapReal *real)
{
g_assert (real);
g_assert ((real->allocated_len / 2) >= real->len);
real->allocated_len = MAX (MIN_HEAP_SIZE, real->allocated_len / 2);
real->data = g_realloc_n (real->data,
real->allocated_len,
real->element_size);
}
static void
g_heap_real_insert_val (GHeapReal *real,
gconstpointer data)
{
gint ipos;
gint ppos;
g_assert (real);
g_assert (data);
if (G_UNLIKELY (real->len == real->allocated_len)) {
g_heap_real_grow (real);
}
memcpy (real->data + (real->element_size * real->len), data,
real->element_size);
ipos = real->len;
ppos = heap_parent (ipos);
while ((ipos > 0) && (heap_compare (real, ppos, ipos) < 0)) {
heap_swap (real, ppos, ipos);
ipos = ppos;
ppos = heap_parent (ipos);
}
real->len++;
}
void
g_heap_insert_vals (GHeap *heap,
gconstpointer data,
guint len)
{
GHeapReal *real = (GHeapReal *)heap;
const gchar *ptr = data;
guint i;
g_return_if_fail (heap);
g_return_if_fail (data);
g_return_if_fail (len);
for (i = 0; i < len; i++, ptr += real->element_size) {
g_heap_real_insert_val (real, ptr);
}
}
gboolean
g_heap_extract (GHeap *heap,
gpointer result)
{
GHeapReal *real = (GHeapReal *)heap;
gboolean ret;
gint ipos;
gint lpos;
gint rpos;
gint mpos;
g_return_val_if_fail (heap, FALSE);
ret = (real->len > 0);
if (ret) {
if (result) {
memcpy (result, heap_index (real, 0), real->element_size);
}
if (real->len && --real->len) {
memmove (real->data, heap_index (real, real->len), real->element_size);
ipos = 0;
for (;;) {
lpos = heap_left (ipos);
rpos = heap_right (ipos);
if ((lpos < real->len) && (heap_compare (real, lpos, ipos) > 0)) {
mpos = lpos;
} else {
mpos = ipos;
}
if ((rpos < real->len) && (heap_compare (real, rpos, mpos) > 0)) {
mpos = rpos;
}
if (mpos == ipos) {
break;
}
heap_swap (real, mpos, ipos);
ipos = mpos;
}
}
}
if ((real->len >= MIN_HEAP_SIZE) &&
(real->allocated_len / 2) >= real->len) {
g_heap_real_shrink (real);
}
return ret;
}
gboolean
g_heap_extract_index (GHeap *heap,
guint index_,
gpointer result)
{
GHeapReal *real = (GHeapReal *)heap;
gboolean ret;
gint ipos;
gint lpos;
gint mpos;
gint ppos;
gint rpos;
g_return_val_if_fail (heap, FALSE);
ret = (real->len > 0);
if (real->len) {
if (result) {
memcpy (result, heap_index (real, 0), real->element_size);
}
real->len--;
if (real->len && index_ != real->len) {
memcpy (heap_index (real, index_),
heap_index (real, real->len),
real->element_size);
ipos = index_;
ppos = heap_parent (ipos);
while (heap_compare (real, ipos, ppos) > 0) {
heap_swap (real, ipos, ppos);
ipos = ppos;
ppos = heap_parent (ppos);
}
if (ipos == index_) {
for (;;) {
lpos = heap_left (ipos);
rpos = heap_right (ipos);
if ((lpos < real->len) && (heap_compare (real, lpos, ipos) > 0)) {
mpos = lpos;
} else {
mpos = ipos;
}
if ((rpos < real->len) && (heap_compare (real, rpos, mpos) > 0)) {
mpos = rpos;
}
if (mpos == ipos) {
break;
}
heap_swap (real, mpos, ipos);
ipos = mpos;
}
}
}
}
if ((real->len >= MIN_HEAP_SIZE) &&
(real->allocated_len / 2) >= real->len) {
g_heap_real_shrink (real);
}
return ret;
}