Skip to content

TectonicFury/Generic-C-DataStructures

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

73 Commits
 
 
 
 
 
 

Repository files navigation

Generic Data Structures in C (using macros)

The data structures in this repository are a C port of Sedgewick's recursive implementations in Java. Currently, it has support for basic data structures like Sequential Search symbol tables (ie. Linked Lists), unbalanced BSTs, Red-Black BSTs, Sets, and Hash Tables with Separate Chaining. In addition to these, it also has code for processing graphs and digraphs.

To make use of a data structure, simply include the appropriate header file and initialise it by passing required arguments for types and comparison functions.

For working examples, check the test files. As a rough (possibly broken) example, here is what a macro initialisation for Red-Black tree having int as both key and value type looks like (pay attention to the naming convention for the comparison functions):

// this example *might* be broken, check the test files for working programs
#include <stdio.h>
#include "rbt.h" // for instantiating red-black trees

/* less_int_ takes two arguments a and b and returns 1 if a < b and 0 otherwise */
/* more_int_ takes a and b and returns 1 if a is greater than b and 0 otherwise. */
/*This convention has to hold for comparison functions for keys of any type, primitive or not */

// comparison functions must have an _ at the end of their names
int less_int_(int a, int b) {
  if (a < b) return 1;
  return 0;
}
int more_int_(int a, int b) {
  if (a > b) return 1;
  return 0;
}

// when passing the name of the comparison function, omit the trailing underscore.
// the "less" function name (minus the trailing underscore) goes first

RedBlackTree(int, int, less_int, more_int) // macro initialisation

The above macro initialisation defines the following types:

Type Description
int_int_rbt the main RBT node type (pasting KEY = int and VALUE = int in KEY ## _ ## VALUE ## _rbt)
int_int_pair the underlying 'value_type' inside an RBT node which holds the Key and Value pair
List_int_int_pair a list type for iterating inorder over the elements of the tree. Each list node contains a int_int_pair

The following functions are also defined:

Function Description
insert_int_int_rbt(int_int_rbt, int, int) Takes the root node of the tree (of type int_int_rbt) and the key and value to insert. Return type is int_int_rbt
size_int_int_rbt(int_int_rbt) Takes the root and returns the size of the tree (or the size of the subtree rooted at the node). Return type is int
min_int_int_rbt(int_int_rbt) Returns the key-value pair with the smallest key in the tree (or subtree). Return type is int_int_pair
max_int_int_rbt(int_int_rbt) Returns the key-value pair with greatest key in tree (or subtree). Return type is int_int_pair
recursive_inorder_int_int_rbt(int_int_rbt) Returns an inorder list of elements in the tree. Return type is List_int_int_pair
delete_min_int_int_rbt(int_int_rbt, void (*destruct) (int, int)) Deletes the minimum taking the root and destruct function pointer as argument. Return type is int_int_rbt
delete_max_int_int_rbt(int_int_rbt, void (*destruct) (int, int)) deletes the maximum taking the root and destruct function pointer as argument. Return type is int_int_rbt
delete_int_int_rbt(int_int_rbt, int, void (*destruct) (int, int)) Takes the key to delete in addition to the root and destruct function pointer. Return type is int_int_rbt
deep_destroy_List_int_int_rbt(List_int_int_pair) Frees memory used for inorder traversal
free_whole_rbt_int_int_(int_int_rbt) Cleans up the RBT

For the delete functions, you need a destruct function appropriate for your key and value types to ensure that there are no memory leaks. For freeing a value_type of type int_int_pair, you would have a destruct function like so:

void destruct_int_int_pair(int_int_pair int_pair) {

  // call appropriate destructors for your key and value here

  // then free the value_type pair struct   
  if (int_pair) { // if already freed, we don't want to double free
    free(int_pair);
  }
}

If the types have a '*' in them, typedef them to 'remove' any *. eg. if key is char*, then do:

typedef char *my_string;
RedBlackTree(my_string, int, some_less, some_more)

Sample program

// sample_rbt.c
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <assert.h>
#include "rbt.h"
int less_int_(int a, int b) {
  if (a < b) return 1;
  return 0;
}
int more_int_(int a, int b) {
  if (a > b) return 1;
  return 0;
}
RedBlackTree(int, int, less_int, more_int)

void destruct_int_int_pair(int_int_pair int_pair) {
  // basic freeing, no recursive freeing for primitive data members (here ints)
  if (int_pair) {
    // if already freed, we don't want to double free
    free(int_pair);
  }
}
int main(int argc, char const *argv[]) {
  int_int_rbt rbt_root = NULL;
  int j;
  int size_rbt;

  srand(time(NULL));

  // testing insert
  printf("Inserting random key-value pairs\n");
  for (size_t i = 0; i < 50000; i++) {
    j = rand() % 100000;
    // inserting random keys and the values (their squares)
    // you have to put the root on LHS for the effect to take place
    rbt_root = insert_int_int_rbt(rbt_root, j, j * j);
  }
  size_rbt = size_int_int_rbt(rbt_root);
  printf("Size after insertions = %d\n", size_rbt);

  // delete the minimum

  rbt_root = delete_min_int_int_rbt(rbt_root, destruct_int_int_pair);

  // delete the maximum
  rbt_root = delete_max_int_int_rbt(rbt_root, destruct_int_int_pair);

  // inorder list
  List_int_int_pair list0 = recursive_inorder_int_int_rbt(rbt_root);
  List_int_int_pair list0_ref = list0; // save a reference to list head in order to free it later

  // printing inorder
  size_rbt = size_int_int_rbt(rbt_root);
  for (size_t i = 0; i < size_rbt; i++) {
    printf("   : key = %d, value = %d\n", list0->entry->key, list0->entry->value);
    list0 = list0->next;
  }

  // clean up
  deep_destroy_List_int_int_rbt(list0, destruct_int_int_pair); // memory leak: list0 points to end of list because of list->next,
  // below line actually frees the memory since it uses reference to first node in list
  deep_destroy_List_int_int_rbt(list0_ref, destruct_int_int_pair); //  cleans up the list as well as the underlying pair struct

  free_whole_rbt_int_int_(&rbt_root, destruct_int_int_pair); // free the whole RBT

  return 0;
}

Compiling

clang -o sample_rbt sample_rbt.c -std=c11

More examples

Read the test files for more examples. Currently BST implementation is having memory bugs.