├── .gitignore ├── Makefile ├── README.markdown ├── heap.c ├── heap.h └── main.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *~ 3 | *.swp 4 | *.swo 5 | test 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | CC_OPT=-Wall -std=gnu9x -c -g -O2 3 | DEPS=main.o heap.o 4 | PRGM=test 5 | 6 | MAIN: $(DEPS) 7 | $(CC) $(DEPS) -o $(PRGM) 8 | 9 | %.o : %.c 10 | $(CC) $(CC_OPT) $< 11 | 12 | clean: 13 | rm *.o $(PRGM) 14 | 15 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | This is a very simple library for implementing a Min-Heap or Priority Queue 4 | in plain C. It should be very understandable, and is a useful reference for 5 | people who are learning C or want to understand the binary heap data structure. 6 | 7 | What is somewhat interesting about this implementation is that it uses 8 | an indirection table instead of a plain array as most implementations do. 9 | The implications of this are that resizing (growing or shrinking) are very cheap. 10 | 11 | In fact, we make use of the low level mmap() utility to map in and out individual 12 | pages of virtual memory, and avoid the overhead of using malloc(). We have a small 13 | table (a few pages maximum), which serves as an index into the other pages we have 14 | allocated, and jump through this to get to the actual data. Because of this, growing 15 | the table requires simply mapping in a new page and adding it to the table, and 16 | shrinking requires only that we unmap the page. If this does not make sense, I highly 17 | recommend reading about virtual memory to get an understand of how memory really 18 | works in modern operating systems. 19 | 20 | A typical implementation would use a simple array, which when it runs out of space 21 | "doubles" in some sense. There are various approaches, not necessarily a simple double, 22 | thinks like the next Fibonacci number, next prime, etc. 23 | 24 | # Using the library 25 | 26 | To use the library you only need to include the heap.h header, and 27 | link against the heap.c file. There is nothing fancy required. 28 | 29 | # Testing the library 30 | 31 | Included in the project is a file main.c which serves as a simple test file. 32 | We randomly generate a large number of keys (variable, default to 10M), and 33 | insert them into our heap. We then extract the keys and verify they are ordered. 34 | To test this, just run `make` and then run the test program. 35 | 36 | # License 37 | 38 | MIT 39 | 40 | 41 | -------------------------------------------------------------------------------- /heap.c: -------------------------------------------------------------------------------- 1 | /** 2 | * This file defines the methods declared in heap.h 3 | * These are used to create and manipulate a heap 4 | * data structure. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "heap.h" 12 | 13 | // Helpful Macro's 14 | #define LEFT_CHILD(i) ((i<<1)+1) 15 | #define RIGHT_CHILD(i) ((i<<1)+2) 16 | #define PARENT_ENTRY(i) ((i-1)>>1) 17 | #define SWAP_ENTRIES(parent,child) { \ 18 | void* temp = parent->key; \ 19 | parent->key = child->key; \ 20 | child->key = temp; \ 21 | temp = parent->value; \ 22 | parent->value = child->value; \ 23 | child->value = temp; \ 24 | } 25 | 26 | #define GET_ENTRY(index,map_table) (((heap_entry*)*(map_table+index/ENTRIES_PER_PAGE))+(index % ENTRIES_PER_PAGE)) 27 | 28 | 29 | 30 | 31 | /** 32 | * Stores the number of heap_entry structures 33 | * we can fit into a single page of memory. 34 | * 35 | * This is determined by the page size, so we 36 | * need to determine this at run time. 37 | */ 38 | static int ENTRIES_PER_PAGE = 0; 39 | 40 | /** 41 | * Stores the number of bytes in a single 42 | * page of memory. 43 | */ 44 | static int PAGE_SIZE = 0; 45 | 46 | // Helper function to map a number of pages into memory 47 | // Returns NULL on error, otherwise returns a pointer to the 48 | // first page. 49 | static void* map_in_pages(int page_count) { 50 | // Check everything 51 | assert(page_count > 0); 52 | 53 | // Call mmmap to get the pages 54 | void* addr = mmap(NULL, page_count*PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, -1, 0); 55 | 56 | if (addr == MAP_FAILED) 57 | return NULL; 58 | else { 59 | // Clear the memory 60 | bzero(addr,page_count*PAGE_SIZE); 61 | 62 | // Return the address 63 | return addr; 64 | } 65 | } 66 | 67 | 68 | // Helper function to map a number of pages out of memory 69 | static void map_out_pages(void* addr, int page_count) { 70 | // Check everything 71 | assert(addr != NULL); 72 | assert(page_count > 0); 73 | 74 | // Call munmap to get rid of the pages 75 | int result = munmap(addr, page_count*PAGE_SIZE); 76 | 77 | // The result should be 0 78 | assert(result == 0); 79 | } 80 | 81 | 82 | // This is a comparison function that treats keys as signed ints 83 | int compare_int_keys(register void* key1, register void* key2) { 84 | // Cast them as int* and read them in 85 | register int key1_v = *((int*)key1); 86 | register int key2_v = *((int*)key2); 87 | 88 | // Perform the comparison 89 | if (key1_v < key2_v) 90 | return -1; 91 | else if (key1_v == key2_v) 92 | return 0; 93 | else 94 | return 1; 95 | } 96 | 97 | 98 | // Creates a new heap 99 | void heap_create(heap* h, int initial_size, int (*comp_func)(void*,void*)) { 100 | // Check if we need to setup our globals 101 | if (PAGE_SIZE == 0) { 102 | // Get the page size 103 | PAGE_SIZE = getpagesize(); 104 | 105 | // Calculate the max entries 106 | ENTRIES_PER_PAGE = PAGE_SIZE / sizeof(heap_entry); 107 | } 108 | 109 | // Check that initial size is greater than 0, else set it to ENTRIES_PER_PAGE 110 | if (initial_size <= 0) 111 | initial_size = ENTRIES_PER_PAGE; 112 | 113 | // If the comp_func is null, treat the keys as signed ints 114 | if (comp_func == NULL) 115 | comp_func = compare_int_keys; 116 | 117 | 118 | // Store the compare function 119 | h->compare_func = comp_func; 120 | 121 | // Set active entries to 0 122 | h->active_entries = 0; 123 | 124 | 125 | // Determine how many pages of entries we need 126 | h->minimum_pages = initial_size / ENTRIES_PER_PAGE + ((initial_size % ENTRIES_PER_PAGE > 0) ? 1 : 0); 127 | 128 | // Determine how big the map table should be 129 | h->map_pages = sizeof(void*) * h->minimum_pages / PAGE_SIZE + 1; 130 | 131 | // Allocate the map table 132 | h->mapping_table = (void**)map_in_pages(h->map_pages); 133 | assert(h->mapping_table != NULL); 134 | 135 | 136 | // Allocate the entry pages 137 | void* addr = map_in_pages(h->minimum_pages); 138 | assert(addr != NULL); 139 | 140 | // Add these to the map table 141 | for (int i=0;iminimum_pages;i++) { 142 | *(h->mapping_table+i) = addr+(i*PAGE_SIZE); 143 | } 144 | 145 | // Set the allocated pages 146 | h->allocated_pages = h->minimum_pages; 147 | } 148 | 149 | 150 | // Cleanup a heap 151 | void heap_destroy(heap* h) { 152 | // Check that h is not null 153 | assert(h != NULL); 154 | 155 | // Un-map all the entry pages 156 | void** map_table = h->mapping_table; 157 | assert(map_table != NULL); 158 | 159 | for (int i=0; i < h->allocated_pages; i++) { 160 | map_out_pages(*(map_table+i),1); 161 | } 162 | 163 | // Map out the map table 164 | map_out_pages(map_table, h->map_pages); 165 | 166 | // Clear everything 167 | h->active_entries = 0; 168 | h->allocated_pages = 0; 169 | h->map_pages = 0; 170 | h->mapping_table = NULL; 171 | } 172 | 173 | 174 | // Gets the size of the heap 175 | int heap_size(heap* h) { 176 | // Return the active entries 177 | return h->active_entries; 178 | } 179 | 180 | 181 | /* In-line version is much faster 182 | // Fetches the address of a heap_entry 183 | static heap_entry* heap_get_entry(int index, void** map_table) { 184 | // Determine which page that index falls in 185 | int entry_page = index / ENTRIES_PER_PAGE; 186 | 187 | // Determine the offset into the page 188 | int page_offset = index % ENTRIES_PER_PAGE; 189 | 190 | // Get the address of the page 191 | heap_entry* page_address = (heap_entry*)*(map_table+entry_page); 192 | 193 | // Get the corrent entry 194 | return page_address+page_offset; 195 | } 196 | */ 197 | 198 | 199 | // Gets the minimum element 200 | int heap_min(heap* h, void** key, void** value) { 201 | // Check the number of elements, abort if 0 202 | if (h->active_entries == 0) 203 | return 0; 204 | 205 | // Get the 0th element 206 | heap_entry* root = GET_ENTRY(0, h->mapping_table); 207 | 208 | // Set the key and value 209 | *key = root->key; 210 | *value = root->value; 211 | 212 | // Success 213 | return 1; 214 | } 215 | 216 | 217 | // Insert a new element 218 | void heap_insert(heap *h, void* key, void* value) { 219 | // Check if this heap is not destoyed 220 | assert(h->mapping_table != NULL); 221 | 222 | // Check if we have room 223 | int max_entries = h->allocated_pages * ENTRIES_PER_PAGE; 224 | if (h->active_entries + 1 > max_entries) { 225 | // Get the number of map pages 226 | int map_pages = h->map_pages; 227 | 228 | // We need a new page, do we have room? 229 | int mapable_pages = map_pages * PAGE_SIZE / sizeof(void*); 230 | 231 | // Check if we need to grow the map table 232 | if (h->allocated_pages + 1 > mapable_pages) { 233 | // Allocate a new table, slightly bigger 234 | void *new_table = map_in_pages(map_pages + 1); 235 | 236 | // Get the old table 237 | void *old_table = (void*)h->mapping_table; 238 | 239 | // Copy the old entries to the new table 240 | memcpy(new_table, old_table, map_pages * PAGE_SIZE); 241 | 242 | // Delete the old table 243 | map_out_pages(old_table, map_pages); 244 | 245 | // Swap to the new table 246 | h->mapping_table = (void**)new_table; 247 | 248 | // Update the number of map pages 249 | h->map_pages = map_pages + 1; 250 | } 251 | 252 | // Allocate a new page 253 | void* addr = map_in_pages(1); 254 | 255 | // Add this to the map 256 | *(h->mapping_table+h->allocated_pages) = addr; 257 | 258 | // Update the number of allocated pages 259 | h->allocated_pages++; 260 | } 261 | 262 | // Store the comparison function 263 | int (*cmp_func)(void*,void*) = h->compare_func; 264 | 265 | // Store the map table address 266 | void** map_table = h->mapping_table; 267 | 268 | // Get the current index 269 | int current_index = h->active_entries; 270 | heap_entry* current = GET_ENTRY(current_index, map_table); 271 | 272 | // Loop variables 273 | int parent_index; 274 | heap_entry *parent; 275 | 276 | // While we can, keep swapping with our parent 277 | while (current_index > 0) { 278 | // Get the parent index 279 | parent_index = PARENT_ENTRY(current_index); 280 | 281 | // Get the parent entry 282 | parent = GET_ENTRY(parent_index, map_table); 283 | 284 | // Compare the keys, and swap if we need to 285 | if (cmp_func(key, parent->key) < 0) { 286 | // Move the parent down 287 | current->key = parent->key; 288 | current->value = parent->value; 289 | 290 | // Move our reference 291 | current_index = parent_index; 292 | current = parent; 293 | 294 | // We are done swapping 295 | } else 296 | break; 297 | } 298 | 299 | // Insert at the current index 300 | current->key = key; 301 | current->value = value; 302 | 303 | // Increase the number of active entries 304 | h->active_entries++; 305 | } 306 | 307 | 308 | // Deletes the minimum entry in the heap 309 | int heap_delmin(heap* h, void** key, void** value) { 310 | // Check there is a minimum 311 | if (h->active_entries == 0) 312 | return 0; 313 | 314 | // Load in the map table 315 | void** map_table = h->mapping_table; 316 | 317 | // Get the root element 318 | int current_index = 0; 319 | heap_entry* current = GET_ENTRY(current_index, map_table); 320 | 321 | // Store the outputs 322 | *key = current->key; 323 | *value = current->value; 324 | 325 | // Reduce the number of active entries 326 | h->active_entries--; 327 | 328 | // Get the active entries 329 | int entries = h->active_entries; 330 | 331 | // If there are any other nodes, we may need to move them up 332 | if (h->active_entries > 0) { 333 | // Move the last element to the root 334 | heap_entry* last = GET_ENTRY(entries,map_table); 335 | current->key = last->key; 336 | current->value = last->value; 337 | 338 | // Loop variables 339 | heap_entry* left_child; 340 | heap_entry* right_child; 341 | 342 | // Load the comparison function 343 | int (*cmp_func)(void*,void*) = h->compare_func; 344 | 345 | // Store the left index 346 | int left_child_index; 347 | 348 | while (left_child_index = LEFT_CHILD(current_index), left_child_index < entries) { 349 | // Load the left child 350 | left_child = GET_ENTRY(left_child_index, map_table); 351 | 352 | // We have a left + right child 353 | if (left_child_index+1 < entries) { 354 | // Load the right child 355 | right_child = GET_ENTRY((left_child_index+1), map_table); 356 | 357 | // Find the smaller child 358 | if (cmp_func(left_child->key, right_child->key) <= 0) { 359 | 360 | // Swap with the left if it is smaller 361 | if (cmp_func(current->key, left_child->key) == 1) { 362 | SWAP_ENTRIES(current,left_child); 363 | current_index = left_child_index; 364 | current = left_child; 365 | 366 | // Otherwise, the current is smaller 367 | } else 368 | break; 369 | 370 | // Right child is smaller 371 | } else { 372 | 373 | // Swap with the right if it is smaller 374 | if (cmp_func(current->key, right_child->key) == 1) { 375 | SWAP_ENTRIES(current,right_child); 376 | current_index = left_child_index+1; 377 | current = right_child; 378 | 379 | // Current is smaller 380 | } else 381 | break; 382 | 383 | } 384 | 385 | 386 | // We only have a left child, only do something if the left is smaller 387 | } else if (cmp_func(current->key, left_child->key) == 1) { 388 | SWAP_ENTRIES(current,left_child); 389 | current_index = left_child_index; 390 | current = left_child; 391 | 392 | // Done otherwise 393 | } else 394 | break; 395 | 396 | } 397 | } 398 | 399 | // Check if we should release a page of memory 400 | int used_pages = entries / ENTRIES_PER_PAGE + ((entries % ENTRIES_PER_PAGE > 0) ? 1 : 0); 401 | 402 | // Allow one empty page, but not two 403 | if (h->allocated_pages > used_pages + 1 && h->allocated_pages > h->minimum_pages) { 404 | // Get the address of the page to delete 405 | void* addr = *(map_table+h->allocated_pages-1); 406 | 407 | // Map out 408 | map_out_pages(addr, 1); 409 | 410 | // Decrement the allocated count 411 | h->allocated_pages--; 412 | } 413 | 414 | // Success 415 | return 1; 416 | } 417 | 418 | 419 | // Allows a user to iterate over all entries, e.g. to free() the memory 420 | void heap_foreach(heap* h, void (*func)(void*,void*)) { 421 | // Store the current index and max index 422 | int index = 0; 423 | int entries = h->active_entries; 424 | 425 | heap_entry* entry; 426 | void** map_table = h->mapping_table; 427 | 428 | for (;indexkey, entry->value); 434 | } 435 | } 436 | 437 | 438 | -------------------------------------------------------------------------------- /heap.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Armon Dadgar 3 | * 4 | * Header for the Heap functions and data definitions 5 | */ 6 | 7 | #ifndef HEAP_H 8 | #define HEAP_H 9 | 10 | // Structure for a single heap entry 11 | typedef struct heap_entry { 12 | void* key; // Key for this entry 13 | void* value; // Value for this entry 14 | } heap_entry; 15 | 16 | 17 | // Main struct for representing the heap 18 | typedef struct heap { 19 | int (*compare_func)(void*, void*); // The key comparison function to use 20 | int active_entries; // The number of entries in the heap 21 | int minimum_pages; // The minimum number of pages to maintain, based on the initial cap. 22 | int allocated_pages; // The number of pages in memory that are allocated 23 | int map_pages; // The number of pages used for the map table 24 | void** mapping_table; // Pointer to the table, which maps to the pages 25 | } heap; 26 | 27 | // Functions 28 | 29 | /** 30 | * Creates a new heap 31 | * @param h Pointer to a heap structure that is initialized 32 | * @param initial_size What should the initial size of the heap be. If <= 0, then it will be set to the minimum 33 | * permissable size, of 1 page (512 entries on 32bit system with 4K pages). 34 | * @param comp_func A pointer to a function that can be used to compare the keys. If NULL, it will be set 35 | * to a function which treats keys as signed ints. This function must take two keys, given as pointers and return an int. 36 | * It should return -1 if key 1 is smaller, 0 if they are equal, and 1 if key 2 is smaller. 37 | */ 38 | void heap_create(heap* h, int initial_size, int (*comp_func)(void*,void*)); 39 | 40 | /** 41 | * Returns the size of the heap 42 | * @param h Pointer to a heap structure 43 | * @return The number of entries in the heap. 44 | */ 45 | int heap_size(heap* h); 46 | 47 | /** 48 | * Inserts a new element into a heap. 49 | * @param h The heap to insert into 50 | * @param key The key of the new entry 51 | * @param value The value of the new entry 52 | */ 53 | void heap_insert(heap* h, void* key, void* value); 54 | 55 | /** 56 | * Returns the element with the smallest key in the heap. 57 | * @param h Pointer to the heap structure 58 | * @param key A pointer to a pointer, to set to the minimum key 59 | * @param value Set to the value corresponding with the key 60 | * @return 1 if the minimum element exists and is set, 0 if there are no elements. 61 | */ 62 | int heap_min(heap* h, void** key, void** value); 63 | 64 | /** 65 | * Deletes the element with the smallest key from the heap. 66 | * @param h Pointer to the heap structure 67 | * @param key A pointer to a pointer, to set to the minimum key 68 | * @param valu Set to the value corresponding with the key 69 | * @return 1if the minimum element exists and is deleted, 0 if there are no elements. 70 | */ 71 | int heap_delmin(heap* h, void** key, void** value); 72 | 73 | /** 74 | * Calls a function for each entry in the heap. 75 | * @param h The heap to iterate over 76 | * @param func The function to call on each entry. Should take a void* key and value. 77 | */ 78 | void heap_foreach(heap* h, void (*func)(void*,void*)); 79 | 80 | /** 81 | * Destroys and cleans up a heap. 82 | * @param h The heap to destroy. 83 | */ 84 | void heap_destroy(heap* h); 85 | 86 | #endif 87 | 88 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "heap.h" 8 | 9 | int main(int argc, char** argv) { 10 | 11 | // Create the heap 12 | heap h; 13 | heap_create(&h,0,NULL); 14 | 15 | // Maximum 16 | int count = 10000000; // 10M 17 | if (argc > 1) 18 | count = atoi(argv[1]); // Get the count as an argument 19 | printf("Sorting array of %d random entries.\n", count); 20 | 21 | // Allocate a key and value 22 | int* key = (int*)malloc(count*sizeof(int)); 23 | char* value = "The meaning of life."; 24 | 25 | // Initialize the first key 26 | unsigned int val = 42; 27 | srand( val); 28 | printf("Seed %d\n",val); 29 | 30 | // Store that as the minimum 31 | int min = INT_MAX; 32 | 33 | // Use a pseudo-random generator for the other keys 34 | for (int i=0;i *min_key) { 58 | printf("Previous key is greater than current key!\n"); 59 | } 60 | prev_key = min_key; 61 | } 62 | 63 | // Clean up the heap 64 | heap_destroy(&h); 65 | } 66 | 67 | --------------------------------------------------------------------------------