60e7c3e7ba
* NEW: Heap is a new class that implements a Min- or MaxHeap, depending upon a mode parameter to its constructor. It supports some advanced ways to manipulate the contained data -- beyond the first entry. git-svn-id: svn://localhost/gambas/trunk@6402 867c0c6c-44f3-4631-809d-bfa615b0a4ec
379 lines
8.9 KiB
C
379 lines
8.9 KiB
C
/*
|
|
* c_heap.c - (Min-/Max-)Heap and PrioSet
|
|
*
|
|
* Copyright (C) 2014 Tobias Boege <tobias@gambas-buch.de>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
* MA 02110-1301, USA.
|
|
*/
|
|
|
|
#define __C_HEAP_C
|
|
|
|
#include "gambas.h"
|
|
#include "gb_common.h" /* EXTERN for gbx_compare.h */
|
|
#include "gbx_compare.h" /* GB_COMP_{ASCENT,DESCENT} */
|
|
#include "gbx_type.h" /* TYPE_is_object() */
|
|
|
|
#include "c_heap.h"
|
|
|
|
typedef struct {
|
|
GB_BASE ob;
|
|
int mode;
|
|
int count;
|
|
GB_VARIANT_VALUE *h;
|
|
} CHEAP; /* "Hey! Don't talk like this about my data structures!" */
|
|
|
|
static inline int compare(CHEAP *heap, int i, int j)
|
|
{
|
|
int res = GB.CompVariant(&heap->h[i], &heap->h[j]);
|
|
|
|
return heap->mode == GB_COMP_ASCENT ? res : -res;
|
|
}
|
|
|
|
static inline int compare1(CHEAP *heap, GB_VARIANT_VALUE *x, int j)
|
|
{
|
|
int res = GB.CompVariant(x, &heap->h[j]);
|
|
|
|
return heap->mode == GB_COMP_ASCENT ? res : -res;
|
|
}
|
|
|
|
static inline int compare3(CHEAP *heap, GB_VARIANT_VALUE *x,
|
|
GB_VARIANT_VALUE *y)
|
|
{
|
|
int res = GB.CompVariant(x, y);
|
|
|
|
return heap->mode == GB_COMP_ASCENT ? res : -res;
|
|
}
|
|
|
|
static inline void copy(CHEAP *heap, int src, int dst)
|
|
{
|
|
memmove(&heap->h[dst], &heap->h[src], sizeof(GB_VARIANT_VALUE));
|
|
}
|
|
|
|
static inline void copy1(CHEAP *heap, int src, GB_VARIANT_VALUE *dst)
|
|
{
|
|
memmove(dst, &heap->h[src], sizeof(GB_VARIANT_VALUE));
|
|
}
|
|
|
|
static inline void copy2(CHEAP *heap, GB_VARIANT_VALUE *src, int dst)
|
|
{
|
|
memmove(&heap->h[dst], src, sizeof(GB_VARIANT_VALUE));
|
|
}
|
|
|
|
#define left(k) (2 * (k) + 1)
|
|
#define right(k) (left(k) + 1)
|
|
#define parent(k) (((k) - 1) / 2)
|
|
|
|
static int upheap(CHEAP *heap, int k)
|
|
{
|
|
GB_VARIANT_VALUE x;
|
|
int r = 0;
|
|
|
|
copy1(heap, k, &x);
|
|
while (k && compare1(heap, &x, parent(k)) < 0) {
|
|
copy(heap, parent(k), k);
|
|
k = parent(k);
|
|
r++;
|
|
}
|
|
copy2(heap, &x, k);
|
|
return r;
|
|
}
|
|
|
|
static int downheap(CHEAP *heap, int k)
|
|
{
|
|
int count = GB.Count(heap->h), r = 0;
|
|
GB_VARIANT_VALUE x;
|
|
|
|
copy1(heap, k, &x);
|
|
while (k <= parent(count - 1)) {
|
|
int j, l = j = left(k), r = right(k);
|
|
|
|
if (r < count && compare(heap, l, r) > 0)
|
|
j = r;
|
|
if (compare1(heap, &x, j) <= 0)
|
|
break;
|
|
copy(heap, j, k);
|
|
k = j;
|
|
r++;
|
|
}
|
|
copy2(heap, &x, k);
|
|
return r;
|
|
}
|
|
|
|
static void new_heap(CHEAP *heap, int count)
|
|
{
|
|
GB.NewArray(&heap->h, sizeof(*heap->h), count);
|
|
}
|
|
|
|
static void rebuild(CHEAP *heap)
|
|
{
|
|
int i;
|
|
|
|
for (i = parent(GB.Count(heap->h) - 1); i >= 0; i--)
|
|
downheap(heap, i);
|
|
}
|
|
|
|
static void from_array(CHEAP *heap, GB_ARRAY array)
|
|
{
|
|
int count = GB.Array.Count(array), i;
|
|
|
|
new_heap(heap, count);
|
|
for (i = 0; i < count; i++) {
|
|
memcpy(&heap->h[i], GB.Array.Get(array, i),
|
|
sizeof(*heap->h));
|
|
if (TYPE_is_object(heap->h[i].type))
|
|
GB.Ref(heap->h[i].value._object);
|
|
}
|
|
rebuild(heap);
|
|
}
|
|
|
|
#define THIS ((CHEAP *) _object)
|
|
|
|
/**G
|
|
* Creates a new Heap.
|
|
*
|
|
* If 'Mode' is gb.Ascent, it's a MinHeap, i.e. the smallest element is at
|
|
* the beginning. If 'mode' is gb.Descent, it's a MaxHeap.
|
|
*
|
|
* If the 'Array' argument is given, a copy of that array is transformed
|
|
* into a Heap (by using a bottom-up algorithm which is O(n)).
|
|
*/
|
|
BEGIN_METHOD(Heap_new, GB_INTEGER mode; GB_OBJECT array)
|
|
|
|
THIS->mode = VARG(mode);
|
|
if (THIS->mode != GB_COMP_ASCENT && THIS->mode != GB_COMP_DESCENT) {
|
|
GB.Error("Invalid mode");
|
|
return;
|
|
}
|
|
|
|
if (MISSING(array)) {
|
|
new_heap(THIS, 0);
|
|
} else {
|
|
GB_ARRAY array = (GB_ARRAY) VARG(array);
|
|
|
|
if (GB.CheckObject(array))
|
|
return;
|
|
from_array(THIS, array);
|
|
}
|
|
|
|
END_METHOD
|
|
|
|
/**G
|
|
* Free up storage of the Heap
|
|
*/
|
|
BEGIN_METHOD_VOID(Heap_free)
|
|
|
|
int count = GB.Count(THIS->h), i;
|
|
|
|
for (i = 0; i < count; i++)
|
|
GB.StoreVariant(NULL, &THIS->h[i]);
|
|
GB.FreeArray(&THIS->h);
|
|
|
|
END_METHOD
|
|
|
|
/**G
|
|
* Insert an element into the Heap.
|
|
*/
|
|
BEGIN_METHOD(Heap_Insert, GB_VARIANT data)
|
|
|
|
GB.StoreVariant(ARG(data), GB.Add(&THIS->h));
|
|
upheap(THIS, GB.Count(THIS->h) - 1);
|
|
|
|
END_METHOD
|
|
|
|
static void delete(CHEAP *heap, int i, GB_VARIANT_VALUE *x)
|
|
{
|
|
int count = GB.Count(heap->h);
|
|
|
|
copy1(heap, i, x);
|
|
copy(heap, count - 1, i);
|
|
GB.Remove(&heap->h, count - 1, 1);
|
|
downheap(heap, i);
|
|
}
|
|
|
|
/**G
|
|
* Remove the first element.
|
|
*/
|
|
BEGIN_METHOD_VOID(Heap_Remove)
|
|
|
|
int count = GB.Count(THIS->h);
|
|
GB_VARIANT_VALUE x;
|
|
|
|
if (!count) {
|
|
GB.Error(GB_ERR_BOUND);
|
|
return;
|
|
}
|
|
|
|
delete(THIS, 0, &x);
|
|
GB.ReturnVariant(&x);
|
|
GB.ReturnBorrow();
|
|
GB.StoreVariant(NULL, &x);
|
|
GB.ReturnRelease();
|
|
|
|
END_METHOD
|
|
|
|
/**G
|
|
* Find all occurences of `Old' and replace them by `New'. This is an O(n)
|
|
* operation. Additionally the heap has to be rebuilt as soon as there are
|
|
* more than one replacement made.
|
|
*
|
|
* If `New' is Null, then the entry will be deleted.
|
|
*
|
|
* The search for objects is done by identity, i.e. by their addresses in
|
|
* memory, and *not* by using the _compare() method.
|
|
*
|
|
* If we are a heap of objects whose _compare() methods compare some sort of
|
|
* priority, and `Old' is the same object as `New', this can be used to
|
|
* propagate a priority change made to the object and will correct its
|
|
* position in the heap.
|
|
*
|
|
* This has an application in Dijkstra's algorithm (or any priority first
|
|
* search like Prim or A*) where you would update a node's priority.
|
|
*/
|
|
BEGIN_METHOD(Heap_Update, GB_VARIANT old; GB_VARIANT new)
|
|
|
|
int count = GB.Count(THIS->h), i, found = 0, idx = -1;
|
|
GB_VARIANT_VALUE *old, *new;
|
|
|
|
old = &VARG(old); new = &VARG(new);
|
|
for (i = 0; i < count; i++) {
|
|
/*
|
|
* Assumes that the identity comparison is unequally
|
|
* stricter than the _compare() comparison but identity
|
|
* equality implies _compare() identity -- so that we
|
|
* don't reject identity-equal objects in the _compare()
|
|
* check.
|
|
*
|
|
* That is to say: we assume that _compare() imposes at
|
|
* least a reflexive relation.
|
|
*/
|
|
if (compare1(THIS, old, i))
|
|
continue;
|
|
/* Compare objects by identity */
|
|
if (TYPE_is_object(THIS->h[i].type)
|
|
&& (!TYPE_is_object(old->type)
|
|
|| THIS->h[i].value._object != old->value._object))
|
|
continue;
|
|
/*
|
|
* Make Null delete the entry. We don't count that as a
|
|
* finding because we can fix the heap up on the fly.
|
|
*/
|
|
if (new->type == GB_T_NULL) {
|
|
GB_VARIANT_VALUE buf;
|
|
|
|
delete(THIS, i, &buf);
|
|
GB.StoreVariant(NULL, &buf);
|
|
count = GB.Count(THIS->h);
|
|
continue;
|
|
}
|
|
/* XXX: If `old' and `new' are the same and if they're
|
|
* objects, memory errors will occur if you store one over
|
|
* the other... Maybe StoreVariant() wasn't made to replace
|
|
* an object by itself (refcount goes to zero before it is
|
|
* incremented again, maybe?) */
|
|
if (!TYPE_is_object(THIS->h[i].type)
|
|
|| THIS->h[i].value._object != new->value._object)
|
|
GB.StoreVariant(ARG(new), &THIS->h[i]);
|
|
found++;
|
|
idx = i;
|
|
}
|
|
/*
|
|
* Most applications will have pairwise distinct elements in the
|
|
* heap, so that at most one element is changed. In this case, we
|
|
* can get away more quickly (O(log n) vs. O(n)) with an upheap()
|
|
* or downheap() call.
|
|
*/
|
|
if (found == 1) {
|
|
if (!upheap(THIS, idx))
|
|
downheap(THIS, idx);
|
|
} else if (found) {
|
|
rebuild(THIS);
|
|
}
|
|
|
|
END_METHOD
|
|
|
|
/**G
|
|
* Return or set the first element of the Heap.
|
|
*/
|
|
BEGIN_PROPERTY(Heap_First)
|
|
|
|
if (!GB.Count(THIS->h)) {
|
|
GB.Error(GB_ERR_BOUND);
|
|
return;
|
|
}
|
|
|
|
if (READ_PROPERTY) {
|
|
GB.ReturnVariant(&THIS->h[0]);
|
|
return;
|
|
}
|
|
GB.StoreVariant(PROP(GB_VARIANT), &THIS->h[0]);
|
|
downheap(THIS, 0);
|
|
|
|
END_PROPERTY
|
|
|
|
/**G
|
|
* Return the number of elements in the Heap.
|
|
*/
|
|
BEGIN_PROPERTY(Heap_Count)
|
|
|
|
GB.ReturnInteger(GB.Count(THIS->h));
|
|
|
|
END_PROPERTY
|
|
|
|
/**G
|
|
* Return whether the Heap is empty.
|
|
*/
|
|
BEGIN_PROPERTY(Heap_IsEmpty)
|
|
|
|
GB.ReturnBoolean(GB.Count(THIS->h) == 0);
|
|
|
|
END_PROPERTY
|
|
|
|
GB_DESC CHeap[] = {
|
|
/**G Heap
|
|
* This class implements a dynamic heap. It can be a MinHeap or a
|
|
* MaxHeap, depending on what mode you specify on construction.
|
|
*
|
|
* Being `dynamic' means that this class allows you to update the
|
|
* data or the position of elements which are already in the heap.
|
|
*/
|
|
GB_DECLARE("Heap", sizeof(CHEAP)),
|
|
|
|
GB_METHOD("_new", NULL, Heap_new, "(Mode)i[(Array)Variant[];]"),
|
|
GB_METHOD("_free", NULL, Heap_free, NULL),
|
|
|
|
GB_METHOD("Insert", NULL, Heap_Insert, "(Data)v"),
|
|
GB_METHOD("Remove", "v", Heap_Remove, NULL),
|
|
GB_METHOD("Update", NULL, Heap_Update, "(Old)v(New)v"),
|
|
|
|
GB_PROPERTY("First", "v", Heap_First),
|
|
|
|
GB_PROPERTY_READ("Count", "i", Heap_Count),
|
|
GB_PROPERTY_READ("IsEmpty", "b", Heap_IsEmpty),
|
|
|
|
GB_END_DECLARE
|
|
};
|
|
|
|
#if 0
|
|
GB_DESC CPrioSet[] = {
|
|
GB_DECLARE("PrioSet", sizeof(CPRIOSET)),
|
|
|
|
GB_METHOD("_new", NULL, PrioSet_new, ""),
|
|
GB_METHOD("_free", NULL, PrioSet_free, NULL),
|
|
|
|
GB_METHOD("Insert", NULL),
|
|
GB_METHOD
|
|
};
|
|
#endif
|