├── LICENSE ├── README.md ├── lib ├── ADT.h ├── ADTlib.a └── makefile ├── modules ├── BloomFilter │ ├── README.md │ ├── bloom_filter.c │ └── bloom_filter.h ├── Graph │ ├── DirectedGraph │ │ ├── DirectedGraph.c │ │ ├── DirectedGraph.h │ │ └── README.md │ ├── README.md │ ├── UndirectedGraph │ │ ├── README.md │ │ ├── UndirectedGraph.c │ │ └── UndirectedGraph.h │ └── WeightedUndirectedGraph │ │ ├── README.md │ │ ├── WeightedUndirectedGraph.c │ │ └── WeightedUndirectedGraph.h ├── HashTable │ ├── DoubleHashing │ │ ├── README.md │ │ ├── hash_table.c │ │ └── hash_table.h │ ├── README.md │ ├── SeparateChaining │ │ ├── README.md │ │ ├── hash_table.c │ │ └── hash_table.h │ ├── UsingRBT │ │ ├── README.md │ │ ├── hash_table.c │ │ └── hash_table.h │ ├── hash_functions.c │ └── hash_functions.h ├── PriorityQueue │ ├── README.md │ ├── pq.c │ └── pq.h ├── Queue │ ├── README.md │ ├── queue.c │ └── queue.h ├── RedBlackTree │ ├── README.md │ ├── RedBlackTree.c │ └── RedBlackTree.h ├── Stack │ ├── README.md │ ├── stack.c │ └── stack.h └── Vector │ ├── README.md │ ├── vector.c │ └── vector.h └── tests ├── include ├── acutest.h └── common.h ├── makefile ├── test_BloomFilter.c ├── test_DirectedGraph.c ├── test_HashTable.c ├── test_PriorityQueue.c ├── test_Queue.c ├── test_RedBlackTree.c ├── test_Stack.c ├── test_UndirectedGraph.c ├── test_Vector.c └── test_WeightedUndirectedGraph.c /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Pavlos Dais 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Abstract Data Types in C 2 | This is a modern C library containing implementations designed to provide fast and efficient data structure operations for a wide range of applications while also being educational, having a relatively non-challenging code to follow. All ADTs have been implemented using [void pointers](https://www.geeksforgeeks.org/void-pointer-c-cpp/) to make them generic, allowing the same code to handle different data types (eg integers, strings, structs, etc). The library contains the following ADTs: 3 | 4 | * [Vector](https://github.com/pavlosdais/Abstract-Data-Types/tree/main/modules/Vector#readme) 5 | * [Stack](https://github.com/pavlosdais/Abstract-Data-Types/tree/main/modules/Stack#readme) 6 | * [Queue](https://github.com/pavlosdais/Abstract-Data-Types/tree/main/modules/Queue#readme) 7 | * [Priority Queue](https://github.com/pavlosdais/Abstract-Data-Types/tree/main/modules/PriorityQueue#readme) 8 | * [Red-Black Tree](https://github.com/pavlosdais/Abstract-Data-Types/tree/main/modules/RedBlackTree#readme) 9 | * [Hash Table](https://github.com/pavlosdais/Abstract-Data-Types/tree/main/modules/HashTable#readme) 10 | * [Bloom Filter](https://github.com/pavlosdais/Abstract-Data-Types/tree/main/modules/BloomFilter#readme) 11 | * [Graph](https://github.com/pavlosdais/Abstract-Data-Types/tree/main/modules/Graph#readme) 12 | 13 | The source code of every ADT can be found over at the [modules](https://github.com/pavlosdais/Abstract-Data-Types/tree/main/modules) directory. 14 | 15 | # Usage 16 | The library can be used in your C project by following 3 (simple) steps. 17 | 18 | ### Step 1 19 | Include the header file of the ADT library 20 | ```c 21 | #include "lib/ADT.h" 22 | ``` 23 | *or* just include the desired header file(s) of the data structure(s) you want to use. 24 | ```c 25 | #include "vector.h" 26 | #include "RedBlackTree.h" 27 | #include "stack.h" 28 | #include "pq.h" 29 | #include "hash_table.h" 30 | ``` 31 | 32 | ### Step 2 33 | Depending on the ADT, provide a number of the following functions upon its creation: 34 | - **[Destroy function](https://github.com/pavlosdais/Abstract-Data-Types/blob/main/tests/test_HashTable.c#L9)**
35 | Destroys the data allocated for the elements. 36 | 37 | - **[Compare function](https://github.com/pavlosdais/Abstract-Data-Types/blob/main/tests/include/common.h#L69)**
38 | Compares two Pointers a and b, and returns < 0 if a < b, 0 if a = b or > 0 if a > b. 39 | 40 | - **[Hash function](https://github.com/pavlosdais/Abstract-Data-Types/blob/main/modules/HashTable/hash_functions.c)**
41 | Takes a Pointer and returns a unsigned integer (Used by the hash table). 42 | 43 | - **[Visit function](https://github.com/pavlosdais/Abstract-Data-Types/blob/main/tests/test_DirectedGraph.c#L44)**
44 | Visits a vertex (Used by the graph). 45 | 46 | ### Step 3 47 | Use `ADTlib.a` on compilation. 48 | ```bash 49 | ~$ gcc -o my_prog_exec my_prog.c -L. lib/ADTlib.a 50 | ``` 51 | 52 | # Tests 53 | The library comes with a comprehensive suite of tests covering the ADT's, using the [acutest](https://github.com/mity/acutest) library. The tests are located in the [tests](https://github.com/pavlosdais/Abstract-Data-Types/tree/main/tests) directory, and are designed to ensure that the library functions as expected. In order to test a certain ADT, navigate to the directory and simply execute: 54 | ```bash 55 | ~$ make run ADT= 56 | ``` 57 | -------------------------------------------------------------------------------- /lib/ADT.h: -------------------------------------------------------------------------------- 1 | #pragma once // include at most once 2 | 3 | #include 4 | #include 5 | 6 | 7 | /**********************************\ 8 | ================================== 9 | DATA STRUCTURE 10 | TYPEDEFS 11 | \**********************************/ 12 | 13 | // generic pointer 14 | typedef void* Pointer; 15 | 16 | // Pointer to function that compares 2 elements a and b and returns: 17 | // < 0 if a < b 18 | // 0 if a and b are equal 19 | // > 0 if a > b 20 | typedef int (*CompareFunc)(Pointer a, Pointer b); 21 | 22 | // Pointer to function that destroys an element value 23 | typedef void (*DestroyFunc)(Pointer value); 24 | 25 | // Pointer to function that hashes a value to a positive integer - needed only by the hash table 26 | typedef unsigned int (*HashFunc)(Pointer value); 27 | 28 | 29 | // Graph typedefs 30 | typedef uint32_t Vertex; 31 | typedef uint32_t cost; // weighted graph's edge cost 32 | 33 | // Pointer to function that visits the vertices 34 | typedef void (*VisitFunc)(Vertex V); 35 | 36 | 37 | 38 | 39 | /**********************************\ 40 | ================================== 41 | DATA STRUCTURE 42 | HANDLES 43 | \**********************************/ 44 | 45 | typedef struct vector_struct* Vector; // vector (Vector) 46 | typedef struct StackSet* Stack; // stack (Stack) 47 | typedef struct queue* Queue; // queue (Queue) 48 | typedef struct pq* PQueue; // priority queue (PQueue) 49 | typedef struct Set* RBTree; // red-black tree (RBTree) 50 | typedef struct hash_table* HashTable; // hash table (HashTable) 51 | typedef struct bfilter* bloom_filter; // bloom filter (bloom_filter) 52 | typedef struct _dir_graph* dir_graph; // directed graph (dir_graph) 53 | typedef struct _undir_graph* undir_graph; // undirected graph (undir_graph) 54 | typedef struct _wu_graph* wu_graph; // weighted undirected (wu_graph) 55 | 56 | 57 | 58 | 59 | /**********************************\ 60 | ================================== 61 | DATA STRUCTURE 62 | UTILITIES 63 | \**********************************/ 64 | 65 | // VECTOR 66 | // -requires a destroy function 67 | Vector vector_create(const DestroyFunc); // creates vector 68 | void vector_push_back(const Vector, const Pointer); // inserts the element at the back of the vector 69 | Pointer vector_at(const Vector, const uint64_t); // returns the element at the given index 70 | void vector_set_at(const Vector, const uint64_t, const Pointer); // sets the value at the given index 71 | bool vector_clear_at(const Vector, const uint64_t); // clear item at given vertex 72 | uint64_t vector_size(const Vector); // returns vector's size 73 | bool is_vector_empty(const Vector); // returns true if the vector is empty, false otherwise 74 | bool vector_delete(const Vector, const Pointer, const CompareFunc); // searches for the element and removes it 75 | void vector_sort(const Vector, const CompareFunc); // sorts the vector using the compare function given 76 | bool vector_binary_search(const Vector, const Pointer, const CompareFunc); // searches the vector using binary search 77 | bool vector_search(const Vector, const Pointer, const CompareFunc); // searches the vector using linear search 78 | DestroyFunc vector_set_destroy(const Vector, const DestroyFunc); // changes the destroy function and returns the old one 79 | void vector_destroy(const Vector); // destroys the memory used by the vector 80 | 81 | 82 | // STACK 83 | // -requires a destroy function 84 | Stack stack_create(const DestroyFunc); // creates stack 85 | void stack_push(const Stack, const Pointer); // pushes value at the top of the stack 86 | Pointer stack_pop(const Stack); // pops value from the top of the stack 87 | uint64_t stack_size(const Stack); // returns the size of the stack 88 | bool is_stack_empty(const Stack); // returns true if the stack is empty, false otherwise 89 | Pointer stack_top_value(const Stack); // returns the value at the top of the stack 90 | DestroyFunc stack_set_destroy(const Stack, const DestroyFunc); // changes the destroy function and returns the old one 91 | void stack_destroy(const Stack); // destroys the memory used by the stack 92 | 93 | 94 | // QUEUE 95 | // -requires a destroy function 96 | Queue queue_create(const DestroyFunc); // creates queue 97 | void queue_enqueue(const Queue, const Pointer); // enqueues value at the end of the queue 98 | void queue_sorted_insert(const Queue, const Pointer, const CompareFunc); // sorted insert of value 99 | Pointer queue_dequeue(const Queue); // dequeues value from the start of the queue 100 | uint64_t queue_size(const Queue); // returns the size of the queue 101 | bool is_queue_empty(const Queue); // returns true if the queue is empty, false otherwise 102 | DestroyFunc queue_set_destroy(const Queue, const DestroyFunc); // changes the destroy function and returns the old one 103 | void queue_destroy(const Queue); // destroys the memory used by the queue 104 | 105 | 106 | // PRIORITY QUEUE 107 | // -requires a compare and destroy function 108 | PQueue pq_create(const CompareFunc, const DestroyFunc); // creates priority queue 109 | void pq_insert(const PQueue, const Pointer); // inserts value at the priority queue 110 | Pointer pq_remove(const PQueue); // returns the element with the highest priority 111 | uint64_t pq_size(const PQueue); // returns the size of the priority queue 112 | bool is_pq_empty(const PQueue); // returns true if the priority queue is empty, false otherwise 113 | Pointer pq_peek(const PQueue); // returns the element with the highest priority without removing it 114 | DestroyFunc pq_set_destroy(const PQueue, const DestroyFunc); // changes the destroy function and returns the old one 115 | void pq_destroy(const PQueue); // destroys memory used by the priority queue 116 | 117 | 118 | // RED-BLACK TREE 119 | // -requires a compare and destroy function 120 | typedef struct tnode* RBTreeNode; // rbt node handle 121 | RBTree rbt_create(const CompareFunc, const DestroyFunc); // creates red-black tree 122 | bool rbt_insert(const RBTree, const Pointer); // insert the item 123 | bool rbt_remove(const RBTree, const Pointer); // remove the item 124 | bool rbt_exists(const RBTree, const Pointer); // returns true if the value exists, false otherwise 125 | uint64_t rbt_size(const RBTree); // returns the size of the tree 126 | bool is_rbt_empty(const RBTree); // returns true if the tree is empty, false otherwise 127 | DestroyFunc rbt_set_destroy(const RBTree, const DestroyFunc); // changes the destroy function and returns the old one 128 | void rbt_destroy(const RBTree); // destroys the memory used by the tree 129 | Pointer rbt_node_value(const RBTreeNode); // returns the value of the node 130 | RBTreeNode rbt_find_node(const RBTree, const Pointer); // returns the node with that value, if it exists, otherwise NULL 131 | RBTreeNode rbt_find_previous(const RBTreeNode); // returns the previous, in order, node of target 132 | RBTreeNode rbt_find_next(const RBTreeNode); // returns the next, in order, node of target 133 | RBTreeNode rbt_first(const RBTree); // returns the node with the lowest value 134 | RBTreeNode rbt_last(const RBTree); // returns the node with the highest value 135 | 136 | 137 | // HASH TABLE 138 | // -requires a hash, compare and destroy function 139 | HashTable hash_create(const HashFunc, const CompareFunc, const DestroyFunc); // creates hash table 140 | bool hash_insert(const HashTable, const Pointer); // inserts value at the hash table 141 | bool hash_remove(const HashTable, const Pointer); // removes the value from the hash table 142 | bool hash_exists(const HashTable, const Pointer); // returns true if value exists in the hash table 143 | uint64_t hash_size(const HashTable); // returns the number of elements in the hash table 144 | bool is_ht_empty(const HashTable); // returns true if the hash table is empty, false otherwise 145 | DestroyFunc hash_set_destroy(const HashTable, const DestroyFunc); // changes the destroy function and returns the old one 146 | void hash_destroy(const HashTable); // destroys the memory used by the hash table 147 | 148 | // provided hash functions 149 | unsigned int hash_int1(Pointer); // hashes an integer (1) 150 | unsigned int hash_int2(Pointer); // hashes an integer (2) 151 | unsigned int hash_int3(Pointer); // hashes an integer (3) 152 | unsigned int hash_string1(Pointer); // hashes a string (1) 153 | unsigned int hash_string2(Pointer); // hashes a string (2) 154 | unsigned int hash_string3(Pointer); // hashes a string (3) 155 | 156 | 157 | // BLOOM FILTER 158 | // -requires an array of hash functions 159 | bloom_filter bf_create(const uint32_t, HashFunc*, const uint8_t); // creates bloom filter (number of elements, hash functions, number of hash functions) 160 | void bf_insert(const bloom_filter, const Pointer); // inserts value at the bloom filter 161 | bool bf_exists(const bloom_filter, const Pointer); // returns true if value exists (possibly falsely) in the bloom filter, false otherwise 162 | void bf_destroy(bloom_filter); // destroys the memory used by the bloom filter 163 | 164 | 165 | // DIRECTED GRAPH 166 | // -visit function required 167 | dir_graph dg_create(const uint32_t, const VisitFunc); // creates directed graph 168 | void dg_insert(const dir_graph, const Vertex A, const Vertex B); // inserts edge (A-B) at the graph 169 | void dg_print(const dir_graph); // prints the graph 170 | void dg_dfs(const dir_graph); // depth first traversal of the graph 171 | dir_graph dg_reverse(const dir_graph); // returns reversed graph 172 | void dg_bts(const dir_graph); // prints a topological ordering of the graph 173 | void dg_scc(const dir_graph); // prints strongly-connected components 174 | void dg_destroy(const dir_graph); // destroys the memory used by the directed graph 175 | 176 | 177 | // UNDIRECTED GRAPH 178 | // -visit function required 179 | undir_graph ug_create(const uint32_t, const VisitFunc); // creates undirected graph 180 | void ug_insert(undir_graph, Vertex A, Vertex B); // inserts edge (A-B) at the graph 181 | void ug_print(const undir_graph); // prints the graph 182 | void ug_simplepathcheck(const undir_graph, const Vertex S, const Vertex G); // prints a "simple" path between vertices S and G, if such path exists 183 | void ug_destroy(const undir_graph); // destroys the memory used by the undirected graph 184 | 185 | 186 | // WEIGHTED UNDIRECTED GRAPH 187 | // -no functions required 188 | wu_graph wug_create(const uint32_t); // creates weighted undirected graph 189 | void wug_insert(const wu_graph, const Vertex A, const Vertex B, const cost); // inserts edge (A-B) with its weight at the graph 190 | void wug_print(const wu_graph); // prints the graph 191 | void wug_minspantree(const wu_graph); // prints minimum spanning tree, as well as its total weight 192 | void wug_destroy(const wu_graph); // destroys the memory used by the weighted undirected graph 193 | -------------------------------------------------------------------------------- /lib/ADTlib.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pavlosdais/Abstract-Data-Types/399c7169cbcd3d795c7df7119835ce404abbe05a/lib/ADTlib.a -------------------------------------------------------------------------------- /lib/makefile: -------------------------------------------------------------------------------- 1 | # compile the library 2 | 3 | # path to the modules directory 4 | ADTs = ../modules 5 | 6 | # implementation of the hash table (SeparateChaining/ DoubleHashing/ UsingRBT) 7 | HT_IMPLEMENTATION = SeparateChaining 8 | 9 | # object files - modules 10 | OBJ = $(ADTs)/Vector/vector.o \ 11 | $(ADTs)/Stack/stack.o \ 12 | $(ADTs)/Queue/queue.o \ 13 | $(ADTs)/PriorityQueue/pq.o \ 14 | $(ADTs)/RedBlackTree/RedBlackTree.o \ 15 | $(ADTs)/HashTable/$(HT_IMPLEMENTATION)/hash_table.o \ 16 | $(ADTs)/HashTable/hash_functions.o \ 17 | $(ADTs)/BloomFilter/bloom_filter.o \ 18 | $(ADTs)/Graph/DirectedGraph/DirectedGraph.o \ 19 | $(ADTs)/Graph/UndirectedGraph/UndirectedGraph.o \ 20 | $(ADTs)/Graph/WeightedUndirectedGraph/WeightedUndirectedGraph.o 21 | 22 | # library 23 | LIB = ADTlib.a 24 | 25 | # compiler 26 | CC = gcc 27 | 28 | # create library 29 | lib: $(OBJ) 30 | ar rcs $(LIB) $(OBJ) 31 | rm -f $(OBJ) 32 | 33 | # delete library 34 | clear: 35 | rm -f $(LIB) 36 | -------------------------------------------------------------------------------- /modules/BloomFilter/README.md: -------------------------------------------------------------------------------- 1 | A [Bloom filter](https://en.wikipedia.org/wiki/Bloom_filter) is a data structure that relies on hashing and operates with a degree of probability. It offers exceptional space efficiency and is commonly employed for adding elements to a set and checking if an element belongs to the set. However, the actual elements are not directly included in the set; rather, a hash of each element is added. While false positive matches can occur, false negatives are eliminated, meaning a query outcome is either "possibly in set" or "definitely not in set." As the number of items added increases, the likelihood of false positives also grows 2 | 3 | # Performance 4 | bloom filter picture 5 | 6 | If n is the number of elements in the bloom filter: 7 | 8 | Algorithm | Average case | Worst case 9 | ---------- | ------- | ---------- 10 | Space | Θ(n) | O(n) 11 | Insert | Θ(1) | O(1) 12 | Search | Θ(1) | O(1) 13 | -------------------------------------------------------------------------------- /modules/BloomFilter/bloom_filter.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "bloom_filter.h" 5 | 6 | typedef struct bfilter 7 | { 8 | uint8_t* bit_array; // bit array 9 | uint32_t size; // size of the bit array 10 | HashFunc* hash; // array of hash functions 11 | uint8_t hash_count; // number of hash functions 12 | } 13 | bfilter; 14 | 15 | #define create_mask(x) (1 << x) 16 | #define get_index(x) (x / sizeof(uint8_t)) 17 | #define get_bit(x) (x % sizeof(uint8_t)) 18 | 19 | bloom_filter bf_create(const uint32_t max_capacity, HashFunc* hash, const uint8_t hash_count) 20 | { 21 | assert(hash != NULL); 22 | 23 | bloom_filter bf = malloc(sizeof(bfilter)); 24 | assert(bf != NULL); 25 | 26 | bf->size = hash_count*max_capacity; // size of the bit array 27 | 28 | bf->bit_array = calloc(sizeof(uint8_t), bf->size/sizeof(uint8_t)+1); 29 | assert(bf->bit_array != NULL); 30 | 31 | bf->hash_count = hash_count; 32 | bf->hash = hash; 33 | 34 | return bf; 35 | } 36 | 37 | static inline void set_bit(bloom_filter bf, const uint32_t hash_value) 38 | { 39 | bf->bit_array[get_index(hash_value)] |= create_mask(get_bit(hash_value)); 40 | } 41 | 42 | static inline bool bit_exists(bloom_filter bf, const uint32_t hash_value) 43 | { 44 | return bf->bit_array[get_index(hash_value)] & create_mask(get_bit(hash_value)) != 0; 45 | } 46 | 47 | void bf_insert(const bloom_filter bf, const Pointer value) 48 | { 49 | assert(bf != NULL && value != NULL); 50 | 51 | // for every hash function, hash the value and set the bit 52 | for (uint8_t i = 0; i < bf->hash_count; i++) 53 | set_bit(bf, bf->hash[i](value) % bf->size); 54 | } 55 | 56 | bool bf_exists(const bloom_filter bf, const Pointer value) 57 | { 58 | assert(bf != NULL && value != NULL); 59 | 60 | // hash the value and check if the bit is set 61 | for (uint8_t i = 0; i < bf->hash_count; i++) 62 | { 63 | if (!bit_exists(bf, bf->hash[i](value) % bf->size)) 64 | return false; 65 | } 66 | 67 | return true; 68 | } 69 | 70 | void bf_destroy(bloom_filter bf) 71 | { 72 | assert(bf != NULL); 73 | 74 | free(bf->bit_array); 75 | free(bf); 76 | } 77 | -------------------------------------------------------------------------------- /modules/BloomFilter/bloom_filter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | typedef void* Pointer; 7 | 8 | // Pointer to function that hashes a value to a positive (unsigned) integer 9 | typedef unsigned int (*HashFunc)(Pointer value); 10 | 11 | typedef struct bfilter* bloom_filter; 12 | 13 | // creates bloom filter 14 | // -requires an array of hash functions 15 | bloom_filter bf_create(const uint32_t, HashFunc*, const uint8_t); 16 | 17 | // inserts value at the bloom filter 18 | void bf_insert(const bloom_filter, const Pointer); 19 | 20 | // returns true if value exists in the bloom filter, false otherwise 21 | // false positives are possible, but false negatives are not 22 | bool bf_exists(const bloom_filter, const Pointer); 23 | 24 | // destroys the memory used by the bloom filter 25 | void bf_destroy(bloom_filter); 26 | -------------------------------------------------------------------------------- /modules/Graph/DirectedGraph/DirectedGraph.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "DirectedGraph.h" 6 | #include "../../Queue/queue.h" 7 | #include "../../Stack/stack.h" 8 | 9 | typedef int* Toporder; 10 | 11 | typedef struct edge 12 | { 13 | Vertex endpoint; 14 | struct edge *nextedge; 15 | } 16 | edge; 17 | typedef struct edge* Edge; 18 | 19 | typedef struct _dir_graph 20 | { 21 | uint32_t n; // number of vertices in the graph 22 | Edge* firstedge; // adjacency list representation 23 | VisitFunc visit; // function that visits the vertices 24 | } 25 | _dir_graph; 26 | 27 | // function prototype 28 | static Vertex* createVertex(Vertex V); 29 | 30 | dir_graph dg_create(const uint32_t num_of_vertices, const VisitFunc visit) 31 | { 32 | assert(visit != NULL); // a visit function needs to be given 33 | 34 | dir_graph G = malloc(sizeof(_dir_graph)); 35 | assert(G != NULL); 36 | 37 | G->firstedge = calloc(sizeof(Edge), num_of_vertices); 38 | assert(G->firstedge != NULL); // allocation failure 39 | 40 | G->n = num_of_vertices; 41 | G->visit = visit; 42 | 43 | return G; 44 | } 45 | 46 | void dg_insert(const dir_graph G, const Vertex A, const Vertex B) 47 | { 48 | // sorted insert of vertex B at list A - O(n) 49 | Edge* al = &(G->firstedge[A]); 50 | 51 | Edge new_edge = malloc(sizeof(edge)); 52 | assert(new_edge != NULL); // allocation failure 53 | 54 | new_edge->endpoint = B; 55 | 56 | if (*al == NULL) 57 | { 58 | new_edge->nextedge = NULL; 59 | *al = new_edge; 60 | return; 61 | } 62 | 63 | // the head of the list has less priority than the new node, meaning the new vertex should be first 64 | if ((*al)->endpoint > B) 65 | { 66 | new_edge->nextedge = (*al); 67 | (*al) = new_edge; 68 | return; 69 | } 70 | 71 | // in any other case, traverse the list and find the correct position to insert the vertex 72 | Edge tmp = *al; 73 | while (tmp->nextedge != NULL && tmp->nextedge->endpoint < B) tmp = tmp->nextedge; 74 | 75 | // put the vertex at its place 76 | new_edge->nextedge = tmp->nextedge; 77 | tmp->nextedge = new_edge; 78 | } 79 | 80 | void dg_print(const dir_graph G) 81 | { 82 | assert(G != NULL); 83 | 84 | for (uint32_t i = 0; i < G->n; i++) 85 | { 86 | Edge a = G->firstedge[i]; 87 | 88 | printf("[%d]", i); 89 | if (a != NULL) 90 | printf(": "); 91 | while (a != NULL) 92 | { 93 | G->visit(a->endpoint); 94 | a = a->nextedge; 95 | } 96 | printf("\n"); 97 | } 98 | } 99 | 100 | // Helper function prototypes 101 | static void Traverse(const dir_graph, const Vertex, bool*, const int*, const int*); 102 | static void Explore(const dir_graph, const Vertex, bool*, int*, int*, int*, int*); 103 | static void print_green(); 104 | static void print_black(); 105 | static void print_red(); 106 | static void reset_col(); 107 | 108 | void dg_dfs(const dir_graph G) 109 | { 110 | assert(G != NULL); 111 | 112 | Vertex v; 113 | bool* visited = calloc(sizeof(bool), G->n); 114 | assert(visited != NULL); // allocation failure 115 | 116 | int* pre = calloc(sizeof(int), G->n); 117 | assert(pre != NULL); // allocation failure 118 | 119 | int* post = calloc(sizeof(int), G->n); 120 | assert(post != NULL); // allocation failure 121 | 122 | // traverse the graph and store the preorder and postorder numberings 123 | // in arrays pre and post, respectively and use them for traverse in 124 | // order to classify the edges as back, cross or forward. 125 | int count1 = 0; 126 | int count2 = 0; 127 | 128 | for (v = 0; v < G->n; v++) 129 | if (!visited[v]) 130 | Explore(G, v, visited, pre, post, &count1, &count2); 131 | 132 | // reset visited array 133 | for (v = 0; v < G->n; v++) 134 | visited[v] = false; 135 | 136 | // dfs 137 | for (v = 0; v < G->n; v++) 138 | if (!visited[v]) 139 | Traverse(G, v, visited, pre, post); 140 | 141 | // free allocated memory 142 | free(post); 143 | free(pre); 144 | free(visited); 145 | } 146 | 147 | static void Traverse(const dir_graph G, const Vertex v, bool* visited, const int* pre, const int* post) 148 | { 149 | Vertex w; 150 | visited[v] = true; 151 | 152 | G->visit(v); printf("\n"); 153 | 154 | Edge curedge = G->firstedge[v]; // curedge is a pointer to the first edge (v,_) of V 155 | 156 | while (curedge != NULL) 157 | { 158 | w = curedge->endpoint; // w is a successor of v and (v,w) is the current edge 159 | 160 | if (!visited[w]) // it is a tree edge 161 | { 162 | Traverse(G, w, visited, pre, post); 163 | printf("Tree edge:"); 164 | } 165 | else // have already visited this vertex 166 | { 167 | if (post[v] < post[w]) // it leads to a vertex with a higher postorder number 168 | { 169 | print_green(); // print with green color back edges 170 | printf("Back edge:"); 171 | } 172 | else if (pre[v] > pre[w]) // it leads to a vertex with a lower preorder number 173 | { 174 | print_red(); // print with red color cross edges 175 | printf("Cross edge:"); 176 | } 177 | else if (pre[v] < pre[w]) // it leads to a vertex with a higher preorder number 178 | { 179 | print_black(); // print with black color forward edges 180 | printf("Forward edge:"); 181 | } 182 | } 183 | printf(" (%d,%d)\n", v, w); 184 | reset_col(); 185 | curedge = curedge->nextedge; // curedge is a pointer to the next edge (v,_) of V 186 | } 187 | } 188 | 189 | static void print_green() { printf("\033[0;32m"); } 190 | 191 | static void print_black() { printf("\033[0;30m"); } 192 | 193 | static void print_red() { printf("\033[0;31m"); } 194 | 195 | // reset the color 196 | static void reset_col() { printf("\033[0m"); } 197 | 198 | // explore is used to store the preorder and postorder numbering of the vertices 199 | static void Explore(const dir_graph G, const Vertex v, bool* visited, int* pre, int* post, int* counter1, int* counter2) 200 | { 201 | Vertex w; 202 | visited[v] = true; 203 | 204 | pre[v] = (*counter1)++; // preorder numbering 205 | 206 | Edge curedge = G->firstedge[v]; // curedge is a pointer to the first edge (v,_) of V 207 | 208 | while (curedge != NULL) 209 | { 210 | w = curedge->endpoint; // w is a successor of v and (v,w) is the current edge 211 | 212 | if (!visited[w]) 213 | Explore(G, w, visited, pre, post, counter1, counter2); 214 | 215 | post[v] = (*counter2)++; // postorder numbering 216 | curedge = curedge->nextedge; 217 | } 218 | } 219 | 220 | static void TopSort(const dir_graph, const Toporder); 221 | void dg_bts(const dir_graph G) 222 | { 223 | Toporder T = malloc(sizeof(*T) * G->n); 224 | for (uint32_t i = 0; i < G->n; i++) 225 | T[i] = -1; 226 | 227 | TopSort(G, T); 228 | 229 | // print the order 230 | for (uint32_t v = 0; v < G->n; v++) 231 | { 232 | if (T[v] == -1) 233 | break; 234 | G->visit(T[v]); 235 | } 236 | 237 | printf("\n"); 238 | free(T); 239 | } 240 | 241 | static void TopSort(const dir_graph G, const Toporder T) 242 | { 243 | int* predecessorcount = calloc(sizeof(int), G->n); /* number of predecessors of each vertex */ 244 | assert(predecessorcount != NULL); // allocation failure 245 | 246 | // increase the predecessor count for each vertex that is a successor of another one 247 | for (uint32_t v = 0; v < G->n; v++) 248 | for (Edge curedge=G->firstedge[v]; curedge != NULL; curedge=curedge->nextedge) 249 | ++predecessorcount[curedge->endpoint]; 250 | 251 | // initialize a queue 252 | Queue Q = queue_create(free); 253 | 254 | // place all vertices with no predecessors into the queue 255 | for (uint32_t v = 0; v < G->n; v++) 256 | if (predecessorcount[v] == 0) 257 | queue_enqueue(Q, createVertex(v)); 258 | 259 | // start the breadth-first traversal 260 | int place = 0; 261 | while (!is_queue_empty(Q)) 262 | { 263 | // visit v by placing it into the topological order 264 | Pointer element = queue_dequeue(Q); 265 | T[place] = *((int*)element); 266 | free(element); 267 | 268 | // traverse the list of successors of v 269 | for (Edge curedge=G->firstedge[T[place]]; curedge != NULL; curedge = curedge->nextedge) 270 | { 271 | // reduce the predecessor count for each successor 272 | predecessorcount[curedge->endpoint]--; 273 | 274 | if (predecessorcount[curedge->endpoint] == 0) // succ has no further predecessors, so it is ready to process 275 | queue_enqueue(Q, createVertex(curedge->endpoint)); 276 | } 277 | place++; 278 | } 279 | 280 | // free allocated memory 281 | queue_destroy(Q); 282 | free(predecessorcount); 283 | } 284 | 285 | dir_graph dg_reverse(const dir_graph G) 286 | { 287 | // create the new reversed graph 288 | dir_graph revG = dg_create(G->n, G->visit); 289 | 290 | for (uint32_t v = 0; v < G->n; v++) 291 | { 292 | Edge a = G->firstedge[v]; 293 | while (a != NULL) 294 | { 295 | Vertex old_eld = a->endpoint; 296 | dg_insert(revG, old_eld, v); 297 | a = a->nextedge; 298 | } 299 | } 300 | 301 | // return the reversed graph 302 | return revG; 303 | } 304 | 305 | // Helper function prototypes 306 | static void Assign(const dir_graph, const Vertex, int*); 307 | static void Visit(const dir_graph, const int, int*, const Stack); 308 | 309 | // Kosaraju's algorithm for finding Strongly-Connected Components 310 | // source: https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm#The_algorithm , where the container L is a stack 311 | void dg_scc(const dir_graph G) 312 | { 313 | int* visited = calloc(sizeof(int), G->n); 314 | assert(visited != NULL); // allocation failure 315 | 316 | // create stack 317 | Stack L = stack_create(free); 318 | 319 | // perform dfs on g and store the finish order of each vertex on a stack 320 | for (uint32_t i = 0; i < G->n; i++) 321 | if (!visited[i]) 322 | Visit(G, i, visited, L); 323 | 324 | // reset visited array 325 | for (uint32_t i = 0; i < G->n; i++) 326 | visited[i] = false; 327 | 328 | // get reversed graph 329 | dir_graph rev_graph = dg_reverse(G); 330 | 331 | while (!is_stack_empty(L)) // there are still vertices to be assigned to a component 332 | { 333 | // perform dfs on the reversed graph with the graphs on the stack 334 | // the output in each line printed is a strongly-connected component 335 | Vertex* v = stack_pop(L); 336 | 337 | // find new strongly-connected component 338 | if (!visited[*v]) 339 | { 340 | // print vertices in the component 341 | Assign(rev_graph, *v, visited); 342 | printf("\n"); 343 | } 344 | free(v); 345 | } 346 | 347 | // free allocated memory 348 | stack_destroy(L); 349 | free(visited); 350 | dg_destroy(rev_graph); 351 | } 352 | 353 | static void Visit(const dir_graph G, const int s, int* visited, const Stack L) 354 | { 355 | if (visited[s]) 356 | return; 357 | 358 | visited[s] = true; 359 | Edge a = G->firstedge[s]; 360 | 361 | while (a != NULL) 362 | { 363 | Vertex n = a->endpoint; 364 | if (!visited[n]) 365 | Visit(G, n, visited, L); 366 | a = a->nextedge; 367 | } 368 | // store vertex 369 | stack_push(L, createVertex(s)); 370 | } 371 | 372 | static void Assign(const dir_graph G, const Vertex s, int* visited) 373 | { 374 | if (visited[s]) // s has already been assigned to a component 375 | return; 376 | 377 | // visit the vertex 378 | visited[s] = true; 379 | 380 | // visit vertex that's in the component 381 | G->visit(s); 382 | 383 | Edge a = G->firstedge[s]; 384 | while (a != NULL) 385 | { 386 | Vertex n = a->endpoint; 387 | if (!visited[n]) 388 | Assign(G, n, visited); 389 | a = a->nextedge; // get next edge/ vertex 390 | } 391 | } 392 | 393 | // allocate memory for the vertex 394 | static Vertex* createVertex(const Vertex V) 395 | { 396 | Vertex* new_v = malloc(sizeof(Vertex)); 397 | assert(new_v != NULL); // allocation failure 398 | 399 | *new_v = V; 400 | return new_v; 401 | } 402 | 403 | void dg_destroy(const dir_graph G) 404 | { 405 | for (uint32_t i = 0; i < G->n; i++) 406 | { 407 | Edge a = G->firstedge[i]; 408 | while (a != NULL) 409 | { 410 | Edge tmp = a; 411 | a = a->nextedge; 412 | free(tmp); 413 | } 414 | } 415 | free(G->firstedge); 416 | free(G); 417 | } 418 | -------------------------------------------------------------------------------- /modules/Graph/DirectedGraph/DirectedGraph.h: -------------------------------------------------------------------------------- 1 | #pragma once // include at most once 2 | 3 | #include 4 | 5 | 6 | typedef uint32_t Vertex; 7 | typedef struct _dir_graph* dir_graph; 8 | 9 | // Pointer to function that visits the vertices 10 | typedef void (*VisitFunc)(Vertex value); 11 | 12 | 13 | // creates directed graph 14 | // -requires a visit function 15 | dir_graph dg_create(const uint32_t, const VisitFunc); 16 | 17 | // inserts edge (A-B) at the graph 18 | void dg_insert(const dir_graph, const Vertex A, const Vertex B); 19 | 20 | // prints the graph 21 | void dg_print(const dir_graph); 22 | 23 | // depth first traversal of the graph 24 | // prints tree, forward, back and cross edges (colored) 25 | void dg_dfs(const dir_graph); 26 | 27 | // returns reversed graph 28 | dir_graph dg_reverse(const dir_graph); 29 | 30 | // prints a topological ordering of the graph 31 | void dg_bts(const dir_graph); 32 | 33 | // prints strongly-connected components using Kosaraju's algorithm 34 | void dg_scc(const dir_graph); 35 | 36 | // destroys the memory used by the directed graph 37 | void dg_destroy(const dir_graph); 38 | -------------------------------------------------------------------------------- /modules/Graph/DirectedGraph/README.md: -------------------------------------------------------------------------------- 1 | This is an implementations of [directed graph](https://en.wikipedia.org/wiki/Directed_graph) using adjecency list. A directed graph, also called a digraph, is a graph in which the edges have a direction. This is usually indicated with an arrow on the edge; more formally, if v and w are vertices, an edge is an unordered pair {v,w}. 2 | 3 | # Performance 4 | directed graph picture 5 | 6 | If V is the number vertices and E is the number of edges in the graph: 7 | 8 | Algorithm | Average case | Worst case 9 | ----------------------------- | --------- | ---------- 10 | Space | Θ(V+E) | O(V+E) 11 | Insert edge | Θ(1) | O(V) 12 | Print | Θ(V+E) | O(V+E) 13 | Dfs | Θ(V+E) | O(V+E) 14 | Reverse | Θ(V+E) | O(V+E) 15 | Topological Ordering | Θ(V+E) | O(V+E) 16 | Strongly-Connected Components | Θ(V+E) | O(V+E) 17 | 18 | [Kosaraju's algorithm](https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm) is used to find the strongly-connected components of the graph. 19 | -------------------------------------------------------------------------------- /modules/Graph/README.md: -------------------------------------------------------------------------------- 1 | A [graph](https://en.wikipedia.org/wiki/Graph_(abstract_data_type)) data structure is a collection of nodes that have data and are connected to other nodes. A graph data structure consists of a finite set of vertices (also called nodes or points), together with a set of unordered pairs of these vertices for an undirected graph or a set of ordered pairs for a directed graph, known as edges. 2 | 3 | # Implementations 4 | graph picture 5 | 6 | This ADT has the following implementations: 7 | * [Directed Graph](https://github.com/pavlosdais/Abstract-Data-Types/tree/main/modules/Graph/DirectedGraph#readme) 8 | * [Undirected Graph](https://github.com/pavlosdais/Abstract-Data-Types/tree/main/modules/Graph/UndirectedGraph#readme) 9 | * [Weighted Undirected Graph](https://github.com/pavlosdais/Abstract-Data-Types/tree/main/modules/Graph/WeightedUndirectedGraph#readme) 10 | 11 | Each of which, has implemented algorithms associated with them. 12 | -------------------------------------------------------------------------------- /modules/Graph/UndirectedGraph/README.md: -------------------------------------------------------------------------------- 1 | This is an implementations of [undirected graph](https://en.wikipedia.org/wiki/Graph_(discrete_mathematics)#Undirected_graph) using adjecency list. An undirected graph is graph, i.e., a set of objects (called vertices or nodes) that are connected together, where all the edges are bidirectional. This means that if {v,w} is an edge, we can travel both from v to w and from w to v. An undirected graph is sometimes called an undirected network. 2 | 3 | # Performance 4 | undirected graph picture 5 | 6 | If V is the number vertices and E is the number of edges in the graph: 7 | 8 | Algorithm | Average case | Worst case 9 | --------------- | --------- | ---------- 10 | Space | Θ(V+E) | O(V+E) 11 | Insert edge | Θ(1) | O(1) 12 | Print | Θ(V+E) | O(V+E) 13 | Simple Path Check | Θ(V+E) | O(V+E) 14 | 15 | The algorithm used to perform the simple path check (meaning a path where each vertex is visited at most once) between two vertices is [Breadth-first search](https://en.wikipedia.org/wiki/Breadth-first_search). 16 | -------------------------------------------------------------------------------- /modules/Graph/UndirectedGraph/UndirectedGraph.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "UndirectedGraph.h" 6 | #include "../../Queue/queue.h" 7 | #include "../../Stack/stack.h" 8 | 9 | typedef struct edge 10 | { 11 | Vertex endpoint; 12 | struct edge *nextedge; 13 | } 14 | edge; 15 | typedef struct edge* Edge; 16 | 17 | typedef struct _undir_graph 18 | { 19 | uint32_t n; // number of vertices in the graph 20 | Edge* firstedge; // adjacency list representation 21 | VisitFunc visit; // function that visits the vertices 22 | } 23 | _undir_graph; 24 | 25 | typedef struct p* point; 26 | typedef struct p 27 | { 28 | point parent; // the the parent of the vertex (meaning the vertex it came from): needed in order to print the path 29 | Vertex vertex; // the actual vertex 30 | } 31 | p; 32 | 33 | undir_graph ug_create(const uint32_t num_of_vertices, const VisitFunc visit) 34 | { 35 | assert(visit != NULL); // a visit function needs to be given 36 | 37 | undir_graph G = malloc(sizeof(_undir_graph)); 38 | assert(G != NULL); // allocation failure 39 | 40 | G->firstedge = calloc(sizeof(Edge), num_of_vertices); 41 | assert(G->firstedge != NULL); // allocation failure 42 | 43 | G->n = num_of_vertices; 44 | G->visit = visit; 45 | 46 | return G; 47 | } 48 | 49 | void ug_insert(const undir_graph G, const Vertex A, const Vertex B) 50 | { 51 | // Insert vertex B at the start of the list A - O(1) 52 | Edge* al = &(G->firstedge[A]); 53 | 54 | Edge new_edge = malloc(sizeof(edge)); 55 | assert(new_edge != NULL); // allocation failure 56 | 57 | new_edge->endpoint = B; 58 | new_edge->nextedge = *al; 59 | *al = new_edge; 60 | 61 | // Insert vertex A at the start of the list B (undirected graph) - O(1) 62 | al = &(G->firstedge[B]); 63 | 64 | new_edge = malloc(sizeof(edge)); 65 | assert(new_edge != NULL); // allocation failure 66 | 67 | new_edge->endpoint = A; 68 | new_edge->nextedge = *al; 69 | *al = new_edge; 70 | } 71 | 72 | void ug_print(const undir_graph G) 73 | { 74 | for (uint32_t i = 0; i < G->n; i++) 75 | { 76 | Edge a = G->firstedge[i]; 77 | printf("[%d]", i); 78 | if (a != NULL) 79 | printf(": "); 80 | while (a != NULL) 81 | { 82 | printf("%d ", a->endpoint); 83 | a = a->nextedge; 84 | } 85 | printf("\n"); 86 | } 87 | } 88 | 89 | // Breadth-first search of the graph from 90 | // source: https://en.wikipedia.org/wiki/Breadth-first_search 91 | 92 | // Helper function prototypes 93 | static point CreatePoint(const Vertex, const point); 94 | static void printPath(const point); 95 | void ug_simplepathcheck(const undir_graph G, const Vertex start, const Vertex goal) 96 | { 97 | int* visited = calloc(sizeof(int), G->n); 98 | assert(visited != NULL); // allocation failure 99 | 100 | Vertex w; 101 | Edge curedge; 102 | 103 | // create queue - necessary for the bfs algorithm 104 | Queue Q = queue_create(free); 105 | 106 | // create stack that stores vertices that are not the goal vertex 107 | Stack S = stack_create(free); 108 | 109 | point a = CreatePoint(start, NULL); 110 | queue_enqueue(Q, a); 111 | do 112 | { 113 | a = queue_dequeue(Q); 114 | stack_push(S, a); 115 | w = a->vertex; 116 | if (w == goal) // found goal vertex, print the path 117 | { 118 | // print the path 119 | printf("\nPath: "); 120 | printPath(a); printf("\n"); 121 | break; 122 | } 123 | 124 | visited[w] = true; // mark the vertex as visited 125 | 126 | // not the goal vertex, explore its neighbours 127 | curedge = G->firstedge[w]; 128 | while (curedge != NULL) 129 | { 130 | w = curedge->endpoint; 131 | if (!visited[w]) 132 | { 133 | point new_p = CreatePoint(w, a); 134 | queue_enqueue(Q, new_p); 135 | } 136 | curedge=curedge->nextedge; // get next edge 137 | } 138 | } 139 | while (!is_queue_empty(Q)); // have not traversed through all vertices 140 | 141 | if (w != goal) // no simple path exists that connects the vertices 142 | printf("\nNo simple path between vertices %d and %d exists!\n", start, goal); 143 | 144 | // free allocated memory 145 | stack_destroy(S); 146 | queue_destroy(Q); 147 | free(visited); 148 | } 149 | 150 | // recursive function that prints the actual path 151 | static void printPath(const point a) 152 | { 153 | if (a == NULL) 154 | return; 155 | printPath(a->parent); 156 | 157 | printf("[%d] ", a->vertex); 158 | } 159 | 160 | // allocate memory for the point 161 | static point CreatePoint(const Vertex v, const point source) 162 | { 163 | point new_point = malloc(sizeof(p)); 164 | assert(new_point != NULL); // allocation failure 165 | 166 | new_point->vertex = v; 167 | new_point->parent = source; 168 | 169 | return new_point; 170 | } 171 | 172 | void ug_destroy(const undir_graph G) 173 | { 174 | for (uint32_t i = 0; i < G->n; i++) 175 | { 176 | Edge a = G->firstedge[i]; 177 | while (a != NULL) 178 | { 179 | Edge tmp = a; 180 | a = a->nextedge; 181 | free(tmp); 182 | } 183 | } 184 | 185 | free(G->firstedge); 186 | free(G); 187 | } 188 | -------------------------------------------------------------------------------- /modules/Graph/UndirectedGraph/UndirectedGraph.h: -------------------------------------------------------------------------------- 1 | #pragma once // include at most once 2 | 3 | #include 4 | 5 | 6 | typedef uint32_t Vertex; 7 | typedef struct _undir_graph* undir_graph; 8 | 9 | // Pointer to function that visits the vertices 10 | typedef void (*VisitFunc)(Vertex value); 11 | 12 | 13 | // creates undirected graph 14 | // -requires a visit function 15 | undir_graph ug_create(const uint32_t, const VisitFunc); 16 | 17 | // inserts edge (A-B) at the graph 18 | void ug_insert(undir_graph, Vertex A, Vertex B); 19 | 20 | // prints the graph 21 | void ug_print(const undir_graph); 22 | 23 | // prints a simple path, meaning a path where each vertex is visited at most once, between vertices start and goal, if such path exists 24 | void ug_simplepathcheck(const undir_graph, const Vertex start, const Vertex goal); 25 | 26 | // destroys the memory used by the undirected graph 27 | void ug_destroy(const undir_graph); 28 | -------------------------------------------------------------------------------- /modules/Graph/WeightedUndirectedGraph/README.md: -------------------------------------------------------------------------------- 1 | This is an implementations of [weighted undirected graph](https://www.codewars.com/kata/5aaea7a25084d71006000082) using adjecency list. A weighted undirected graph is the same as a undirected graph except that each edge has a weight, or cost, associated with it. 2 | 3 | # Performance 4 | weighted undirected graph picture 5 | 6 | If V is the number vertices and E is the number of edges in the graph: 7 | 8 | Algorithm | Average case | Worst case 9 | --------------- | --------- | ---------- 10 | Space | Θ(V+E) | O(V+E) 11 | Insert edge | Θ(1) | O(1) 12 | Print | Θ(V+E) | O(V+E) 13 | Minimum spanning tree | O(Elog V) | O(Elog V) 14 | 15 | [Prim-Jarnik's algorithm](https://en.wikipedia.org/wiki/Prim%27s_algorithm) is used to find the minimum spanning tree of the graph. 16 | -------------------------------------------------------------------------------- /modules/Graph/WeightedUndirectedGraph/WeightedUndirectedGraph.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "WeightedUndirectedGraph.h" 7 | 8 | typedef struct edge 9 | { 10 | Vertex endpoint; 11 | cost weight; // the weight of the edge 12 | struct edge *nextedge; 13 | } 14 | edge; 15 | typedef struct edge* Edge; 16 | 17 | typedef struct _wu_graph 18 | { 19 | uint32_t n; // number of vertices in the graph 20 | Edge* firstedge; // adjacency list representation 21 | } 22 | _wu_graph; 23 | 24 | wu_graph wug_create(const uint32_t num_of_vertices) 25 | { 26 | wu_graph G = malloc(sizeof(_wu_graph)); 27 | assert(G != NULL); // allocation failure 28 | 29 | G->firstedge = calloc(sizeof(Edge), num_of_vertices); 30 | assert(G->firstedge != NULL); // allocation failure 31 | 32 | G->n = num_of_vertices; 33 | 34 | return G; 35 | } 36 | 37 | void wug_insert(const wu_graph G, const Vertex A, const Vertex B, const cost weight) 38 | { 39 | // insert vertex B at the start of the list A - O(1) 40 | Edge* al = &(G->firstedge[A]); 41 | 42 | Edge new_edge = malloc(sizeof(edge)); 43 | assert(new_edge != NULL); // allocation failure 44 | 45 | new_edge->endpoint = B; 46 | new_edge->weight = weight; 47 | new_edge->nextedge = *al; 48 | *al = new_edge; 49 | 50 | // insert vertex A at the start of the list B (undirected graph) - O(1) 51 | al = &(G->firstedge[B]); 52 | 53 | new_edge = malloc(sizeof(edge)); 54 | assert(new_edge != NULL); // allocation failure 55 | 56 | new_edge->endpoint = A; 57 | new_edge->weight = weight; 58 | new_edge->nextedge = *al; 59 | *al = new_edge; 60 | } 61 | 62 | // helper function prototypes 63 | typedef struct pq* PQueue; 64 | typedef struct n 65 | { 66 | int v; // vertex v 67 | uint32_t weight; // weight of vertex 68 | } 69 | n; 70 | // priority queue 71 | static PQueue createPQueue(uint32_t n, uint32_t* E, uint32_t* C); 72 | static n pq_remove(PQueue PQ); 73 | static bool is_pq_empty(PQueue PQ); 74 | static void updateWeights(PQueue PQ, int v, uint32_t new_weight); 75 | static bool pq_exists(PQueue PQ, uint32_t v); 76 | static void pq_destroy(PQueue PQ); 77 | 78 | static inline void print_min_span_tree(const wu_graph G, uint32_t* arr, const int n); 79 | 80 | // source: https://en.wikipedia.org/wiki/Prim%27s_algorithm#Description 81 | // adjacency list representation w/ binary heap priority queue - O(Elog V) 82 | void wug_minspantree(const wu_graph G) 83 | { 84 | assert(G != NULL); 85 | 86 | int size_of_graph = G->n; 87 | 88 | // create helper arrays 89 | uint32_t* E = calloc(sizeof(uint32_t), size_of_graph); 90 | assert(E != NULL); // allocation failure 91 | 92 | cost* C = calloc(sizeof(cost), size_of_graph); 93 | assert(C != NULL); // allocation failure 94 | 95 | // create priority queue 96 | PQueue Q = createPQueue(size_of_graph, E, C); 97 | 98 | while (!is_pq_empty(Q)) // not an empty queue 99 | { 100 | n min_vertex = pq_remove(Q); 101 | Vertex vert = min_vertex.v; 102 | 103 | Edge ed = G->firstedge[vert]; 104 | while (ed != NULL) 105 | { 106 | int dest = ed->endpoint; 107 | if (pq_exists(Q, dest) && ed->weight < C[dest]) // edge with lower weight found 108 | { 109 | // update weight / keep pq heapified 110 | updateWeights(Q, dest, ed->weight); 111 | 112 | // update the parent and minimum weight of vertex dest 113 | C[dest] = ed->weight; 114 | E[dest] = vert; 115 | } 116 | ed = ed->nextedge; // get next vertex 117 | } 118 | } 119 | 120 | // print the minimum spanning tree created 121 | print_min_span_tree(G, E, size_of_graph); 122 | 123 | // free allocated memory 124 | pq_destroy(Q); 125 | free(E); 126 | free(C); 127 | } 128 | 129 | // print the minimum spanning tree 130 | static inline void print_min_span_tree(const wu_graph G, uint32_t* E, const int n) 131 | { 132 | cost total_weight = 0; 133 | for (int i = 1; i < n; i++) 134 | { 135 | // vertex is not included in the minimum spanning tree 136 | if (E[i] == INT_MIN) 137 | continue; 138 | 139 | Edge ed = G->firstedge[i]; 140 | printf("(%d-%d)", E[i], i); 141 | 142 | // find the weight of the edge 143 | while (ed != NULL) 144 | { 145 | if (ed->endpoint == E[i]) 146 | { 147 | total_weight += ed->weight; 148 | printf(" || weight = %d\n", ed->weight); 149 | break; 150 | } 151 | ed = ed->nextedge; 152 | } 153 | } 154 | printf("Total weight = %d\n", total_weight); 155 | } 156 | 157 | void wug_print(const wu_graph G) 158 | { 159 | for (uint32_t i = 0; i < G->n; i++) 160 | { 161 | Edge a = G->firstedge[i]; 162 | printf("[%d]", i); 163 | if (a != NULL) 164 | printf(" :"); 165 | while (a != NULL) 166 | { 167 | printf("%d|%d| ", a->endpoint, a->weight); 168 | a = a->nextedge; 169 | } 170 | printf("\n"); 171 | } 172 | } 173 | 174 | void wug_destroy(const wu_graph G) 175 | { 176 | for (uint32_t i = 0; i < G->n; i++) 177 | { 178 | Edge a = G->firstedge[i]; 179 | while (a != NULL) 180 | { 181 | Edge tmp = a; 182 | a = a->nextedge; 183 | free(tmp); 184 | } 185 | } 186 | free(G->firstedge); 187 | free(G); 188 | } 189 | 190 | 191 | ///////////////////////////////////////////////////////////////////////////////////////////////////// 192 | ////////////////////////////////////// custom priority queue /////////////////////////////////////// 193 | ///////////////////////////////////////////////////////////////////////////////////////////////////// 194 | 195 | typedef struct n* node; 196 | typedef struct pq 197 | { 198 | node arr; // array of nodes containing the data 199 | uint32_t* pos; // position of vertices in the queue 200 | uint32_t curr_size; // current size of the heap 201 | uint32_t capacity; // max capacity of the heap 202 | } 203 | pq; 204 | 205 | #define ROOT 0 206 | #define find_parent(i) ((i-1)/2) 207 | #define find_left_child(i) (2*i + 1) 208 | #define find_right_child(i) (2*i + 2) 209 | 210 | // function prototype 211 | static void bubble_down(PQueue PQ, uint32_t node); 212 | 213 | static void pq_init(PQueue* PQ, uint32_t size) 214 | { 215 | *PQ = malloc(sizeof(pq)); 216 | assert(*PQ != NULL); // allocation failure 217 | 218 | // allocate memory for the array of nodes 219 | (*PQ)->arr = calloc(size, sizeof( *((*PQ)->arr)) ); 220 | assert((*PQ)->arr != NULL); // allocation failure 221 | 222 | (*PQ)->pos = calloc(size, sizeof(int)); 223 | assert((*PQ)->pos != NULL); // allocation failure 224 | 225 | (*PQ)->capacity = size; 226 | (*PQ)->curr_size = size; 227 | } 228 | 229 | static bool is_pq_empty(PQueue PQ) 230 | { 231 | return PQ->curr_size == 0; 232 | } 233 | 234 | static PQueue createPQueue(uint32_t n, uint32_t* E, uint32_t* C) 235 | { 236 | // create priority queue 237 | PQueue pq; 238 | pq_init(&pq, n); 239 | 240 | // initialize pq 241 | for (uint32_t i = 0; i < n; i++) 242 | { 243 | C[i] = INT_MAX; 244 | E[i] = INT_MIN; 245 | pq->arr[i].weight = INT_MAX; 246 | 247 | pq->pos[i] = i; 248 | pq->arr[i].v = i; 249 | } 250 | 251 | // make sure the item removed has the highest priority 252 | pq->arr[0].weight = 0; 253 | 254 | return pq; 255 | } 256 | 257 | static n pq_remove(PQueue PQ) 258 | { 259 | // store root node 260 | n root = PQ->arr[ROOT]; 261 | 262 | // swap positions 263 | PQ->pos[root.v] = PQ->curr_size-1; 264 | PQ->pos[PQ->arr[PQ->curr_size-1].v] = ROOT; 265 | PQ->arr[ROOT] = PQ->arr[PQ->curr_size-1]; 266 | 267 | PQ->curr_size--; // node removed, decrement the number of elements in the queue 268 | 269 | bubble_down(PQ, ROOT); // heapify 270 | 271 | // return root node - node with the highest priority 272 | return root; 273 | } 274 | 275 | static void bubble_down(PQueue PQ, uint32_t node) 276 | { 277 | // get left child 278 | uint32_t left = find_left_child(node); 279 | if (left > PQ->curr_size) // children do not exist 280 | return; 281 | 282 | // get right child 283 | uint32_t max_child, right = find_right_child(node); 284 | 285 | // find max child 286 | max_child = left; 287 | if (right <= PQ->curr_size && PQ->arr[right].weight < PQ->arr[max_child].weight) 288 | max_child = right; 289 | 290 | if (PQ->arr[max_child].weight < PQ->arr[node].weight) 291 | { 292 | // swap positions 293 | PQ->pos[PQ->arr[max_child].v] = node; 294 | PQ->pos[PQ->arr[node].v] = max_child; 295 | 296 | // swap values 297 | n tmp = PQ->arr[node]; 298 | PQ->arr[node] = PQ->arr[max_child]; 299 | PQ->arr[max_child] = tmp; 300 | 301 | bubble_down(PQ, max_child); 302 | } 303 | } 304 | 305 | static void updateWeights(PQueue PQ, int v, uint32_t new_weight) 306 | { 307 | // update weight for vertex v 308 | uint32_t curr_node = PQ->pos[v]; 309 | PQ->arr[curr_node].weight = new_weight; 310 | 311 | // heapify 312 | uint32_t parent = find_parent(curr_node); 313 | while (curr_node > 0 && PQ->arr[curr_node].weight < PQ->arr[parent].weight) 314 | { 315 | // swap positions 316 | PQ->pos[PQ->arr[curr_node].v] = parent; 317 | PQ->pos[PQ->arr[parent].v] = curr_node; 318 | 319 | // swap values 320 | n tmp = PQ->arr[curr_node]; 321 | PQ->arr[curr_node] = PQ->arr[parent]; 322 | PQ->arr[parent] = tmp; 323 | 324 | // move up 325 | curr_node = parent; 326 | parent = find_parent(curr_node); 327 | } 328 | } 329 | 330 | static bool pq_exists(PQueue PQ, uint32_t v) 331 | { 332 | return (PQ->pos[v] < PQ->curr_size ? true: false); 333 | } 334 | 335 | static void pq_destroy(PQueue PQ) 336 | { 337 | free(PQ->pos); 338 | free(PQ->arr); 339 | free(PQ); 340 | } 341 | -------------------------------------------------------------------------------- /modules/Graph/WeightedUndirectedGraph/WeightedUndirectedGraph.h: -------------------------------------------------------------------------------- 1 | #pragma once // include at most once 2 | 3 | #include 4 | 5 | 6 | // needed typedefs 7 | typedef uint32_t Vertex; 8 | typedef struct _wu_graph* wu_graph; 9 | typedef uint32_t cost; // the cost of the edge 10 | 11 | 12 | // creates weighted undirected graph 13 | wu_graph wug_create(const uint32_t); 14 | 15 | // inserts edge (A-B) with its weight at the graph 16 | void wug_insert(const wu_graph, const Vertex A, const Vertex B, const cost); 17 | 18 | // prints the graph 19 | void wug_print(const wu_graph); 20 | 21 | // prints minimum spanning tree, as well as its total weight using the prim-jarnik algorithm 22 | void wug_minspantree(const wu_graph); 23 | 24 | // destroys the memory used by the weighted undirected graph 25 | void wug_destroy(const wu_graph); 26 | -------------------------------------------------------------------------------- /modules/HashTable/DoubleHashing/README.md: -------------------------------------------------------------------------------- 1 | This is an implentation using [double hashing](https://en.wikipedia.org/wiki/Double_hashing). Double hashing is used in conjunction with [open addressing](https://en.wikipedia.org/wiki/Open_addressing) in hash tables to resolve hash collisions, by using a secondary hash of the key as an offset when a collision occurs. The double hashing technique uses the first hash value as an index into the table and then repeatedly steps forward an interval until the desired value is located, an empty location is reached, or the entire table has been searched. The interval is set by a second hash function. Here, the second function used is the popular:
2 | `hash_func2 = PRIME_NUM – (hash_func1 % PRIME_NUM)`, where PRIME_NUM is a prime smaller than the hash table's capacity. 3 | 4 | # Performance 5 | double hashing picture 6 | 7 | If n is the number of elements in the hash table: 8 | 9 | Algorithm | Average case | Worst case 10 | ---------- | ------- | ---------- 11 | Space | Θ(n) | O(n) 12 | Insert | Θ(1) | O(n) 13 | Remove | Θ(1) | O(n) 14 | Search | Θ(1) | O(n) 15 | -------------------------------------------------------------------------------- /modules/HashTable/DoubleHashing/hash_table.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "hash_table.h" 5 | 6 | // when max load factor is exceeded, rehashing operation occurs 7 | #define MAX_LOAD_FACTOR 0.5 8 | 9 | typedef enum { EMPTY = 0, OCCUPIED, DELETED } bucket_state; 10 | 11 | // bucket 12 | typedef struct node 13 | { 14 | Pointer data; // pointer to the data we are storing 15 | uint32_t hash_value; // hash value of the data 16 | bucket_state state; // state of the bucket (empty/occupied/deleted) 17 | } 18 | node; 19 | 20 | typedef struct hash_table 21 | { 22 | node* buckets; // buckets storing the data 23 | uint8_t capacity; // the capacity of the hash table - index to the "hash_sizes" array 24 | uint8_t sec_prime; // the prime number used for the second hash function 25 | uint64_t elements; // number of elements currently stored in the hash table 26 | HashFunc hash; // function that hashes an element into a positive integer 27 | CompareFunc compare; // function that compares the elements 28 | DestroyFunc destroy; // function that destroys the elements, NULL if not 29 | } 30 | hash_table; 31 | 32 | // the second hash function 33 | // source: https://cgi.di.uoa.gr/~k08/manolis/2020-2021/lectures/Hashing.pdf , page 87 34 | #define hash_func2(PRIME, h1) (PRIME - (h1 % PRIME)) 35 | 36 | // available number of buckets, preferably prime numbers since it has been proven they have better behavior 37 | static uint64_t hash_sizes[] = 38 | { 29, 67, 131, 263, 509, 1021, 2053, 4093, 8179, 16369, 32749, 65521, 131071, 262147, 524287, 1048573, 2097143, 39 | 4194301, 8388593, 16777213, 33554467, 67108879, 134217757, 268435459, 536870923, 1073741827, 2147483647, 4294967291, 40 | 8589934583, 17179869143, 34359738337, 68719476731, 137438953447, 274877906899, 549755813881, 1099511627689, 2199023255531, 41 | 4398046511093, 8796093022151, 17592186044399, 35184372088777, 70368744177643, 140737488355213, 281474976710597, 562949953421231, 42 | 1125899906842597, 2251799813685119, 4503599627370449, 9007199254740881, 18014398509481951, 36028797018963913, 72057594037927931, 43 | 144115188075855859, 288230376151711717, 576460752303423433, 1152921504606846883, 2305843009213693951, 4611686018427387847 }; 44 | 45 | #define get_hash(i) (hash_sizes[i]) 46 | 47 | 48 | // function prototype 49 | static inline void rehash(HashTable); 50 | 51 | HashTable hash_create(const HashFunc hash, const CompareFunc compare, const DestroyFunc destroy) 52 | { 53 | assert(hash != NULL && compare != NULL); // a hash and compare function needs to be given 54 | 55 | HashTable ht = malloc(sizeof(hash_table)); 56 | assert(ht != NULL); // allocation failure 57 | 58 | ht->capacity = 1; 59 | ht->buckets = calloc(sizeof(node), get_hash(ht->capacity)); // allocate memory for the buckets 60 | assert(ht->buckets != NULL); // allocation failure 61 | 62 | ht->sec_prime = 0; 63 | ht->elements = 0; 64 | ht->hash = hash; 65 | ht->compare = compare; 66 | ht->destroy = destroy; 67 | 68 | return ht; 69 | } 70 | 71 | uint64_t hash_size(const HashTable ht) 72 | { 73 | assert(ht != NULL); 74 | return ht->elements; 75 | } 76 | 77 | bool is_ht_empty(const HashTable ht) 78 | { 79 | assert(ht != NULL); 80 | return ht->elements == 0; 81 | } 82 | 83 | bool hash_insert(const HashTable ht, const Pointer value) 84 | { 85 | assert(ht != NULL); 86 | 87 | if (((float)ht->elements / get_hash(ht->capacity)) > MAX_LOAD_FACTOR) // max load factor exceeded, start rehash 88 | rehash(ht); 89 | 90 | const uint32_t hash_value = ht->hash(value), interval = hash_func2(get_hash(ht->sec_prime), hash_value); 91 | uint32_t pos = hash_value % get_hash(ht->capacity), pos_adjustment = 0; 92 | 93 | uint64_t deleted_index = get_hash(ht->capacity)+1; // save deleted node's index if found 94 | 95 | for (uint64_t i = 0; i < get_hash(ht->capacity); pos_adjustment += interval, i++) 96 | { 97 | const uint64_t new_pos = (pos + pos_adjustment) % get_hash(ht->capacity); 98 | 99 | if (ht->buckets[new_pos].state == EMPTY) // empty spot found, insert 100 | { 101 | pos = deleted_index == get_hash(ht->capacity)+1? new_pos : deleted_index; 102 | break; 103 | } 104 | // a deleted, possible, spot found 105 | // altough we could just insert here, we mark it and keep searching in case the value already exists in order to avoid duplicates 106 | else if (ht->buckets[new_pos].state == DELETED) 107 | { 108 | if (deleted_index == get_hash(ht->capacity)+1) 109 | deleted_index = new_pos; 110 | } 111 | // check to see if value already exists in the hash table 112 | else if (ht->compare(ht->buckets[new_pos].data, value) == 0) // value already exists 113 | { 114 | // if a destroy function exists, destroy the value 115 | if (ht->destroy != NULL) 116 | ht->destroy(value); 117 | return false; 118 | } 119 | } 120 | 121 | ht->buckets[pos].state = OCCUPIED; // mark the bucket as occupied 122 | ht->buckets[pos].data = value; 123 | ht->buckets[pos].hash_value = hash_value; 124 | ht->elements++; // value inserted, increment the number of elements in the hash table 125 | 126 | return true; 127 | } 128 | 129 | // helper function 130 | static inline void rehash_insert(const HashTable ht, const Pointer value, const uint32_t hash_value); 131 | static inline void rehash(const HashTable ht) 132 | { 133 | // save previous buckets 134 | node* old_buckets = ht->buckets; 135 | 136 | ht->sec_prime = ht->capacity; 137 | (ht->capacity)++; 138 | 139 | // create the new number of buckets 140 | ht->buckets = calloc(sizeof(node), get_hash(ht->capacity)); 141 | assert(ht->buckets != NULL); // allocation failure 142 | 143 | // start rehash operation 144 | for (uint64_t i = 0; i < get_hash(ht->sec_prime); i++) 145 | { 146 | if (old_buckets[i].state == OCCUPIED) 147 | rehash_insert(ht, old_buckets[i].data, old_buckets[i].hash_value); 148 | } 149 | free(old_buckets); 150 | } 151 | 152 | static inline void rehash_insert(const HashTable ht, const Pointer value, const uint32_t hash_value) 153 | { 154 | uint64_t pos = hash_value % get_hash(ht->capacity), pos_adjustment = 0; 155 | const uint32_t interval = hash_func2(get_hash(ht->sec_prime), hash_value); 156 | 157 | for (uint64_t i = 0; i < get_hash(ht->capacity); pos_adjustment += interval, i++) 158 | { 159 | const uint64_t new_pos = (pos + pos_adjustment) % get_hash(ht->capacity); 160 | 161 | // during rehashing only empty spots exist 162 | if (ht->buckets[new_pos].state == EMPTY) // empty spot found, insert 163 | { 164 | pos = new_pos; 165 | break; 166 | } 167 | } 168 | 169 | // insert the element and mark the bucket as occupied 170 | ht->buckets[pos].state = OCCUPIED; 171 | ht->buckets[pos].data = value; 172 | ht->buckets[pos].hash_value = hash_value; 173 | } 174 | 175 | // returns the bucket in which the value exists 176 | // if it does not exist, returns the capacity of the hash table 177 | static inline uint64_t find_bucket(const HashTable ht, const Pointer value) 178 | { 179 | const uint32_t h1 = ht->hash(value), interval = hash_func2(get_hash(ht->sec_prime), h1); 180 | uint64_t buckets_checked = 0; 181 | 182 | for (uint64_t pos = h1 % get_hash(ht->capacity); ht->buckets[pos].state != EMPTY; pos = (pos + interval) % get_hash(ht->capacity)) 183 | { 184 | if (ht->buckets[pos].state == OCCUPIED && ht->compare(ht->buckets[pos].data, value) == 0) 185 | return pos; 186 | else if (++buckets_checked == get_hash(ht->capacity)) // searched all buckets containing data, value does not exist 187 | break; 188 | } 189 | 190 | // reached an empty bucket, value does not exist 191 | return get_hash(ht->capacity); 192 | } 193 | 194 | bool hash_remove(const HashTable ht, const Pointer value) 195 | { 196 | if (is_ht_empty(ht)) // empty hash table - nothing to remove 197 | return false; 198 | 199 | // find the potential bucket the value exists in 200 | const uint64_t pos = find_bucket(ht, value); 201 | if (pos == get_hash(ht->capacity)) // value does not exist 202 | return false; 203 | 204 | // destroy the data, if a destroy function is given 205 | if (ht->destroy != NULL) 206 | ht->destroy(ht->buckets[pos].data); 207 | 208 | ht->buckets[pos].state = DELETED; // mark the bucket as deleted 209 | ht->buckets[pos].data = NULL; 210 | ht->elements--; // value removed, decrement the number of elements in the hash table 211 | return true; 212 | } 213 | 214 | bool hash_exists(const HashTable ht, const Pointer value) 215 | { 216 | if (is_ht_empty(ht)) // hash table is empty, nothing to search 217 | return false; 218 | 219 | return find_bucket(ht, value) != get_hash(ht->capacity); 220 | } 221 | 222 | DestroyFunc hash_set_destroy(const HashTable ht, const DestroyFunc new_destroy_func) 223 | { 224 | assert(ht != NULL); 225 | 226 | DestroyFunc old_destroy_func = ht->destroy; 227 | ht->destroy = new_destroy_func; 228 | return old_destroy_func; 229 | } 230 | 231 | void hash_destroy(const HashTable ht) 232 | { 233 | assert(ht != NULL); 234 | 235 | // if a destroy function exists & there are elements, destroy the data 236 | if (ht->destroy != NULL && ht->elements != 0) 237 | { 238 | for (uint64_t i = 0 ;; i++) 239 | { 240 | if (ht->buckets[i].state == OCCUPIED) 241 | { 242 | ht->destroy(ht->buckets[i].data); 243 | if (--(ht->elements) == 0) break; // all elements deleted 244 | } 245 | } 246 | } 247 | 248 | free(ht->buckets); 249 | free(ht); 250 | } 251 | -------------------------------------------------------------------------------- /modules/HashTable/DoubleHashing/hash_table.h: -------------------------------------------------------------------------------- 1 | #pragma once // include at most once 2 | 3 | #include 4 | #include 5 | 6 | 7 | typedef void* Pointer; 8 | 9 | // Pointer to function that compares 2 elements a and b and returns 0 if a and b are equal 10 | typedef int (*CompareFunc)(Pointer a, Pointer b); 11 | 12 | // Pointer to function that destroys an element value 13 | typedef void (*DestroyFunc)(Pointer value); 14 | 15 | // Pointer to function that hashes a value to a positive (unsigned) integer 16 | typedef unsigned int (*HashFunc)(Pointer value); 17 | 18 | typedef struct hash_table* HashTable; 19 | 20 | 21 | // creates hash table 22 | // -requires a hash function 23 | // a compare function 24 | // a destroy function (or NULL if you want to preserve the data) 25 | HashTable hash_create(const HashFunc, const CompareFunc, const DestroyFunc); 26 | 27 | // inserts value at the hash table 28 | // returns true if the value was inserted, false if it already exists 29 | bool hash_insert(const HashTable, const Pointer); 30 | 31 | // removes the value from the hash table and destroys its value if a destroy function was given 32 | // returns true if the value was deleted, false in any other case 33 | bool hash_remove(const HashTable, const Pointer); 34 | 35 | // returns true if value exists in the hash table, false otherwise 36 | bool hash_exists(const HashTable, const Pointer); 37 | 38 | // returns the number of elements in the hash table 39 | uint64_t hash_size(const HashTable); 40 | 41 | // returns true if the hash table is empty, false otherwise 42 | bool is_ht_empty(const HashTable); 43 | 44 | // changes the destroy function and returns the old one 45 | DestroyFunc hash_set_destroy(const HashTable, const DestroyFunc); 46 | 47 | // destroys the memory used by the hash table 48 | void hash_destroy(const HashTable); 49 | -------------------------------------------------------------------------------- /modules/HashTable/README.md: -------------------------------------------------------------------------------- 1 | [Hash table](https://en.wikipedia.org/wiki/Hash_table) also known as hash map, is a data structure that implements a set abstract data type, a structure that can map keys to values. A hash table uses a hash function to compute an index, also called a hash code, into an array of buckets or slots from which the desired value can be found. 2 | 3 | # Implementations 4 | separate chaining picture 5 | 6 | This ADT has the following implementations: 7 | - [Separate chaining](https://github.com/pavlosdais/Abstract-Data-Types/tree/main/modules/HashTable/SeparateChaining#readme) 8 | - [Double hashing](https://github.com/pavlosdais/Abstract-Data-Types/tree/main/modules/HashTable/DoubleHashing#readme) 9 | - [Using Red-Black Trees](https://github.com/pavlosdais/Abstract-Data-Types/tree/main/modules/HashTable/UsingRBT#readme) 10 | 11 | # Hash Functions 12 | A file with (good) hash functions for strings and integers is also included. 13 | -------------------------------------------------------------------------------- /modules/HashTable/SeparateChaining/README.md: -------------------------------------------------------------------------------- 1 | This is an implentation using [separate chaining](https://en.wikipedia.org/wiki/Hash_table#Separate_chaining). In separate chaining, each slot of the hash table is a linked list. When two or more elements are hashed to the same location (when a collision occurs), these elements are represented into a singly-linked list much like a chain. If there are n elements and b is the number of the buckets there would be n/b entries on each bucket. This value n/b is called the load factor that represents the load that is there on our map. So, theoretically, when the load factor increases so does the complexity of the operations. In order for the load factor to be kept low and remain almost constant complexity, we increase the number of buckets (approximately doubling) and rehash once the load factor increases to more than a pre-defined value (the default value here is 1.2). 2 | 3 | ## Performance 4 | separate chaining picture 5 | 6 | If n is the number of elements in the hash table: 7 | 8 | Algorithm | Average case | Worst case 9 | ---------- | ------- | ---------- 10 | Space | Θ(n) | O(n) 11 | Insert | Θ(1) | O(n) 12 | Remove | Θ(1) | O(n) 13 | Search | Θ(1) | O(n) 14 | -------------------------------------------------------------------------------- /modules/HashTable/SeparateChaining/hash_table.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "hash_table.h" 5 | 6 | // when max load factor is exceeded, rehashing operation occurs 7 | #define MAX_LOAD_FACTOR 0.8 8 | 9 | // bucket 10 | typedef struct node 11 | { 12 | Pointer data; // pointer to the data we are storing 13 | uint32_t hash_value; // hash value of the data 14 | struct node* next; // next element in the bucket (NULL if it's the last) 15 | } 16 | node; 17 | 18 | typedef struct hash_table 19 | { 20 | node** buckets; // buckets (lists) storing the data 21 | uint8_t capacity; // the capacity of the hash table - index to the "hash_sizes" array 22 | uint64_t elements; // number of elements in the hash table 23 | HashFunc hash; // function that hashes an element into a positive integer 24 | CompareFunc compare; // function that compares the elements 25 | DestroyFunc destroy; // function that destroys the elements, NULL if not 26 | } 27 | hash_table; 28 | 29 | // available number of buckets, preferably prime numbers since it has been proven they have better behavior 30 | static uint64_t hash_sizes[] = 31 | { 67, 131, 263, 509, 1021, 2053, 4093, 8179, 16369, 32749, 65521, 131071, 262147, 524287, 1048573, 2097143, 32 | 4194301, 8388593, 16777213, 33554467, 67108879, 134217757, 268435459, 536870923, 1073741827, 2147483647, 4294967291, 33 | 8589934583, 17179869143, 34359738337, 68719476731, 137438953447, 274877906899, 549755813881, 1099511627689, 2199023255531, 34 | 4398046511093, 8796093022151, 17592186044399, 35184372088777, 70368744177643, 140737488355213, 281474976710597, 562949953421231, 35 | 1125899906842597, 2251799813685119, 4503599627370449, 9007199254740881, 18014398509481951, 36028797018963913, 72057594037927931, 36 | 144115188075855859, 288230376151711717, 576460752303423433, 1152921504606846883, 2305843009213693951, 4611686018427387847 }; 37 | 38 | #define get_hash(i) (hash_sizes[i]) 39 | 40 | // function prototypes 41 | static inline void rehash(const HashTable ht); 42 | static inline bool hash_search(const HashTable ht, const Pointer value, uint32_t* hash_value); 43 | 44 | HashTable hash_create(const HashFunc hash, const CompareFunc compare, const DestroyFunc destroy) 45 | { 46 | assert(hash != NULL && compare != NULL); // a hash and compare function needs to be given 47 | 48 | HashTable ht = malloc(sizeof(hash_table)); 49 | assert(ht != NULL); // allocation failure 50 | 51 | ht->capacity = 0; 52 | ht->buckets = calloc(sizeof(node), get_hash(ht->capacity)); // allocate memory for the buckets 53 | assert(ht->buckets != NULL); // allocation failure 54 | 55 | ht->elements = 0; 56 | ht->hash = hash; 57 | ht->compare = compare; 58 | ht->destroy = destroy; 59 | 60 | return ht; 61 | } 62 | 63 | uint64_t hash_size(const HashTable ht) 64 | { 65 | assert(ht != NULL); 66 | return ht->elements; 67 | } 68 | 69 | bool is_ht_empty(const HashTable ht) 70 | { 71 | assert(ht != NULL); 72 | return ht->elements == 0; 73 | } 74 | 75 | bool hash_insert(const HashTable ht, const Pointer value) 76 | { 77 | // check to see if value already exists in the hash table 78 | uint32_t hash_value = 0; 79 | if (hash_search(ht, value, &hash_value)) // value already exists 80 | { 81 | if (ht->destroy != NULL) ht->destroy(value); 82 | return false; 83 | } 84 | 85 | if (((float)ht->elements / get_hash(ht->capacity)) > MAX_LOAD_FACTOR) // max load factor exceeded, try to rehash 86 | { 87 | if (get_hash(ht->capacity) != hash_sizes[sizeof(hash_sizes) / sizeof(hash_sizes[0]) - 1]) // if a new, available, size exists 88 | rehash(ht); // rehash 89 | } 90 | 91 | // insert value 92 | node* new_node = malloc(sizeof(node)); 93 | assert(new_node != NULL); // allocation failure 94 | 95 | // fill the node's contents 96 | new_node->data = value; 97 | new_node->hash_value = hash_value; 98 | 99 | // insert value at the start of the bucket 100 | const uint32_t bucket = hash_value % get_hash(ht->capacity); 101 | new_node->next = ht->buckets[bucket]; 102 | ht->buckets[bucket] = new_node; 103 | 104 | ht->elements++; // value inserted, increment the number of elements in the hash table 105 | return true; 106 | } 107 | 108 | static inline void rehash(HashTable ht) 109 | { 110 | node** old_buckets = ht->buckets; // save previous buckets 111 | 112 | uint8_t old_capacity = ht->capacity; 113 | (ht->capacity)++; // get the next size 114 | 115 | // create the new number of buckets 116 | ht->buckets = calloc(sizeof(node), get_hash(ht->capacity)); 117 | assert(ht->buckets != NULL); // allocation failure 118 | 119 | // start rehash operation 120 | for (uint64_t i = 0; i < get_hash(old_capacity); i++) 121 | { 122 | node* bkt = old_buckets[i]; 123 | 124 | while (bkt != NULL) 125 | { 126 | node* next = bkt->next; 127 | 128 | // reuse the bucket 129 | const uint32_t bucket = bkt->hash_value % get_hash(ht->capacity); 130 | bkt->next = ht->buckets[bucket]; 131 | ht->buckets[bucket] = bkt; 132 | 133 | bkt = next; 134 | } 135 | } 136 | free(old_buckets); 137 | } 138 | 139 | bool hash_remove(const HashTable ht, const Pointer value) 140 | { 141 | if (is_ht_empty(ht)) // hash table is empty, nothing to search 142 | return false; 143 | 144 | // hash to the find the potential bucket the value belongs to 145 | uint32_t hash_value = ht->hash(value) % get_hash(ht->capacity); 146 | 147 | node** bkt = &(ht->buckets[hash_value]); 148 | 149 | // search the bucket for the value 150 | while (*bkt != NULL) 151 | { 152 | Pointer bkt_value = (*bkt)->data; 153 | 154 | if (ht->compare(value, bkt_value) == 0) // value found 155 | { 156 | node* tmp = *bkt; 157 | (*bkt) = (*bkt)->next; 158 | 159 | // if a destroy function exists, destroy the value 160 | if (ht->destroy != NULL) 161 | ht->destroy(tmp->data); 162 | 163 | free(tmp); 164 | ht->elements--; // value removed, decrement the number of elements in the hash table 165 | return true; 166 | } 167 | else // search next value 168 | bkt = &((*bkt)->next); 169 | } 170 | return false; 171 | } 172 | 173 | bool hash_exists(const HashTable ht, const Pointer value) 174 | { 175 | uint32_t tmp = 0; 176 | return hash_search(ht, value, &tmp); 177 | } 178 | 179 | static inline bool hash_search(const HashTable ht, const Pointer value, uint32_t* hash_value) 180 | { 181 | *hash_value = ht->hash(value); 182 | if (is_ht_empty(ht)) // hash table is empty, nothing to search 183 | return false; 184 | 185 | node* bkt = ht->buckets[*hash_value % get_hash(ht->capacity)]; 186 | 187 | // search for the value in the bucket h 188 | while (bkt != NULL) 189 | { 190 | Pointer bkt_value = bkt->data; 191 | if (ht->compare(value, bkt_value) == 0) // value found 192 | return true; 193 | 194 | bkt = bkt->next; 195 | } 196 | 197 | return false; 198 | } 199 | 200 | DestroyFunc hash_set_destroy(const HashTable ht, const DestroyFunc new_destroy_func) 201 | { 202 | assert(ht != NULL); 203 | 204 | DestroyFunc old_destroy_func = ht->destroy; 205 | ht->destroy = new_destroy_func; 206 | return old_destroy_func; 207 | } 208 | 209 | void hash_destroy(const HashTable ht) 210 | { 211 | assert(ht != NULL); 212 | 213 | // destroy the buckets 214 | if (ht->elements != 0) 215 | { 216 | for (uint64_t i = 0 ;; i++) 217 | { 218 | node* bkt = ht->buckets[i]; 219 | while (bkt != NULL) 220 | { 221 | node* tmp = bkt; 222 | bkt = bkt->next; 223 | 224 | // if a destroy function exists, destroy the data 225 | if (ht->destroy != NULL) ht->destroy(tmp->data); 226 | 227 | free(tmp); 228 | --(ht->elements); 229 | } 230 | if (ht->elements == 0) break; // all elements deleted 231 | } 232 | } 233 | 234 | free(ht->buckets); 235 | free(ht); 236 | } 237 | -------------------------------------------------------------------------------- /modules/HashTable/SeparateChaining/hash_table.h: -------------------------------------------------------------------------------- 1 | #pragma once // include at most once 2 | 3 | #include 4 | #include 5 | 6 | 7 | typedef void* Pointer; 8 | 9 | // Pointer to function that compares 2 elements a and b and returns 0 if a and b are equal 10 | typedef int (*CompareFunc)(Pointer a, Pointer b); 11 | 12 | // Pointer to function that destroys an element value 13 | typedef void (*DestroyFunc)(Pointer value); 14 | 15 | // Pointer to function that hashes a value to a positive (unsigned) integer 16 | typedef unsigned int (*HashFunc)(Pointer value); 17 | 18 | typedef struct hash_table* HashTable; 19 | 20 | 21 | // creates hash table 22 | // -requires a hash function 23 | // a compare function 24 | // a destroy function (or NULL if you want to preserve the data) 25 | HashTable hash_create(const HashFunc, const CompareFunc, const DestroyFunc); 26 | 27 | // inserts value at the hash table 28 | // returns true if the value was inserted, false if it already exists 29 | bool hash_insert(const HashTable, const Pointer); 30 | 31 | // removes the value from the hash table and destroys its value if a destroy function was given 32 | // returns true if the value was deleted, false in any other case 33 | bool hash_remove(const HashTable, const Pointer); 34 | 35 | // returns true if value exists in the hash table, false otherwise 36 | bool hash_exists(const HashTable, const Pointer); 37 | 38 | // returns the number of elements in the hash table 39 | uint64_t hash_size(const HashTable); 40 | 41 | // returns true if the hash table is empty, false otherwise 42 | bool is_ht_empty(const HashTable); 43 | 44 | // changes the destroy function and returns the old one 45 | DestroyFunc hash_set_destroy(const HashTable, DestroyFunc); 46 | 47 | // destroys the memory used by the hash table 48 | void hash_destroy(const HashTable); 49 | -------------------------------------------------------------------------------- /modules/HashTable/UsingRBT/README.md: -------------------------------------------------------------------------------- 1 | In this implentation we use red-black trees in order to implement the buckets (instead of, for example, linked lists). This way, we combine the two data structures getting the best from both worlds. The downside of such an implementation is that in the vast majority of cases, there will be few objects in the bucket, and the insertion may cause a delay (due to extra mallocs, etc). So, as an optimization, we will keep the first FIXED_SIZE (eg the first 3) elements of each bucket in an array, and only if we have more will we insert them into the red-black tree. The biggest advantage of this implentation is that, even in the worst case scenario, we maintain logarithmic complexity on all operations. 2 | 3 | ## Performance 4 | If n is the number of elements in the hash table: 5 | 6 | Algorithm | Best case | Worst case 7 | ---------- | ------- | ---------- 8 | Space | Ω(n) | O(n) 9 | Insert | Ω(1) | O(log n) 10 | Remove | Ω(1) | O(log n) 11 | Search | Ω(1) | O(log n) 12 | -------------------------------------------------------------------------------- /modules/HashTable/UsingRBT/hash_table.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "hash_table.h" 5 | // red-black tree's include file (note that it might need to be updated according to its path) 6 | #include "../../RedBlackTree/RedBlackTree.h" 7 | 8 | #define FIXED_SIZE 3 // minimum number of elements in the array before inserting them at a rbt 9 | #define OVERFLOW_SIZE 4 // size at which elements are inserted at a rbt (FIXED_SIZE+1) 10 | 11 | // bucket 12 | typedef struct node 13 | { 14 | RBTree rbt; // red-black tree 15 | Pointer* data; // array 16 | uint8_t arr_el; // number of elements in the array (if it is equal to FIXED_SIZE, the elements are in the rbt) 17 | } 18 | node; 19 | 20 | typedef struct hash_table 21 | { 22 | node* buckets; // buckets (red-black trees) storing the data 23 | uint64_t capacity; // number of buckets 24 | uint64_t elements; // number of elements in the hash table 25 | HashFunc hash; // function that hashes an element into a positive integer 26 | CompareFunc compare; // function that compares the elements 27 | DestroyFunc destroy; // function that destroys the elements, NULL if not 28 | } 29 | hash_table; 30 | 31 | HashTable hash_create(const HashFunc hash, const CompareFunc compare, const DestroyFunc destroy) 32 | { 33 | assert(hash != NULL && compare != NULL); // a hash and compare function needs to be given 34 | 35 | HashTable ht = malloc(sizeof(hash_table)); 36 | assert(ht != NULL); // allocation failure 37 | 38 | ht->capacity = NUM_OF_BUCKETS; 39 | 40 | ht->buckets = calloc(sizeof(node), NUM_OF_BUCKETS); // allocate memory for the buckets 41 | assert(ht->buckets != NULL); // allocation failure 42 | 43 | // allocate memory for the buckets 44 | for(uint64_t i = 0; i < NUM_OF_BUCKETS; i++) 45 | { 46 | ht->buckets[i].data = calloc(sizeof(Pointer), FIXED_SIZE); 47 | assert(ht->buckets[i].data != NULL); // allocation failure 48 | } 49 | 50 | ht->capacity = NUM_OF_BUCKETS; 51 | ht->elements = 0; 52 | 53 | // initialize functions 54 | ht->compare = compare; 55 | ht->destroy = destroy; 56 | ht->hash = hash; 57 | } 58 | 59 | uint64_t hash_size(const HashTable ht) 60 | { 61 | assert(ht != NULL); 62 | return ht->elements; 63 | } 64 | 65 | bool is_ht_empty(const HashTable ht) 66 | { 67 | assert(ht != NULL); 68 | return ht->elements == 0; 69 | } 70 | 71 | bool hash_insert(const HashTable ht, const Pointer value) 72 | { 73 | assert(ht != NULL); 74 | 75 | // find the potential bucket the value belongs to 76 | uint64_t bucket = ht->hash(value) % ht->capacity; 77 | 78 | // insert at the rbt 79 | if (ht->buckets[bucket].arr_el == OVERFLOW_SIZE) 80 | { 81 | if (rbt_insert(ht->buckets[bucket].rbt, value)) 82 | { 83 | ht->elements++; // value inserted, increment the number of elements in the hash table 84 | return true; 85 | } 86 | } 87 | else // insert at the array 88 | { 89 | // search to see if value already exists 90 | int empty_space = -1; 91 | for (uint8_t i = 0; i < FIXED_SIZE; i++) 92 | { 93 | if (ht->buckets[bucket].data[i] == NULL) 94 | { 95 | if (empty_space == -1) 96 | empty_space = i; 97 | } 98 | else if (ht->compare(ht->buckets[bucket].data[i], value) == 0) // value already exists 99 | { 100 | // if a destroy function exists, destroy the value 101 | if (ht->destroy != NULL) 102 | ht->destroy(value); 103 | return false; 104 | } 105 | } 106 | 107 | // value does not already exist, insert operation 108 | ht->buckets[bucket].arr_el++; 109 | 110 | if (ht->buckets[bucket].arr_el == OVERFLOW_SIZE) // overflow 111 | { 112 | // move all the elemenets to a rbt 113 | ht->buckets[bucket].rbt = rbt_create(ht->compare, ht->destroy); 114 | for (uint8_t i = 0; i < FIXED_SIZE; i++) 115 | { 116 | if (ht->buckets[bucket].data[i] != NULL) 117 | rbt_insert(ht->buckets[bucket].rbt, ht->buckets[bucket].data[i]); 118 | } 119 | rbt_insert(ht->buckets[bucket].rbt, value); 120 | 121 | // data has now been moved to a rbt, no need for the array anymore 122 | free(ht->buckets[bucket].data); 123 | } 124 | else 125 | ht->buckets[bucket].data[empty_space] = value; 126 | 127 | ht->elements++; // value inserted, increment the number of elements in the hash table 128 | return true; 129 | } 130 | 131 | // value already exists in the hash table 132 | return false; 133 | } 134 | 135 | bool hash_remove(const HashTable ht, const Pointer value) 136 | { 137 | if (is_ht_empty(ht)) // hash table is empty, nothing to search 138 | return false; 139 | 140 | // find the potential bucket the value exists in 141 | uint64_t bucket = ht->hash(value) % ht->capacity; 142 | 143 | if (ht->buckets[bucket].arr_el == OVERFLOW_SIZE) 144 | { 145 | if (rbt_remove(ht->buckets[bucket].rbt, value)) 146 | { 147 | ht->elements--; // value removed, decrement the number of elements in the hash table 148 | return true; 149 | } 150 | } 151 | else 152 | { 153 | // search for the value 154 | bool found = false; 155 | uint8_t i = 0; 156 | for (i = 0; i < FIXED_SIZE; i++) 157 | { 158 | if (ht->buckets[bucket].data[i] != NULL && ht->compare(ht->buckets[bucket].data[i], value) == 0) 159 | { 160 | found = true; 161 | break; 162 | } 163 | } 164 | if (found) // value found 165 | { 166 | // if a destroy function exists, destroy the value 167 | if (ht->destroy != NULL) 168 | ht->destroy(ht->buckets[bucket].data[i]); 169 | 170 | ht->buckets[bucket].data[i] = NULL; // mark the spot empty 171 | ht->buckets[bucket].arr_el--; 172 | ht->elements--; // value removed, decrement the number of elements in the hash table 173 | return true; 174 | } 175 | } 176 | 177 | // value does not exist in the hash table 178 | return false; 179 | } 180 | 181 | bool hash_exists(const HashTable ht, const Pointer value) 182 | { 183 | if (is_ht_empty(ht)) // hash table is empty, nothing to search 184 | return false; 185 | 186 | // find the potential bucket the value exists in 187 | uint64_t bucket = ht->hash(value) % ht->capacity; 188 | 189 | // search the rbt 190 | if (ht->buckets[bucket].arr_el == OVERFLOW_SIZE) 191 | return rbt_exists(ht->buckets[bucket].rbt, value); 192 | else // search the array 193 | { 194 | for (uint8_t i = 0; i < FIXED_SIZE; i++) 195 | { 196 | if (ht->buckets[bucket].data[i] != NULL && ht->compare(ht->buckets[bucket].data[i], value) == 0) 197 | return true; // value found 198 | } 199 | } 200 | return false; // value not found 201 | } 202 | 203 | DestroyFunc hash_set_destroy(const HashTable ht, const DestroyFunc new_destroy_func) 204 | { 205 | assert(ht != NULL); 206 | 207 | for (uint64_t i = 0; i < ht->capacity; i++) 208 | { 209 | if (ht->buckets[i].arr_el == OVERFLOW_SIZE) // elements are at a rbt in this bucket 210 | rbt_set_destroy(ht->buckets[i].rbt, new_destroy_func); 211 | } 212 | 213 | DestroyFunc old_destroy_func = ht->destroy; 214 | ht->destroy = new_destroy_func; 215 | return old_destroy_func; 216 | } 217 | 218 | void hash_destroy(const HashTable ht) 219 | { 220 | assert(ht != NULL); 221 | 222 | for (uint64_t i = 0; i < ht->capacity; i++) 223 | { 224 | if (ht->buckets[i].arr_el != OVERFLOW_SIZE && ht->destroy != NULL) 225 | { 226 | for (uint8_t j = 0; j < FIXED_SIZE; j++) 227 | { 228 | if (ht->buckets[i].data[j] != NULL) 229 | ht->destroy(ht->buckets[i].data[j]); 230 | } 231 | } 232 | if (ht->buckets[i].arr_el == OVERFLOW_SIZE) // elements are at a rbt 233 | rbt_destroy(ht->buckets[i].rbt); 234 | else // elements are at an array 235 | free(ht->buckets[i].data); 236 | } 237 | free(ht->buckets); 238 | free(ht); 239 | } 240 | -------------------------------------------------------------------------------- /modules/HashTable/UsingRBT/hash_table.h: -------------------------------------------------------------------------------- 1 | #pragma once // include at most once 2 | 3 | #include 4 | #include 5 | 6 | 7 | typedef void* Pointer; 8 | 9 | // Pointer to function that compares 2 elements a and b and returns: 10 | // < 0 if a < b 11 | // 0 if a and b are equal 12 | // > 0 if a > b 13 | typedef int (*CompareFunc)(Pointer a, Pointer b); 14 | 15 | // Pointer to function that destroys an element value 16 | typedef void (*DestroyFunc)(Pointer value); 17 | 18 | // Pointer to function that hashes a value to a positive (unsigned) integer 19 | typedef unsigned int (*HashFunc)(Pointer value); 20 | 21 | // number of buckets used 22 | #define NUM_OF_BUCKETS 32749 23 | 24 | typedef struct hash_table* HashTable; 25 | 26 | 27 | // creates hash table 28 | // -requires a hash function 29 | // a compare function 30 | // a destroy function (or NULL if you want to preserve the data) 31 | HashTable hash_create(const HashFunc, const CompareFunc, const DestroyFunc); 32 | 33 | // inserts value at the hash table 34 | // returns true if the value was inserted, false if it already exists 35 | bool hash_insert(const HashTable, const Pointer); 36 | 37 | // removes the value from the hash table and destroys its value if a destroy function was given 38 | // returns true if the value was deleted, false in any other case 39 | bool hash_remove(const HashTable, const Pointer); 40 | 41 | // returns true if value exists in the hash table, false otherwise 42 | bool hash_exists(const HashTable, const Pointer); 43 | 44 | // returns the number of elements in the hash table 45 | uint64_t hash_size(const HashTable); 46 | 47 | // returns true if the hash table is empty, false otherwise 48 | bool is_ht_empty(const HashTable); 49 | 50 | // changes the destroy function and returns the old one 51 | DestroyFunc hash_set_destroy(const HashTable, DestroyFunc new_destroy_func); 52 | 53 | // destroys the memory used by the hash table 54 | void hash_destroy(const HashTable ht); 55 | -------------------------------------------------------------------------------- /modules/HashTable/hash_functions.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "hash_functions.h" 5 | 6 | // polynomial rolling hash function 7 | // source: https://www.geeksforgeeks.org/string-hashing-using-polynomial-rolling-hash-function/ 8 | unsigned int hash_string1(Pointer value) 9 | { 10 | const char* str = (*((const char**)value)); 11 | 12 | const int p = 111111, m = 1e9 + 7; 13 | unsigned int hash = 0; 14 | unsigned long p_pow = 1; 15 | for(int i = 0; str[i] != '\0'; i++) 16 | { 17 | hash = (hash + tolower(str[i]) * p_pow) % m; 18 | p_pow = (p_pow * p) % m; 19 | } 20 | return hash; 21 | } 22 | 23 | // djb2 hash function 24 | // source: http://www.cse.yorku.ca/~oz/hash.html 25 | unsigned int hash_string2(Pointer value) 26 | { 27 | const char* str = (*((const char**)value)); 28 | 29 | unsigned int hash = 5381; 30 | for(unsigned int i = 0; str[i] != '\0'; i++) 31 | hash = ((hash << 5) + hash) + str[i]; // hash*33 + c 32 | 33 | return hash; 34 | } 35 | 36 | // simple hash function that sums the characters of the string 37 | unsigned int hash_string3(Pointer value) 38 | { 39 | const char* str = (*((const char**)value)); 40 | 41 | unsigned int i, sum = 0; 42 | for(i = 0; str[i] != '\0'; i++) 43 | sum += str[i]; 44 | return sum; 45 | } 46 | 47 | // FNV-1a algorithm 48 | unsigned int hash_int1(Pointer value) 49 | { 50 | int val = (*((int*)value)); 51 | 52 | unsigned int hash = 2166136261u; // FNV offset basis 53 | const unsigned char* data = (unsigned char*)&val; 54 | for (int i = 0; i < sizeof(int); i++) 55 | { 56 | hash ^= data[i]; 57 | hash *= 16777619; // FNV prime 58 | } 59 | return hash; 60 | } 61 | 62 | unsigned int hash_int2(Pointer value) 63 | { 64 | int val = (*((int*)value)); 65 | 66 | val = val ^ (val >> 4); 67 | val = (val ^ 0xdeadbeef) + (val << 5); 68 | val = val ^ (val >> 11); 69 | return (unsigned int)val; 70 | } 71 | 72 | unsigned int hash_int3(Pointer value) 73 | { 74 | unsigned int hash = (unsigned int)(*((int*)value)); 75 | 76 | hash = (hash + 0x7ed55d16) + (hash << 12); 77 | hash = (hash ^ 0xc761c23c) ^ (hash >> 19); 78 | hash = (hash + 0x165667b1) + (hash << 5); 79 | hash = (hash + 0xd3a2646c) ^ (hash << 9); 80 | hash = (hash + 0xfd7046c5) + (hash << 3); 81 | hash = (hash ^ 0xb55a4f09) ^ (hash >> 16); 82 | return hash; 83 | } 84 | -------------------------------------------------------------------------------- /modules/HashTable/hash_functions.h: -------------------------------------------------------------------------------- 1 | #pragma once // include at most once 2 | 3 | typedef void* Pointer; 4 | 5 | // hashes an integer 6 | unsigned int hash_int1(Pointer); 7 | unsigned int hash_int2(Pointer); 8 | unsigned int hash_int3(Pointer); 9 | 10 | // hashes a string 11 | unsigned int hash_string1(Pointer); 12 | unsigned int hash_string2(Pointer); 13 | unsigned int hash_string3(Pointer); 14 | -------------------------------------------------------------------------------- /modules/PriorityQueue/README.md: -------------------------------------------------------------------------------- 1 | [Priority queue](https://en.wikipedia.org/wiki/Priority_queue) is an abstract data-type similar to a regular queue or stack data structure in which each element additionally has a "priority" associated with it. In a priority queue, an element with high priority is removed before an element with low priority. In this implementation, a binary heap is used. A binary heap is a binary tree with following properties: 2 | 3 | 1. A binary heap is a complete binary tree. This means all levels of the tree, except possibly the last one (deepest) are fully filled and if the last level of the tree is not complete, the nodes of that level are filled from left to right. 4 | 2. The key stored in each node is either greater than or equal to (≥) or less than or equal to (≤) the keys in the node's children, according to some order(dictated by the compare function). 5 | 6 | * Check an application of this ADT [here](https://github.com/pavlosdais/n-puzzle) 7 | 8 | # Performance 9 | priority queue picture 10 | 11 | If n is the number of elements in the priority queue: 12 | 13 | Algorithm | Average case | Worst case 14 | ---------- | ------- | ---------- 15 | Space | Θ(n) | O(n) 16 | Insert | Θ(log n) | O(n) 17 | Remove | Θ(log n) | O(log n) 18 | -------------------------------------------------------------------------------- /modules/PriorityQueue/pq.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "pq.h" 5 | 6 | // heap's minimum starting size 7 | #define MIN_SIZE 64 8 | 9 | #define ROOT 0 10 | #define find_parent(a) ((a-1)/2) 11 | #define find_left_child(a) (a*2 + 1) 12 | #define find_right_child(a) (a*2 + 2) 13 | 14 | typedef struct node 15 | { 16 | Pointer data; 17 | } 18 | node; 19 | 20 | typedef struct pq 21 | { 22 | node* arr; // array of nodes containing the data 23 | uint64_t curr_size; // current size of the heap 24 | uint64_t capacity; // max capacity of the heap 25 | CompareFunc compare; // function that compares the data - dictates the order of the elements 26 | DestroyFunc destroy; // function that destroys the elements, NULL if not 27 | } 28 | pq; 29 | 30 | // function prototypes 31 | static inline void swap_nodes(node*, node*); 32 | static inline void bubble_up(const PQueue, uint64_t); 33 | static void bubble_down(const PQueue, const uint64_t); 34 | 35 | PQueue pq_create(const CompareFunc compare, const DestroyFunc destroy) 36 | { 37 | assert(compare != NULL); 38 | 39 | PQueue PQ = malloc(sizeof(pq)); 40 | assert(PQ != NULL); // allocation failure 41 | 42 | // allocate memory for the array of nodes 43 | PQ->arr = calloc(MIN_SIZE, sizeof( *(PQ->arr)) ); 44 | 45 | assert(PQ->arr != NULL); // allocation failure 46 | 47 | PQ->curr_size = 0; 48 | PQ->capacity = MIN_SIZE; 49 | PQ->compare = compare; 50 | PQ->destroy = destroy; 51 | 52 | return PQ; 53 | } 54 | 55 | uint64_t pq_size(const PQueue PQ) 56 | { 57 | assert(PQ != NULL); 58 | return PQ->curr_size; 59 | } 60 | 61 | bool is_pq_empty(const PQueue PQ) 62 | { 63 | assert(PQ != NULL); 64 | return PQ->curr_size == 0; 65 | } 66 | 67 | Pointer pq_peek(const PQueue PQ) { return PQ->arr[ROOT].data; } 68 | 69 | void pq_insert(const PQueue PQ, const Pointer value) 70 | { 71 | assert(PQ != NULL); 72 | 73 | // heap is full, double its size 74 | if (PQ->curr_size == PQ->capacity) 75 | { 76 | PQ->capacity *= 2; 77 | 78 | PQ->arr = realloc(PQ->arr, PQ->capacity * sizeof(*(PQ->arr))); 79 | assert(PQ->arr != NULL); // allocation failure 80 | } 81 | 82 | PQ->arr[PQ->curr_size].data = value; 83 | 84 | // fix the heap 85 | bubble_up(PQ, PQ->curr_size); 86 | 87 | PQ->curr_size++; 88 | } 89 | 90 | static inline void bubble_up(const PQueue PQ, uint64_t node) 91 | { 92 | uint64_t parent = find_parent(node); 93 | 94 | // bubble up until you find a tree node 95 | while (node != ROOT && PQ->compare(PQ->arr[parent].data, PQ->arr[node].data) < 0) 96 | { 97 | swap_nodes(&(PQ->arr[parent]), &(PQ->arr[node])); 98 | 99 | node = parent; 100 | parent = find_parent(node); 101 | } 102 | } 103 | 104 | Pointer pq_remove(const PQueue PQ) 105 | { 106 | if (is_pq_empty(PQ)) // empty priority queue - nothing to remove 107 | return NULL; 108 | 109 | // save the element with the highest priority (which is at the root) and mark it as removed by making it NULL 110 | const Pointer hp = PQ->arr[ROOT].data; 111 | PQ->arr[ROOT].data = NULL; 112 | 113 | PQ->curr_size--; 114 | 115 | // root and far right leaf swap 116 | swap_nodes(&(PQ->arr[ROOT]), &(PQ->arr[PQ->curr_size])); 117 | 118 | bubble_down(PQ, ROOT); 119 | 120 | return hp; 121 | } 122 | 123 | static void bubble_down(const PQueue PQ, const uint64_t node) 124 | { 125 | uint64_t left_child = find_left_child(node); 126 | if (left_child >= PQ->curr_size) // children do not exist 127 | return; 128 | 129 | // find the child with the highest priority 130 | uint64_t right_child = find_right_child(node), max_child = left_child; 131 | if (right_child < PQ->curr_size && PQ->compare(PQ->arr[left_child].data, PQ->arr[right_child].data) < 0) 132 | max_child = right_child; 133 | 134 | // bubble down if the the child with the highest priority 135 | // has a higher priority than the current node 136 | if (PQ->compare(PQ->arr[node].data, PQ->arr[max_child].data) < 0) 137 | { 138 | swap_nodes(&(PQ->arr[node]), &(PQ->arr[max_child])); 139 | bubble_down(PQ, max_child); 140 | } 141 | } 142 | 143 | DestroyFunc pq_set_destroy(const PQueue PQ, const DestroyFunc new_destroy_func) 144 | { 145 | assert(PQ != NULL); 146 | 147 | DestroyFunc old_destroy_func = PQ->destroy; 148 | PQ->destroy = new_destroy_func; 149 | return old_destroy_func; 150 | } 151 | 152 | void pq_destroy(const PQueue PQ) 153 | { 154 | assert(PQ != NULL); 155 | 156 | // if a destroy function was given, destroy the data 157 | if (PQ->destroy != NULL) 158 | { 159 | for (uint64_t i = 0, size = PQ->curr_size+1; i < size; i++) 160 | PQ->destroy(PQ->arr[i].data); 161 | } 162 | free(PQ->arr); 163 | free(PQ); 164 | } 165 | 166 | static inline void swap_nodes(node* a, node* b) 167 | { 168 | node* tmp = a->data; 169 | a->data = b->data; 170 | b->data = tmp; 171 | } 172 | -------------------------------------------------------------------------------- /modules/PriorityQueue/pq.h: -------------------------------------------------------------------------------- 1 | #pragma once // include at most once 2 | 3 | #include 4 | #include 5 | 6 | 7 | typedef void* Pointer; 8 | 9 | // Pointer to function that compares 2 elements a and b and returns: 10 | // < 0 if a < b 11 | // 0 if a and b are equal 12 | // > 0 if a > b 13 | typedef int (*CompareFunc)(Pointer a, Pointer b); 14 | 15 | // Pointer to function that destroys an element value 16 | typedef void (*DestroyFunc)(Pointer value); 17 | 18 | typedef struct pq* PQueue; 19 | 20 | 21 | // creates priority queue 22 | // -requires a compare function 23 | // a destroy function (or NULL if you want to preserve the data) 24 | PQueue pq_create(const CompareFunc, const DestroyFunc); 25 | 26 | // inserts value at the priority queue 27 | void pq_insert(const PQueue, const Pointer); 28 | 29 | // returns the element with the highest priority as given by the compare function 30 | // or NULL if it's empty 31 | // it's important to note that once removed, the element is not destroyed by the 32 | // the destroy function (pq_destroy) 33 | Pointer pq_remove(const PQueue); 34 | 35 | // returns the size of the priority queue 36 | uint64_t pq_size(const PQueue); 37 | 38 | // returns true if the priority queue is empty, false otherwise 39 | bool is_pq_empty(const PQueue); 40 | 41 | // returns the element with the highest priority without removing it 42 | Pointer pq_peek(const PQueue); 43 | 44 | // changes the destroy function and returns the old one 45 | DestroyFunc pq_set_destroy(const PQueue, const DestroyFunc); 46 | 47 | // destroys memory used by the priority queue 48 | void pq_destroy(const PQueue); 49 | -------------------------------------------------------------------------------- /modules/Queue/README.md: -------------------------------------------------------------------------------- 1 | [Queue](https://en.wikipedia.org/wiki/Queue_(abstract_data_type)) is a linear data structure which follows a particular order in which the operations are performed. The order is First In First Out (FIFO). 2 | 3 | The 2 basic operations performed are: 4 | * Enqueue: Adds an item at the end of the queue. 5 | * Dequeue: Removes an item from the start of the queue. 6 | 7 | # Performance 8 | queue picture 9 | If n is the number of elements in the queue: 10 | 11 | Algorithm | Average case | Worst case 12 | ---------- | ------- | ---------- 13 | Space | Θ(n) | O(n) 14 | Enqueue | Θ(1) | O(1) 15 | Sorted Insert | Θ(n) | O(n) 16 | Dequeue | Θ(1) | O(1) 17 | -------------------------------------------------------------------------------- /modules/Queue/queue.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "queue.h" 5 | 6 | typedef struct QueueNode 7 | { 8 | Pointer value; 9 | struct QueueNode* next; 10 | } 11 | QueueNode; 12 | 13 | typedef struct queue 14 | { 15 | QueueNode* head; // head node 16 | QueueNode* tail; // tail node 17 | uint64_t num_of_elements; // number of elements in the queue 18 | DestroyFunc destroy; // function that destroys the elements, NULL if not 19 | } 20 | queue; 21 | 22 | Queue queue_create(const DestroyFunc destroy) 23 | { 24 | Queue Q = malloc(sizeof(queue)); 25 | assert(Q != NULL); // allocation failure 26 | 27 | Q->head = NULL; 28 | Q->tail = NULL; 29 | Q->destroy= destroy; 30 | Q->num_of_elements = 0; 31 | 32 | return Q; 33 | } 34 | 35 | uint64_t queue_size(const Queue Q) 36 | { 37 | assert(Q != NULL); 38 | return Q->num_of_elements; 39 | } 40 | 41 | bool is_queue_empty(const Queue Q) 42 | { 43 | assert(Q != NULL); 44 | return (Q->head == NULL); 45 | } 46 | 47 | void queue_enqueue(const Queue Q, const Pointer value) 48 | { 49 | assert(Q != NULL); 50 | 51 | // allocate memory for the new node 52 | QueueNode* new_node = malloc(sizeof(QueueNode)); 53 | assert(new_node != NULL); // allocation failure 54 | 55 | // fill the node 56 | new_node->value = value; 57 | new_node->next = NULL; 58 | 59 | if (Q->tail != NULL) // queue was not empty previously 60 | Q->tail->next = new_node; 61 | 62 | Q->tail = new_node; 63 | 64 | // queue was previously empty, reset 65 | if (is_queue_empty(Q)) 66 | Q->head = new_node; 67 | 68 | // pushing at the end was successful, increase the number of elements by 1 69 | Q->num_of_elements++; 70 | } 71 | 72 | void queue_sorted_insert(const Queue Q, const Pointer value, const CompareFunc compare) 73 | { 74 | assert(Q != NULL); 75 | 76 | QueueNode** Queue = &(Q->head); // pointer to the head of the node 77 | 78 | // create a new node 79 | QueueNode* new_node = malloc(sizeof(QueueNode)); 80 | assert(new_node != NULL); // allocation failure 81 | 82 | new_node->value = value; 83 | 84 | // empty queue - the new node becomes the head of the queue 85 | if (is_queue_empty(Q)) 86 | { 87 | new_node->next = NULL; 88 | (*Queue) = new_node; 89 | Q->num_of_elements = 1; 90 | return; 91 | } 92 | 93 | Q->num_of_elements++; 94 | 95 | // the head of the node has less priority than the new node, meaning the new value should be first 96 | if (compare(value, (*Queue)->value) < 0) 97 | { 98 | new_node->next = (*Queue); 99 | (*Queue) = new_node; 100 | return; 101 | } 102 | 103 | // in any other case, traverse the list and find the correct position to insert the new node 104 | QueueNode* tmp = (*Queue); 105 | while (tmp->next != NULL && compare(tmp->next->value, value) < 0) tmp = tmp->next; 106 | 107 | // put the node at its place 108 | new_node->next = tmp->next; 109 | tmp->next = new_node; 110 | } 111 | 112 | Pointer queue_dequeue(const Queue Q) 113 | { 114 | if (is_queue_empty(Q)) 115 | return NULL; 116 | 117 | QueueNode* tmp = Q->head; 118 | Pointer value = tmp->value; 119 | 120 | Q->head = Q->head->next; 121 | free(tmp); 122 | 123 | // queue is now empty, reset 124 | if (is_queue_empty(Q)) 125 | Q->tail = NULL; 126 | 127 | // decrease the number of elements by 1 and return the dequeued element 128 | Q->num_of_elements--; 129 | return value; 130 | } 131 | 132 | DestroyFunc queue_set_destroy(const Queue Q, const DestroyFunc new_destroy_func) 133 | { 134 | assert(Q != NULL); 135 | 136 | DestroyFunc old_destroy_func = Q->destroy; 137 | Q->destroy = new_destroy_func; 138 | return old_destroy_func; 139 | } 140 | 141 | void queue_destroy(const Queue Q) 142 | { 143 | assert(Q != NULL); 144 | 145 | while (Q->head != NULL) 146 | { 147 | // destroy the value of the node if a destroy function is given 148 | if (Q->destroy != NULL) 149 | Q->destroy(Q->head->value); 150 | 151 | QueueNode* tmp = Q->head->next; 152 | free(Q->head); 153 | Q->head = tmp; 154 | } 155 | free(Q); 156 | } 157 | -------------------------------------------------------------------------------- /modules/Queue/queue.h: -------------------------------------------------------------------------------- 1 | #pragma once // include at most once 2 | 3 | #include 4 | #include 5 | 6 | typedef void* Pointer; 7 | 8 | // Pointer to function that compares 2 elements a and b and returns: 9 | // < 0 if a < b 10 | // 0 if a and b are equal 11 | // > 0 if a > b 12 | typedef int (*CompareFunc)(Pointer a, Pointer b); 13 | 14 | // Pointer to function that destroys an element value 15 | typedef void (*DestroyFunc)(Pointer value); 16 | 17 | typedef struct queue* Queue; 18 | 19 | 20 | // creates queue 21 | // -requires a destroy function (or NULL if you want to preserve the data) 22 | Queue queue_create(const DestroyFunc); 23 | 24 | // enqueues value at the end of the queue 25 | void queue_enqueue(const Queue, const Pointer); 26 | 27 | // sorted insert of value, as indicated by the compare function 28 | void queue_sorted_insert(const Queue, const Pointer, const CompareFunc); 29 | 30 | // dequeues value from the start of the queue and returns it, returns NULL if the queue is empty 31 | Pointer queue_dequeue(const Queue); 32 | 33 | // returns the size of the queue 34 | uint64_t queue_size(const Queue); 35 | 36 | // returns true if the queue is empty, false otherwise 37 | bool is_queue_empty(const Queue); 38 | 39 | // changes the destroy function and returns the old one 40 | DestroyFunc queue_set_destroy(const Queue, const DestroyFunc); 41 | 42 | // destroys the memory used by the queue 43 | void queue_destroy(const Queue); 44 | -------------------------------------------------------------------------------- /modules/RedBlackTree/README.md: -------------------------------------------------------------------------------- 1 | [Red-Black Tree](https://en.wikipedia.org/wiki/Red%E2%80%93black_tree) is a binary search tree with one extra bit of storage per node: its color, which can be either RED or BLACK. By constraining the way nodes can be colored on any path from the root to a leaf, red-black trees ensure that no such path is more than twice as long as any other, so that the tree is approximately balanced. 2 | 3 | # Properties 4 | 1. Every node is colored either red or black. 5 | 2. The root of the tree is always black. 6 | 3. There are no two adjacent red nodes (A red node cannot have a red parent or red child). 7 | 4. Every path from a node (including root) to any of its leaf nodes has the same number of black nodes. 8 | 5. All leaf nodes are black nodes. 9 | 10 | When the tree is modified, the new tree is rearranged and repainted to restore the coloring properties that constrain how unbalanced the tree can become in the worst case. 11 | The properties are designed such that this rearranging and recoloring can be performed efficiently. 12 | 13 | # Performance 14 | Red-Black Tree picture 15 | 16 | Although the balance of the tree is not perfect, it is good enough to reduce the searching time and maintain logarithmic complexity.
17 | So, if n is the number of values in the tree: 18 | 19 | Algorithm | Average case | Worst case 20 | ---------- | ------- | ---------- 21 | Space | Θ(n) | O(n) 22 | Insert | Θ(log n) | O(log n) 23 | Remove | Θ(log n) | O(log n) 24 | Search | Θ(log n) | O(log n) 25 | 26 | # Learn more 27 | For more information as well as examples click [here](http://staff.ustc.edu.cn/~csli/graduate/algorithms/book6/chap14.htm). 28 | -------------------------------------------------------------------------------- /modules/RedBlackTree/RedBlackTree.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "RedBlackTree.h" 5 | 6 | // source: http://staff.ustc.edu.cn/~csli/graduate/algorithms/book6/chap14.htm 7 | 8 | typedef enum COLORS 9 | { 10 | RED, 11 | BLACK 12 | } 13 | COLORS; 14 | 15 | typedef struct tnode 16 | { 17 | Pointer data; 18 | COLORS col; // represents the color of the node (Red/Black) 19 | 20 | struct tnode *left, *right, *parent; 21 | } 22 | tnode; 23 | typedef struct tnode* RBTreeNode; 24 | 25 | struct Set 26 | { 27 | RBTreeNode root; // root node, NULL if the the tree is empty 28 | uint64_t size; // number of elements in the tree 29 | CompareFunc compare; // function that compares the elements - dictates the order of the elements 30 | DestroyFunc destroy; // function that destroys the elements, NULL if not 31 | }; 32 | 33 | // function prototypes 34 | static inline void right_rotation(RBTreeNode*, RBTreeNode*); 35 | static inline void left_rotation(RBTreeNode*, RBTreeNode*); 36 | static inline RBTreeNode find_successor(const RBTreeNode); 37 | static inline void shift_node(RBTreeNode*, const RBTreeNode, const RBTreeNode); 38 | static inline void fix_insert(RBTreeNode*, RBTreeNode*); 39 | static inline void fix_remove(RBTreeNode*, RBTreeNode*); 40 | 41 | tnode NULLNode = {NULL, BLACK, NULL, NULL, NULL}; // dummy leaf node 42 | 43 | RBTree rbt_create(const CompareFunc compare, const DestroyFunc destroy) 44 | { 45 | assert(compare != NULL); // a compare function needs to be given 46 | 47 | RBTree Tree = malloc(sizeof(struct Set)); 48 | assert(Tree != NULL); // allocation failure 49 | 50 | Tree->size = 0; 51 | Tree->root = NULL; 52 | Tree->compare = compare; 53 | Tree->destroy = destroy; 54 | 55 | return Tree; 56 | } 57 | 58 | uint64_t rbt_size(const RBTree Tree) 59 | { 60 | assert(Tree != NULL); 61 | return Tree->size; 62 | } 63 | 64 | bool is_rbt_empty(const RBTree Tree) 65 | { 66 | assert(Tree != NULL); 67 | return Tree->root == NULL; 68 | } 69 | 70 | // creates and returns node 71 | static inline RBTreeNode CreateNode() 72 | { 73 | RBTreeNode new_node = malloc(sizeof(tnode)); 74 | assert(new_node != NULL); // allocation failure 75 | 76 | new_node->col = RED; // default color is red 77 | new_node->left = &NULLNode; 78 | new_node->right = &NULLNode; 79 | return new_node; 80 | } 81 | 82 | RBTreeNode rbt_find_node(const RBTree Tree, const Pointer value) 83 | { 84 | if (Tree->size == 0) return false; 85 | 86 | RBTreeNode node = Tree->root; 87 | 88 | while (node != &NULLNode) 89 | { 90 | int comp = Tree->compare(value, node->data); 91 | if (comp == 0) // node->data == value 92 | return node; 93 | else if (comp < 0) // value < node->data 94 | node = node->left; 95 | else // value >= node->data 96 | node = node->right; 97 | } 98 | 99 | // value was not found 100 | return NULL; 101 | } 102 | 103 | Pointer rbt_node_value(const RBTreeNode rbt_node) 104 | { 105 | assert(rbt_node != NULL); 106 | return rbt_node->data; 107 | } 108 | 109 | bool rbt_insert(const RBTree Tree, const Pointer value) 110 | { 111 | assert(Tree != NULL); 112 | 113 | RBTreeNode* root = &(Tree->root); 114 | 115 | RBTreeNode new_node = CreateNode(); 116 | assert(new_node != NULL); // allocation failure 117 | 118 | new_node->data = value; 119 | if (*root == NULL) // empty tree 120 | { 121 | Tree->size = 1; 122 | new_node->col = BLACK; // root is black 123 | new_node->parent = NULL; // root's parent is NULL 124 | *root = new_node; 125 | return true; 126 | } 127 | 128 | // standard BST insertion - by the end the node prev will 129 | // be the parent of the node we will insert 130 | RBTreeNode prev = NULL, tmp = *root; 131 | 132 | int prev_comp, comp = Tree->compare(tmp->data, value); 133 | while(true) 134 | { 135 | prev = tmp; 136 | prev_comp = comp; 137 | 138 | if (comp == 0) // value already exists 139 | { 140 | free(new_node); 141 | 142 | // if a destroy function exists, destroy the value 143 | if (Tree->destroy != NULL) 144 | Tree->destroy(value); 145 | 146 | return false; 147 | } 148 | else if (comp < 0) // tmp->data < value 149 | tmp = tmp->right; 150 | else // tmp->data >= value 151 | tmp = tmp->left; 152 | 153 | if (tmp != &NULLNode) 154 | comp = Tree->compare(tmp->data, value); 155 | else break; 156 | } 157 | 158 | // save parent 159 | new_node->parent = prev; 160 | 161 | if (prev_comp < 0) 162 | prev->right = new_node; 163 | else 164 | prev->left = new_node; 165 | 166 | // fix possible violations 167 | fix_insert(root, &new_node); 168 | 169 | Tree->size++; // value inserted, increment the number of elements in the tree 170 | return true; 171 | } 172 | 173 | bool rbt_remove(RBTree Tree, Pointer value) 174 | { 175 | assert(Tree != NULL); 176 | 177 | RBTreeNode* root = &(Tree->root); 178 | 179 | RBTreeNode tmp = rbt_find_node(Tree, value); 180 | if (tmp == NULL) // value does not exist 181 | return false; 182 | 183 | RBTreeNode node_to_be_deleted = tmp; 184 | 185 | COLORS col = tmp->col; // save the color of the node that is about to be deleted 186 | if (node_to_be_deleted->left == &NULLNode) 187 | { 188 | tmp = node_to_be_deleted->right; 189 | shift_node(root, node_to_be_deleted, node_to_be_deleted->right); 190 | } 191 | else if (node_to_be_deleted->right == &NULLNode) 192 | { 193 | tmp = node_to_be_deleted->left; 194 | shift_node(root, node_to_be_deleted, tmp); 195 | } 196 | else // node has 2 children 197 | { 198 | // Find the successor of the node, swap it with the node we want to delete 199 | // and delete the successor instead 200 | RBTreeNode successor = find_successor(node_to_be_deleted); 201 | col = successor->col; // save the new color of the node 202 | tmp = successor->right; // save the right child of the node we want to delete 203 | successor->col = node_to_be_deleted->col; // keep the color same 204 | 205 | if (successor->parent == node_to_be_deleted) // successor's parent is the node that we want to delete 206 | tmp->parent = successor; 207 | else 208 | { 209 | shift_node(root, successor, successor->right); 210 | successor->right = node_to_be_deleted->right; 211 | successor->right->parent = successor; 212 | } 213 | 214 | shift_node(root, node_to_be_deleted, successor); 215 | successor->left = node_to_be_deleted->left; 216 | successor->left->parent = successor; 217 | successor->col = node_to_be_deleted->col; 218 | } 219 | 220 | if (Tree->destroy != NULL) 221 | Tree->destroy(node_to_be_deleted->data); 222 | free(node_to_be_deleted); 223 | 224 | if (col == BLACK) // no violations if the node deleted is red 225 | fix_remove(root, &tmp); // if node is black, fix violations 226 | 227 | Tree->size--; // value removed, decrement the number of elements in the tree 228 | return true; 229 | } 230 | 231 | // fix possible violations at insertion 232 | static inline void fix_insert(RBTreeNode* root, RBTreeNode* node) 233 | { 234 | while(((*node) != *root) && ((*node)->col == RED) && ((*node)->parent->col == RED)) 235 | { 236 | // store parent, grandparent and uncle 237 | RBTreeNode uncle = NULL, parent = (*node)->parent, grandparent = parent->parent; 238 | 239 | if (grandparent->left == parent) 240 | uncle = grandparent->right; 241 | else 242 | uncle = grandparent->left; 243 | 244 | if (uncle != NULL && uncle->col == RED) // uncle is red, recolor as follows: 245 | { 246 | parent->col = uncle->col = BLACK; // 1. Change the color of parent and uncle as black 247 | grandparent->col = RED; // 2. Change the color of the grandparent as red 248 | (*node) = grandparent; // 3. New node becomes its grandparent 249 | } 250 | else // uncle is black, rotation 251 | { 252 | // There are 4 cases: 253 | // 1. parent is left child of grandparent and node is left child of parent (LL) 254 | // 2. parent is left child of grandparent and node is right child of parent (LR) 255 | // 3. parent is right child of grandparent and node is right child of parent (RR) 256 | // 4. parent is right child of grandparent and node is left child of parent (RL) 257 | 258 | if (grandparent->left == parent) // parent is left child of grandparent - Lx 259 | { 260 | if (parent->right == (*node)) // LR 261 | { 262 | left_rotation(root, &parent); // Left rotation of parent 263 | (*node) = parent; 264 | parent = (*node)->parent; 265 | } 266 | // Apply the steps of LL 267 | right_rotation(root, &grandparent); // 1. Right rotation of grandparent 268 | parent->col = BLACK; // 2. Color parent black 269 | grandparent->col = RED; // and grandparent red 270 | (*node) = parent; 271 | } 272 | else // parent is right child of grandparent - Rx 273 | { 274 | if (parent->left == (*node)) // RL 275 | { 276 | right_rotation(root, &parent); // Right rotation of parent 277 | (*node) = parent; 278 | parent = (*node)->parent; 279 | } 280 | // Apply the steps of RR 281 | left_rotation(root, &grandparent); // 1. Left rotation of grandparent 282 | parent->col = BLACK; // 2. Color parent black 283 | grandparent->col = RED; // and grandparent red 284 | (*node) = parent; 285 | } 286 | } 287 | } 288 | 289 | (*root)->col = BLACK; // keep root black 290 | } 291 | 292 | // fix possible violations at removal 293 | static inline void fix_remove(RBTreeNode* root, RBTreeNode* node) 294 | { 295 | while ((*node) != *root && (*node)->col == BLACK) 296 | { 297 | // S = sibling, n = node 298 | if ((*node) == (*node)->parent->right) 299 | { 300 | RBTreeNode sibling = (*node)->parent->left; 301 | 302 | // -CASE 1: 303 | // S is red. Since s must have black children, we can switch the colors 304 | // of s and its parent and then perform a right-rotation on the parent 305 | // without violating any of the red-black properties. The new sibling of 306 | // node, one of s's children, is now black, and thus we have converted case 307 | // 1 into case 2, 3, or 4. 308 | if (sibling->col == RED) 309 | { 310 | sibling->col = BLACK; 311 | (*node)->parent->col = RED; 312 | right_rotation(root, &((*node)->parent)); 313 | sibling = (*node)->parent->left; 314 | } 315 | 316 | // -CASE 2: 317 | // S is black by now. If both of the children of s are black, since 318 | // s is black we make s red leaving only n with black color and s with 319 | // red. We then repeat the while loop with the parent as the node. 320 | if (sibling->right->col == BLACK && sibling->left->col == BLACK) 321 | { 322 | sibling->col = RED; 323 | (*node) = (*node)->parent; 324 | } 325 | else 326 | { 327 | // -CASE 3: 328 | // N is black, its right child is red and its left child is black. 329 | // We can switch the colors of the sibling and its right child and then 330 | // perform a left rotation on the sibling without violating any of the 331 | // red-black properties. The new sibling s of n is now a black node with 332 | // a red left child, and thus case 3 is transformed into case 4. 333 | if (sibling->left->col == BLACK) 334 | { 335 | sibling->col = RED; 336 | sibling->right->col = BLACK; 337 | left_rotation(root, &sibling); 338 | sibling = (*node)->parent->left; 339 | } 340 | 341 | // -CASE 4: 342 | // N's sibling is black and s's left child is red. By making some color 343 | // changes and performing a right rotation on its parent, we can remove the 344 | // extra black on node without violating any of the red-black properties. 345 | // We then terminate the loop by making the node the root. 346 | sibling->col = (*node)->parent->col; 347 | (*node)->parent->col = sibling->left->col = BLACK; 348 | right_rotation(root, &((*node)->parent)); 349 | (*node) = (*root); // terminate 350 | } 351 | } 352 | else // node == parent->left 353 | { 354 | // The cases here are mirror of the previous ones, if we swap left with right. 355 | RBTreeNode sibling = (*node)->parent->right; 356 | 357 | // CASE 1 358 | if (sibling->col == RED) 359 | { 360 | sibling->col = BLACK; 361 | (*node)->parent->col = RED; 362 | left_rotation(root, &((*node)->parent)); 363 | sibling = (*node)->parent->right; 364 | } 365 | 366 | // CASE 2 367 | if (sibling->right->col == BLACK && sibling->left->col == BLACK) 368 | { 369 | sibling->col = RED; 370 | (*node) = (*node)->parent; 371 | } 372 | else 373 | { 374 | // CASE 3 375 | if (sibling->right->col == BLACK) 376 | { 377 | sibling->col = RED; 378 | sibling->left->col = BLACK; 379 | right_rotation(root, &sibling); 380 | sibling = (*node)->parent->right; 381 | } 382 | 383 | // CASE 4 384 | sibling->col = (*node)->parent->col; 385 | (*node)->parent->col = sibling->right->col = BLACK; 386 | left_rotation(root, &((*node)->parent)); 387 | (*node) = (*root); // terminate 388 | } 389 | } 390 | } 391 | 392 | (*node)->col = BLACK; 393 | } 394 | 395 | // destroys the nodes of the tree and their data, if a destroy function is given 396 | static void destroy_nodes(const RBTreeNode node, const DestroyFunc destroy_data) 397 | { 398 | if (node == &NULLNode) // base case 399 | return; 400 | 401 | // first destroy the children, then the data 402 | destroy_nodes(node->left, destroy_data); 403 | destroy_nodes(node->right, destroy_data); 404 | 405 | // if a destroy function was given, destroy the data 406 | if (destroy_data != NULL) 407 | destroy_data(node->data); 408 | 409 | free(node); 410 | } 411 | 412 | void rbt_destroy(const RBTree Tree) 413 | { 414 | assert(Tree != NULL); 415 | 416 | if (Tree->root != NULL) 417 | destroy_nodes(Tree->root, Tree->destroy); // destroy the nodes 418 | free(Tree); // then the tree 419 | } 420 | 421 | bool rbt_exists(const RBTree Tree, const Pointer value) 422 | { 423 | assert(Tree != NULL); 424 | 425 | if (rbt_find_node(Tree, value) == NULL) return false; 426 | return true; 427 | } 428 | 429 | static inline RBTreeNode node_max(const RBTreeNode node) 430 | { 431 | RBTreeNode tmp = node; 432 | while (tmp->right != &NULLNode) 433 | tmp = tmp->right; 434 | 435 | return tmp; 436 | } 437 | 438 | static inline RBTreeNode node_min(const RBTreeNode node) 439 | { 440 | RBTreeNode tmp = node; 441 | while (tmp->left != &NULLNode) 442 | tmp = tmp->left; 443 | 444 | return tmp; 445 | } 446 | 447 | static RBTreeNode find_predecessor(const RBTreeNode node) 448 | { 449 | return node_max(node->left); 450 | } 451 | 452 | static inline RBTreeNode find_successor(const RBTreeNode node) 453 | { 454 | return node_min(node->right); 455 | } 456 | 457 | RBTreeNode rbt_find_previous(RBTreeNode target) 458 | { 459 | if (target->left != &NULLNode) 460 | return find_predecessor(target); 461 | 462 | // left tree does not exist, the next in order node is one of the ancestors 463 | RBTreeNode parent = target->parent; 464 | while(parent != NULL && target == parent->left) 465 | { 466 | target = parent; 467 | parent = parent->parent; 468 | } 469 | 470 | return parent; 471 | } 472 | 473 | RBTreeNode rbt_find_next(RBTreeNode target) 474 | { 475 | if (target->right != &NULLNode) 476 | return find_successor(target); 477 | 478 | // right tree does not exist, the previous in order node is one of the predecessors 479 | RBTreeNode parent = target->parent; 480 | while(parent != NULL && target == parent->right) 481 | { 482 | target = parent; 483 | parent = parent->parent; 484 | } 485 | 486 | return parent; 487 | } 488 | 489 | RBTreeNode rbt_first(const RBTree Tree) 490 | { 491 | assert(Tree != NULL); 492 | 493 | if (Tree->size == 0) return NULL; 494 | 495 | return node_min(Tree->root); 496 | } 497 | 498 | RBTreeNode rbt_last(const RBTree Tree) 499 | { 500 | assert(Tree != NULL); 501 | 502 | if (Tree->size == 0) return NULL; 503 | 504 | return node_max(Tree->root); 505 | } 506 | 507 | DestroyFunc rbt_set_destroy(const RBTree Tree, const DestroyFunc new_destroy_func) 508 | { 509 | assert(Tree != NULL); 510 | 511 | DestroyFunc old_destroy_func = Tree->destroy; 512 | Tree->destroy = new_destroy_func; 513 | return old_destroy_func; 514 | } 515 | 516 | // node b takes a's place 517 | static inline void shift_node(RBTreeNode* root, const RBTreeNode a, const RBTreeNode b) 518 | { 519 | if (a->parent == NULL) 520 | *root = b; 521 | else if (a == a->parent->right) 522 | a->parent->right = b; 523 | else 524 | a->parent->left = b; 525 | 526 | b->parent = a->parent; 527 | } 528 | 529 | // right rotation at node 530 | static inline void right_rotation(RBTreeNode* root, RBTreeNode* node) 531 | { 532 | RBTreeNode left_child = (*node)->left; 533 | (*node)->left = left_child->right; 534 | 535 | if (left_child->right != &NULLNode) 536 | left_child->right->parent = (*node); 537 | 538 | left_child->parent = (*node)->parent; 539 | 540 | if (*node == *root) 541 | (*root) = left_child; 542 | else if ((*node) == (*node)->parent->left) 543 | (*node)->parent->left = left_child; 544 | else 545 | (*node)->parent->right = left_child; 546 | 547 | left_child->right = (*node); 548 | (*node)->parent = left_child; 549 | } 550 | 551 | // left rotation at node 552 | static inline void left_rotation(RBTreeNode* root, RBTreeNode* node) 553 | { 554 | RBTreeNode right_child = (*node)->right; 555 | (*node)->right = right_child->left; 556 | 557 | if (right_child->left != &NULLNode) 558 | right_child->left->parent = (*node); 559 | 560 | right_child->parent = (*node)->parent; 561 | 562 | if ((*node) == *root) 563 | (*root) = right_child; 564 | else if ((*node) == (*node)->parent->left) 565 | (*node)->parent->left = right_child; 566 | else 567 | (*node)->parent->right = right_child; 568 | 569 | right_child->left = (*node); 570 | (*node)->parent = right_child; 571 | } 572 | -------------------------------------------------------------------------------- /modules/RedBlackTree/RedBlackTree.h: -------------------------------------------------------------------------------- 1 | #pragma once // include at most once 2 | 3 | #include 4 | #include 5 | 6 | 7 | typedef void* Pointer; 8 | 9 | // Pointer to function that compares 2 elements a and b and returns: 10 | // < 0 if a < b 11 | // 0 if a and b are equal 12 | // > 0 if a > b 13 | typedef int (*CompareFunc)(Pointer a, Pointer b); 14 | 15 | // Pointer to function that destroys an element value 16 | typedef void (*DestroyFunc)(Pointer value); 17 | 18 | typedef struct Set* RBTree; 19 | 20 | 21 | // creates red-black tree 22 | // -requires a compare function 23 | // a destroy function (or NULL if you want to preserve the data) 24 | RBTree rbt_create(const CompareFunc, const DestroyFunc); 25 | 26 | // returns true if the item is inserted, in any other case false 27 | bool rbt_insert(const RBTree, const Pointer); 28 | 29 | // returns true if the item is deleted, in any other case false 30 | bool rbt_remove(const RBTree, const Pointer); 31 | 32 | // returns true if the value exists, false otherwise 33 | bool rbt_exists(const RBTree, const Pointer); 34 | 35 | // returns the size of the tree 36 | uint64_t rbt_size(const RBTree); 37 | 38 | // returns true if the tree is empty, false otherwise 39 | bool is_rbt_empty(const RBTree); 40 | 41 | // changes the destroy function and returns the old one 42 | DestroyFunc rbt_set_destroy(const RBTree, const DestroyFunc); 43 | 44 | // destroys the memory used by the tree 45 | void rbt_destroy(const RBTree); 46 | 47 | ////////////////////////////// 48 | // tree traversal functions // 49 | ////////////////////////////// 50 | typedef struct tnode* RBTreeNode; // node handle 51 | 52 | // returns the value of the node 53 | Pointer rbt_node_value(const RBTreeNode); 54 | 55 | // returns the node with that value, if it exists, otherwise NULL 56 | RBTreeNode rbt_find_node(const RBTree, const Pointer); 57 | 58 | // returns the previous, in order, node of target or NULL if there is no previous value 59 | RBTreeNode rbt_find_previous(const RBTreeNode); 60 | 61 | // returns the next, in order, node of target or NULL if there is no next value 62 | RBTreeNode rbt_find_next(const RBTreeNode); 63 | 64 | // returns the node with the lowest value 65 | RBTreeNode rbt_first(const RBTree); 66 | 67 | // returns the node with the highest value 68 | RBTreeNode rbt_last(const RBTree); 69 | -------------------------------------------------------------------------------- /modules/Stack/README.md: -------------------------------------------------------------------------------- 1 | [Stack](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)) is a linear data structure which follows a particular order in which the operations are performed. The order is LIFO (Last In First Out).
2 | 3 | The 2 basic operations performed are: 4 | * Push: Adds an item at the top of the stack. 5 | * Pop: Removes an item from the top of the stack. 6 | 7 | # Performance 8 | stack picture 9 | If n is the number of elements in the stack: 10 | 11 | Algorithm | Average case | Worst case 12 | ---------- | ------- | ---------- 13 | Space | Θ(n) | O(n) 14 | Push | Θ(1) | O(1) 15 | Pop | Θ(1) | O(1) 16 | -------------------------------------------------------------------------------- /modules/Stack/stack.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "stack.h" 5 | 6 | typedef struct StackNode 7 | { 8 | Pointer value; 9 | struct StackNode* next; 10 | } 11 | StackNode; 12 | typedef struct StackNode* StackNodePointer; 13 | 14 | typedef struct StackSet 15 | { 16 | StackNodePointer top; // node at the top of the stack, NULL if the stack is empty 17 | uint64_t size; // number of elements in the stack 18 | DestroyFunc destroy; // function that destroys the elements, NULL if not 19 | } 20 | StackSet; 21 | 22 | Stack stack_create(const DestroyFunc destroy) 23 | { 24 | Stack S = malloc(sizeof(StackSet)); 25 | assert(S != NULL); // allocation failure 26 | 27 | S->top = NULL; 28 | S->destroy = destroy; 29 | S->size = 0; 30 | 31 | return S; 32 | } 33 | 34 | uint64_t stack_size(const Stack S) 35 | { 36 | assert(S != NULL); 37 | return S->size; 38 | } 39 | 40 | bool is_stack_empty(const Stack S) 41 | { 42 | assert(S != NULL); 43 | return (S->top == NULL); 44 | } 45 | 46 | Pointer stack_top_value(const Stack S) 47 | { 48 | assert(S != NULL); 49 | return (S->top->value); 50 | } 51 | 52 | DestroyFunc stack_set_destroy(const Stack S, const DestroyFunc new_destroy_func) 53 | { 54 | assert(S != NULL); 55 | 56 | DestroyFunc old_destroy_func = S->destroy; 57 | S->destroy = new_destroy_func; 58 | return old_destroy_func; 59 | } 60 | 61 | void stack_push(const Stack S, const Pointer value) 62 | { 63 | assert(S != NULL); 64 | 65 | StackNodePointer* head = &(S->top); 66 | 67 | // create a new node 68 | StackNodePointer new_node = malloc(sizeof(StackNode)); 69 | assert(new_node != NULL); // allocation failure 70 | 71 | // fill the node's contents 72 | new_node->value = value; 73 | 74 | new_node->next = *head; 75 | *head = new_node; 76 | 77 | S->size++; // value pushed, increment the number of elements in the stack 78 | } 79 | 80 | Pointer stack_pop(const Stack S) 81 | { 82 | if (is_stack_empty(S)) 83 | return NULL; 84 | 85 | StackNodePointer head = S->top; 86 | 87 | Pointer data = head->value; 88 | StackNodePointer tmp = head; 89 | 90 | S->top = S->top->next; 91 | 92 | free(tmp); 93 | 94 | S->size--; // value popped, decrement the number of elements in the stack 95 | return data; 96 | } 97 | 98 | void stack_destroy(const Stack S) 99 | { 100 | assert(S != NULL); 101 | 102 | StackNodePointer head = S->top; 103 | 104 | while(head != NULL) 105 | { 106 | StackNodePointer tmp = head; 107 | 108 | head = head->next; 109 | 110 | if (S->destroy != NULL) 111 | S->destroy(tmp->value); 112 | free(tmp); 113 | } 114 | 115 | free(S); 116 | } 117 | -------------------------------------------------------------------------------- /modules/Stack/stack.h: -------------------------------------------------------------------------------- 1 | #pragma once // include at most once 2 | 3 | #include 4 | #include 5 | 6 | typedef void* Pointer; 7 | 8 | // Pointer to function that destroys an element value 9 | typedef void (*DestroyFunc)(Pointer value); 10 | 11 | typedef struct StackSet* Stack; 12 | 13 | 14 | // creates stack 15 | // -requires a destroy function (or NULL if you want to preserve the data) 16 | Stack stack_create(const DestroyFunc); 17 | 18 | // pushes value at the top of the stack 19 | void stack_push(const Stack, const Pointer); 20 | 21 | // pops value from the top of the stack and returns it, returns NULL if the stack is empty 22 | Pointer stack_pop(const Stack); 23 | 24 | // returns the size of the stack 25 | uint64_t stack_size(const Stack); 26 | 27 | // returns true if the stack is empty, false otherwise 28 | bool is_stack_empty(const Stack); 29 | 30 | // returns the value at the top of the stack 31 | Pointer stack_top_value(const Stack); 32 | 33 | // changes the destroy function and returns the old one 34 | DestroyFunc stack_set_destroy(const Stack, const DestroyFunc); 35 | 36 | // destroys the memory used by the stack 37 | void stack_destroy(const Stack); 38 | -------------------------------------------------------------------------------- /modules/Vector/README.md: -------------------------------------------------------------------------------- 1 | [Vector](https://en.wikipedia.org/wiki/Dynamic_array) (or dynamic array) is a data structure that allows elements to be added or removed. Vectors overcome the limit of static arrays, which have a fixed capacity that needs to be specified at allocation. In this implementation this limitation is overcomed by by doubling. 2 | 3 | # Performance 4 | vector picture 5 | 6 | If n is the number of elements in the vector: 7 | 8 | Algorithm | Average case | Worst case 9 | ---------- | ------- | ---------- 10 | Space | Θ(n) | O(n) 11 | Push Back | Θ(1) | O(n) 12 | Clear/set at | Θ(1) | O(1) 13 | Sort | Θ(n logn) | O(n2) 14 | Binary Search | Θ(logn) | O(logn) 15 | Search | Θ(n) | O(n) 16 | -------------------------------------------------------------------------------- /modules/Vector/vector.c: -------------------------------------------------------------------------------- 1 | #include "vector.h" 2 | #include 3 | #include 4 | #include 5 | 6 | // the starting capacity of the vector 7 | #define STARTING_CAPACITY 64 8 | 9 | typedef struct n 10 | { 11 | Pointer data; 12 | } 13 | n; 14 | typedef struct n* node; 15 | 16 | struct vector_struct 17 | { 18 | node arr; // array of nodes containing the data 19 | uint64_t size; // current size of vector 20 | uint64_t capacity; // capacity of the vector 21 | uint64_t elements; // current number of elements in the vector 22 | CompareFunc compare; // function that compares the elements (used for sort) 23 | DestroyFunc destroy; // function that destroys the elements, NULL if not 24 | }; 25 | 26 | // make sure the index is within the bounds of the vector's array 27 | #define SAFE_INDEX(vector, index) (assert(index < vector->size)) 28 | 29 | Vector vector_create(DestroyFunc destroy) 30 | { 31 | Vector vec = malloc(sizeof(struct vector_struct)); 32 | assert(vec != NULL); // allocation failure 33 | 34 | vec->arr = calloc(STARTING_CAPACITY, sizeof(*(vec->arr))); 35 | assert(vec->arr != NULL); // allocation failure 36 | 37 | // initialize the vector 38 | vec->destroy = destroy; 39 | vec->capacity = STARTING_CAPACITY; 40 | vec->size = vec->elements = 0; 41 | vec->compare = NULL; 42 | return vec; 43 | } 44 | 45 | uint64_t vector_size(const Vector vector) 46 | { 47 | assert(vector != NULL); 48 | return vector->elements; 49 | } 50 | 51 | Pointer vector_at(const Vector vector, const uint64_t index) 52 | { 53 | assert(vector != NULL); 54 | SAFE_INDEX(vector, index); // make sure a valid index was given 55 | 56 | return vector->arr[index].data; 57 | } 58 | 59 | void vector_set_at(const Vector vector, const uint64_t index, const Pointer data) 60 | { 61 | assert(vector != NULL); 62 | SAFE_INDEX(vector, index); // make sure a valid index was given 63 | 64 | if (vector->arr[index].data != NULL) // an element already exists there 65 | { 66 | if (vector->destroy != NULL) 67 | vector->destroy(vector->arr[index].data); 68 | } 69 | else // free spot 70 | vector->elements++; 71 | 72 | vector->arr[index].data = data; 73 | } 74 | 75 | bool vector_clear_at(const Vector vector, const uint64_t index) 76 | { 77 | assert(vector != NULL); 78 | SAFE_INDEX(vector, index); // make sure a valid index was given 79 | 80 | // make sure an element exists in the index 81 | if (vector->arr[index].data != NULL) 82 | { 83 | if (vector->destroy != NULL) // a destroy function exists, clear the data 84 | vector->destroy(vector->arr[index].data); 85 | 86 | vector->arr[index].data = NULL; 87 | vector->elements--; 88 | return true; 89 | } 90 | return false; 91 | } 92 | 93 | DestroyFunc vector_set_destroy(const Vector vector, const DestroyFunc new_destroy) 94 | { 95 | assert(vector != NULL); 96 | 97 | DestroyFunc old_destroy = vector->destroy; 98 | vector->destroy = new_destroy; 99 | return old_destroy; 100 | } 101 | 102 | bool is_vector_empty(const Vector vector) 103 | { 104 | assert(vector != NULL); 105 | return vector->elements == 0; 106 | } 107 | 108 | void vector_push_back(const Vector vector, const Pointer data) 109 | { 110 | assert(vector != NULL); 111 | 112 | // array is full, double its size 113 | if (vector->size == vector->capacity) 114 | { 115 | vector->capacity *= 2; 116 | vector->arr = realloc(vector->arr, vector->capacity * sizeof(*(vector->arr))); 117 | } 118 | 119 | // insert data 120 | vector->arr[(vector->size)++].data = data; 121 | vector->elements++; 122 | } 123 | 124 | bool vector_delete(const Vector vector, const Pointer data, const CompareFunc compare) 125 | { 126 | assert(vector != NULL); 127 | 128 | if (vector->elements == 0) return false; 129 | 130 | // linear search for the element 131 | uint64_t num_of_elements = vector->elements; 132 | for (uint64_t element_ind = 0 ;; element_ind++) 133 | { 134 | if (vector->arr[element_ind].data != NULL) 135 | { 136 | if (compare(vector->arr[element_ind].data, data) == 0) // data found 137 | { 138 | // if a destroy function was given, destroy the element 139 | if (vector->destroy != NULL) 140 | vector->destroy(vector->arr[element_ind].data); 141 | 142 | vector->arr[element_ind].data = NULL; 143 | return true; 144 | } 145 | if ((--num_of_elements) == 0) break; 146 | } 147 | } 148 | return false; 149 | } 150 | 151 | // swaps the value of node a & b 152 | static inline void swap_nodes(const node a, const node b) 153 | { 154 | node tmp = a->data; 155 | a->data = b->data; 156 | b->data = tmp; 157 | } 158 | 159 | static inline int partition(const Vector vector, const int left, const int right) 160 | { 161 | const n pivot = vector->arr[right]; 162 | int i = left-1; 163 | 164 | for (int j = left; j < right; j++) 165 | { 166 | if (vector->compare(vector->arr[j].data, pivot.data) < 0) 167 | swap_nodes(&(vector->arr[++i]), &(vector->arr[j])); 168 | } 169 | swap_nodes(&(vector->arr[i+1]), &(vector->arr[right])); 170 | 171 | return i+1; 172 | } 173 | 174 | static void quicksort(const Vector vector, const int left, const int right) 175 | { 176 | if (left < right) 177 | { 178 | int pi = partition(vector, left, right); 179 | 180 | quicksort(vector, left, pi-1); 181 | quicksort(vector, pi+1, right); 182 | } 183 | } 184 | 185 | void vector_sort(const Vector vector, const CompareFunc compare) 186 | { 187 | assert(vector != NULL); 188 | 189 | vector->compare = compare; 190 | 191 | // sort the vector using quick sort 192 | quicksort(vector, 0, vector->size-1); 193 | } 194 | 195 | bool vector_binary_search(const Vector vector, const Pointer data, const CompareFunc compare) 196 | { 197 | assert(vector != NULL); 198 | 199 | uint64_t low = 0, high = vector->size-1; 200 | while (low <= high) 201 | { 202 | const uint64_t mid = low + (high-low) / 2; 203 | const int cmp = compare(vector->arr[mid].data, data); 204 | 205 | if (cmp == 0) // found the element 206 | return true; 207 | else if (cmp > 0) 208 | high = mid-1; 209 | else 210 | low = mid+1; 211 | } 212 | return false; 213 | } 214 | 215 | bool vector_search(const Vector vector, const Pointer data, const CompareFunc compare) 216 | { 217 | assert(vector != NULL); 218 | 219 | if (vector->elements == 0) return false; 220 | 221 | // linear search 222 | uint64_t num_of_elements = vector->elements; 223 | for (uint64_t element_ind = 0 ;; element_ind++) 224 | { 225 | if (vector->arr[element_ind].data != NULL) 226 | { 227 | if (compare(vector->arr[element_ind].data, data) == 0) return true; // data found 228 | if ((--num_of_elements) == 0) break; 229 | } 230 | } 231 | return false; 232 | } 233 | 234 | void vector_destroy(const Vector vector) 235 | { 236 | assert(vector != NULL); 237 | 238 | // first destroy the data, if a destroy function was given 239 | if (vector->destroy != NULL && vector->elements != 0) 240 | { 241 | for (uint64_t element_ind = 0 ;; element_ind++) 242 | { 243 | if (vector->arr[element_ind].data != NULL) 244 | { 245 | vector->destroy(vector->arr[element_ind].data); 246 | if ((--(vector->elements)) == 0) break; // all of the elements are deleted 247 | } 248 | } 249 | } 250 | 251 | // destroy the rest of the vector 252 | free(vector->arr); 253 | free(vector); 254 | } 255 | -------------------------------------------------------------------------------- /modules/Vector/vector.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | typedef void* Pointer; 7 | 8 | // Pointer to function that compares 2 elements a and b and returns: 9 | // < 0 if a < b 10 | // 0 if a and b are equal 11 | // > 0 if a > b 12 | typedef int (*CompareFunc)(Pointer a, Pointer b); 13 | 14 | // Pointer to function that destroys an element value 15 | typedef void (*DestroyFunc)(Pointer value); 16 | 17 | typedef struct vector_struct* Vector; 18 | 19 | 20 | // creates vector 21 | // -requires a destroy function (or NULL if you want to preserve the data) 22 | Vector vector_create(const DestroyFunc); 23 | 24 | // inserts the element at the back of the vector 25 | void vector_push_back(const Vector, const Pointer); 26 | 27 | // sets the element at the given index (if there is an element there, it destroys it provided that a destroy function was given) 28 | void vector_set_at(const Vector, const uint64_t, const Pointer); 29 | 30 | // returns the element at the given index (NULL if no element exists there) 31 | Pointer vector_at(const Vector, const uint64_t); 32 | 33 | // if the an element exists at the given index, it clears it and returns true (also destroys it provided that a destroy function was given) 34 | // otherwise returns false 35 | bool vector_clear_at(const Vector, const uint64_t); 36 | 37 | // returns the number of elements the vector currently stores 38 | uint64_t vector_size(const Vector); 39 | 40 | // returns true if the vector is empty, false otherwise 41 | bool is_vector_empty(const Vector); 42 | 43 | // searches for the element and removes it 44 | // returns true if the element is deleted, false if not 45 | bool vector_delete(const Vector, const Pointer, const CompareFunc); 46 | 47 | // searches the vector using linear search 48 | // returns true if the element is found, false if not 49 | bool vector_search(const Vector, const Pointer, const CompareFunc); 50 | 51 | // sorts the vector using the compare function given 52 | void vector_sort(const Vector, const CompareFunc); 53 | 54 | // searches the vector using binary search (requires the vector to be sorted, unidentified behaviour if not) 55 | // returns true if found, false if not 56 | bool vector_binary_search(const Vector, const Pointer, const CompareFunc); 57 | 58 | // changes the destroy function and returns the old one 59 | DestroyFunc vector_set_destroy(const Vector, const DestroyFunc); 60 | 61 | // destroys memory used by the vector 62 | void vector_destroy(const Vector); 63 | -------------------------------------------------------------------------------- /tests/include/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include "acutest.h" 6 | 7 | #define calc_time(cur_time) (((double)(clock() - cur_time))/CLOCKS_PER_SEC) 8 | 9 | static inline int* allocate_array(unsigned int num_of_elements) 10 | { 11 | int* arr = malloc(sizeof(int) * num_of_elements); 12 | assert(arr != NULL); // allocation failure 13 | 14 | return arr; 15 | } 16 | 17 | // creates an ordered array eg. [0, 1, 2.. num_of_elements-1] 18 | static inline int* create_ordered_array(unsigned int num_of_elements) 19 | { 20 | int* arr = allocate_array(num_of_elements); 21 | 22 | for (unsigned int i = 0; i < num_of_elements; i++) 23 | arr[i] = i; 24 | 25 | return arr; 26 | } 27 | 28 | // creates a completely random array (duplicates allowed) 29 | static inline int* create_random_array(unsigned int num_of_elements) 30 | { 31 | int* arr = allocate_array(num_of_elements); 32 | 33 | for (unsigned int i = 0; i < num_of_elements; i++) 34 | arr[i] = rand() % RAND_MAX; 35 | 36 | return arr; 37 | } 38 | 39 | // creates a shuffled random array with elements in range [0, num_of_elements-1] 40 | static inline int* create_shuffled_array(unsigned int num_of_elements) 41 | { 42 | int* arr = create_ordered_array(num_of_elements); 43 | 44 | // shuffle the array 45 | for (unsigned int i = 0; i < num_of_elements; i++) 46 | { 47 | // create random spot for the elements to swap places 48 | unsigned int new_spot = i + rand() / (RAND_MAX / (num_of_elements-i) + 1); 49 | 50 | int tmp = arr[i]; 51 | arr[i] = arr[new_spot]; 52 | arr[new_spot] = tmp; 53 | } 54 | 55 | return arr; 56 | } 57 | 58 | // allocates memory for an integer 59 | int* createData(int a) 60 | { 61 | int* val = malloc(sizeof(int)); 62 | assert(val != NULL); // allocation failure 63 | 64 | *val = a; 65 | return val; 66 | } 67 | 68 | // compare function 69 | int compareFunction(void* v1, void* v2) { return *((int*)v1) - *((int*)v2); } 70 | -------------------------------------------------------------------------------- /tests/makefile: -------------------------------------------------------------------------------- 1 | # tested ADT 2 | # Vector/ Stack/ Queue/ PriorityQueue/ RedBlackTree/ HashTable/ BloomFilter/ DirectedGraph/ UndirectedGraph/ WeightedUndirectedGraph 3 | ADT ?= HashTable 4 | 5 | # compiler settings 6 | CC = gcc 7 | CFLAGS = 8 | 9 | # ADTlib directory path 10 | LIB = ../lib 11 | 12 | # executable program 13 | EXEC = test 14 | 15 | # object file 16 | OBJS = test_$(ADT).o 17 | 18 | $(ADT): $(OBJS) 19 | $(CC) $(CFLAGS) -o $(EXEC) $(OBJS) -L. $(LIB)/ADTlib.a 20 | 21 | .PHONY: run help clear 22 | 23 | # run the test 24 | run: $(ADT) 25 | ./$(EXEC) 26 | 27 | # run valgrind - check for memory errors 28 | help: $(EXEC) 29 | valgrind valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose ./$(EXEC) 30 | 31 | # delete all files created during the test of the ADT 32 | clear: 33 | rm -f $(OBJS) $(EXEC) 34 | -------------------------------------------------------------------------------- /tests/test_BloomFilter.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../lib/ADT.h" 3 | #include "./include/common.h" 4 | 5 | #define NUM_OF_ELEMENTS 100000 6 | #define BLOOM_SIZE NUM_OF_ELEMENTS*5 7 | 8 | void test_create(void) 9 | { 10 | HashFunc hash_functions[] = {hash_int1, hash_int2, hash_int3}; 11 | bloom_filter bf = bf_create(100000, hash_functions, sizeof(hash_functions)/sizeof(HashFunc)); 12 | TEST_ASSERT(bf != NULL); 13 | bf_destroy(bf); 14 | } 15 | 16 | void test_insert(void) 17 | { 18 | HashFunc hash_functions[] = {hash_int1, hash_int2, hash_int3}; 19 | bloom_filter bf = bf_create(BLOOM_SIZE, hash_functions, sizeof(hash_functions)/sizeof(HashFunc)); 20 | 21 | int* arr = create_ordered_array(NUM_OF_ELEMENTS); 22 | for (uint32_t i = 0; i < NUM_OF_ELEMENTS; i++) 23 | bf_insert(bf, &arr[i]); 24 | 25 | unsigned int new_start = NUM_OF_ELEMENTS; 26 | unsigned int new_end = NUM_OF_ELEMENTS*2; 27 | int* new_array = create_ordered_array(new_end); 28 | 29 | unsigned int false_positives = 0; 30 | for (uint32_t i = new_start; i < new_end; i++) 31 | if (bf_exists(bf, new_array+i)) 32 | false_positives++; 33 | 34 | printf("False positives: %d\n", false_positives); 35 | 36 | // free memory used 37 | bf_destroy(bf); 38 | free(arr); 39 | free(new_array); 40 | } 41 | 42 | TEST_LIST = { 43 | { "create", test_create }, 44 | { "insert", test_insert }, 45 | { NULL, NULL } 46 | }; 47 | -------------------------------------------------------------------------------- /tests/test_DirectedGraph.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../lib/ADT.h" 4 | 5 | // function prototype 6 | void visit(Vertex x); 7 | 8 | int main(void) 9 | { 10 | // create graph 11 | dir_graph A = dg_create(6, visit); 12 | 13 | // the directed graph: 14 | // https://cgi.di.uoa.gr/~k08/manolis/2021-2022/lectures/Graphs.pdf , page 139 15 | dg_insert(A, 1, 4); 16 | dg_insert(A, 1, 2); 17 | dg_insert(A, 2, 3); 18 | dg_insert(A, 5, 2); 19 | dg_insert(A, 3, 4); 20 | dg_insert(A, 3, 1); 21 | 22 | // print graph 23 | printf("\nGraph:\n"); 24 | dg_print(A); printf("\n"); 25 | 26 | // dfs at graph 27 | printf("DFS:\n"); 28 | dg_dfs(A); printf("\n"); 29 | 30 | // print topological ordering of graph 31 | printf("Topological ordering of the graph:\n"); 32 | dg_bts(A); printf("\n"); 33 | 34 | // print strongly connected components 35 | printf("Strongly-Connected Components:\n"); 36 | dg_scc(A); 37 | 38 | // free the graph 39 | dg_destroy(A); 40 | 41 | return 0; 42 | } 43 | 44 | void visit(Vertex x) 45 | { 46 | printf("%d ", x); 47 | } -------------------------------------------------------------------------------- /tests/test_HashTable.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../lib/ADT.h" 3 | #include "./include/common.h" 4 | 5 | #define NUM_OF_ELEMENTS 2000000 6 | 7 | void test_create(void) 8 | { 9 | HashTable ht = hash_create(hash_int1, compareFunction, free); 10 | TEST_ASSERT(ht != NULL); 11 | TEST_ASSERT(hash_size(ht) == 0 && is_ht_empty(ht)); 12 | hash_destroy(ht); 13 | } 14 | 15 | void test_insert(void) 16 | { 17 | // create hash table 18 | HashTable ht = hash_create(hash_int1, compareFunction, free); 19 | 20 | time_t t; 21 | srand((unsigned) time(&t)); 22 | 23 | int* arr = create_shuffled_array(NUM_OF_ELEMENTS); 24 | 25 | clock_t cur_time = clock(); 26 | 27 | for (uint32_t i = 0; i < NUM_OF_ELEMENTS; i++) 28 | { 29 | // the value does not exist 30 | TEST_ASSERT(!hash_exists(ht, arr+i)); 31 | 32 | // insert the value 33 | hash_insert(ht, createData(arr[i])); 34 | 35 | // the value now exists 36 | TEST_ASSERT(hash_exists(ht, arr+i)); 37 | 38 | // the size has changed 39 | TEST_ASSERT(hash_size(ht) == i+1); 40 | } 41 | 42 | double time_insert = calc_time(cur_time); // calculate insert time 43 | 44 | // free memory used 45 | hash_destroy(ht); 46 | free(arr); 47 | 48 | // report time taken 49 | printf("\n\nInsertion took %f seconds to complete\n", time_insert); 50 | } 51 | 52 | void test_remove(void) 53 | { 54 | // create hash table 55 | HashTable ht = hash_create(hash_int1, compareFunction, free); 56 | 57 | time_t t; 58 | srand((unsigned) time(&t)); 59 | 60 | int* arr = create_shuffled_array(NUM_OF_ELEMENTS); 61 | 62 | for (uint32_t i = 0; i < NUM_OF_ELEMENTS; i++) 63 | hash_insert(ht, createData(arr[i])); 64 | 65 | clock_t cur_time = clock(); 66 | 67 | for (uint32_t i = 0; i < NUM_OF_ELEMENTS; i++) 68 | { 69 | // the value exists 70 | TEST_ASSERT(hash_exists(ht, arr+i)); 71 | 72 | // remove the value 73 | hash_remove(ht, arr+i); 74 | 75 | // the value now does not exist 76 | TEST_ASSERT(!hash_exists(ht, arr+i)); 77 | 78 | // the size has changed 79 | TEST_ASSERT(hash_size(ht) == NUM_OF_ELEMENTS-i-1); 80 | } 81 | 82 | double time_insert = calc_time(cur_time); // calculate remove time 83 | 84 | // free memory used 85 | hash_destroy(ht); 86 | free(arr); 87 | 88 | // report time taken 89 | printf("\n\nRemove took %f seconds to complete\n", time_insert); 90 | } 91 | 92 | TEST_LIST = { 93 | { "create", test_create }, 94 | { "insert", test_insert }, 95 | { "remove", test_remove }, 96 | { NULL, NULL } 97 | }; 98 | -------------------------------------------------------------------------------- /tests/test_PriorityQueue.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../lib/ADT.h" 3 | #include "./include/common.h" 4 | 5 | #define NUM_OF_ELEMENTS 10000000 6 | 7 | void test_create(void) 8 | { 9 | PQueue pq = pq_create(compareFunction, free); 10 | TEST_ASSERT(pq != NULL); 11 | TEST_ASSERT(pq_size(pq) == 0 && is_pq_empty(pq)); 12 | pq_destroy(pq); 13 | } 14 | 15 | void test_insert(void) 16 | { 17 | // create hash table 18 | PQueue pq = pq_create(compareFunction, free); 19 | 20 | time_t t; 21 | srand((unsigned) time(&t)); 22 | 23 | int* arr = create_random_array(NUM_OF_ELEMENTS); 24 | 25 | clock_t cur_time = clock(); 26 | 27 | for (uint32_t i = 0; i < NUM_OF_ELEMENTS; i++) 28 | { 29 | // insert the value 30 | pq_insert(pq, createData(arr[i])); 31 | 32 | // the size has changed 33 | TEST_ASSERT(pq_size(pq) == i+1); 34 | } 35 | 36 | double time_insert = calc_time(cur_time); // calculate insert time 37 | 38 | // free memory used 39 | pq_destroy(pq); 40 | free(arr); 41 | 42 | // report time taken 43 | printf("\n\nInsertion took %f seconds to complete\n", time_insert); 44 | } 45 | 46 | void test_remove(void) 47 | { 48 | // create hash table 49 | PQueue pq = pq_create(compareFunction, free); 50 | 51 | time_t t; 52 | srand((unsigned) time(&t)); 53 | 54 | int* arr = create_ordered_array(NUM_OF_ELEMENTS); 55 | 56 | for (uint32_t i = 0; i < NUM_OF_ELEMENTS; i++) 57 | pq_insert(pq, createData(arr[i])); 58 | 59 | clock_t cur_time = clock(); 60 | 61 | int* element = NULL; 62 | for (uint32_t i = NUM_OF_ELEMENTS-1; i > 0; i--) 63 | { 64 | element = pq_remove(pq); 65 | TEST_ASSERT(*element == arr[i]); 66 | free(element); 67 | 68 | // the size has changed 69 | TEST_ASSERT(pq_size(pq) == i); 70 | } 71 | 72 | double time_insert = calc_time(cur_time); // calculate remove time 73 | 74 | // free memory used 75 | free(arr); 76 | pq_destroy(pq); 77 | 78 | // report time taken 79 | printf("\n\nRemove took %f seconds to complete\n", time_insert); 80 | } 81 | 82 | TEST_LIST = { 83 | { "create", test_create }, 84 | { "insert", test_insert }, 85 | { "remove", test_remove }, 86 | { NULL, NULL } 87 | }; 88 | -------------------------------------------------------------------------------- /tests/test_Queue.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../lib/ADT.h" 3 | #include "./include/common.h" 4 | 5 | #define NUM_OF_ELEMENTS 1000000 6 | 7 | void test_create(void) 8 | { 9 | Queue Q = queue_create(free); 10 | TEST_ASSERT(Q != NULL); 11 | TEST_ASSERT(queue_size(Q) == 0 && is_queue_empty(Q)); 12 | queue_destroy(Q); 13 | } 14 | 15 | void test_enqueue(void) 16 | { 17 | // create queue 18 | Queue Q = queue_create(free); 19 | 20 | time_t t; 21 | srand((unsigned) time(&t)); 22 | 23 | int* arr = create_random_array(NUM_OF_ELEMENTS); 24 | 25 | clock_t cur_time = clock(); 26 | 27 | for (uint32_t i = 0; i < NUM_OF_ELEMENTS; i++) 28 | { 29 | // insert the value 30 | queue_enqueue(Q, createData(arr[i])); 31 | 32 | // the size has changed 33 | TEST_ASSERT(queue_size(Q) == i+1); 34 | } 35 | 36 | double time_insert = calc_time(cur_time); // calculate insert time 37 | 38 | // free memory used 39 | queue_destroy(Q); 40 | free(arr); 41 | 42 | // report time taken 43 | printf("\n\nEnqueue took %f seconds to complete\n", time_insert); 44 | } 45 | 46 | void test_dequeue(void) 47 | { 48 | // create queue 49 | Queue Q = queue_create(free); 50 | 51 | time_t t; 52 | srand((unsigned) time(&t)); 53 | 54 | int* arr = create_random_array(NUM_OF_ELEMENTS); 55 | 56 | clock_t cur_time = clock(); 57 | 58 | for (uint32_t i = 0; i < NUM_OF_ELEMENTS; i++) 59 | queue_enqueue(Q, createData(arr[i])); 60 | 61 | int* element = NULL; 62 | for (uint32_t i = 0; i < NUM_OF_ELEMENTS; i++) 63 | { 64 | // dequeue the value 65 | element = queue_dequeue(Q); 66 | TEST_ASSERT(*element == arr[i]); 67 | free(element); 68 | 69 | // the size has changed 70 | TEST_ASSERT(queue_size(Q) == NUM_OF_ELEMENTS-i-1); 71 | } 72 | 73 | double time_insert = calc_time(cur_time); // calculate insert time 74 | 75 | // free memory used 76 | queue_destroy(Q); 77 | free(arr); 78 | 79 | // report time taken 80 | printf("\n\nDequeue took %f seconds to complete\n", time_insert); 81 | } 82 | 83 | TEST_LIST = { 84 | { "create", test_create }, 85 | { "enqueue", test_enqueue }, 86 | { "dequeue", test_dequeue }, 87 | { NULL, NULL } 88 | }; 89 | -------------------------------------------------------------------------------- /tests/test_RedBlackTree.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../lib/ADT.h" 3 | #include "./include/common.h" 4 | 5 | #define NUM_OF_ELEMENTS 100000 6 | 7 | void test_create(void) 8 | { 9 | RBTree rbt = rbt_create(compareFunction, free); 10 | TEST_ASSERT(rbt != NULL); 11 | TEST_ASSERT(rbt_size(rbt) == 0 && is_rbt_empty(rbt)); 12 | rbt_destroy(rbt); 13 | } 14 | 15 | void test_insert(void) 16 | { 17 | // create create rbt 18 | RBTree rbt = rbt_create(compareFunction, free); 19 | 20 | time_t t; 21 | srand((unsigned) time(&t)); 22 | 23 | int* arr = create_shuffled_array(NUM_OF_ELEMENTS); 24 | 25 | clock_t cur_time = clock(); 26 | 27 | for (uint32_t i = 0; i < NUM_OF_ELEMENTS; i++) 28 | { 29 | // the value does not exist 30 | TEST_ASSERT(!rbt_exists(rbt, arr+i)); 31 | 32 | // insert the value 33 | rbt_insert(rbt, createData(arr[i])); 34 | 35 | // the value now exists 36 | TEST_ASSERT(rbt_exists(rbt, arr+i)); 37 | 38 | // the size has changed 39 | TEST_ASSERT(rbt_size(rbt) == i+1); 40 | } 41 | 42 | double time_insert = calc_time(cur_time); // calculate insert time 43 | 44 | // free memory used 45 | rbt_destroy(rbt); 46 | free(arr); 47 | 48 | // report time taken 49 | printf("\n\nInsertion took %f seconds to complete\n", time_insert); 50 | } 51 | 52 | void test_remove(void) 53 | { 54 | // create create rbt 55 | RBTree rbt = rbt_create(compareFunction, free); 56 | 57 | time_t t; 58 | srand((unsigned) time(&t)); 59 | 60 | int* arr = create_shuffled_array(NUM_OF_ELEMENTS); 61 | 62 | clock_t cur_time = clock(); 63 | 64 | for (uint32_t i = 0; i < NUM_OF_ELEMENTS; i++) 65 | rbt_insert(rbt, createData(arr[i])); 66 | 67 | for (uint32_t i = 0; i < NUM_OF_ELEMENTS; i++) 68 | { 69 | // the value exists 70 | TEST_ASSERT(rbt_exists(rbt, arr+i)); 71 | 72 | // remove the value 73 | rbt_remove(rbt, arr+i); 74 | 75 | // the value now does not exist 76 | TEST_ASSERT(!rbt_exists(rbt, arr+i)); 77 | 78 | // the size has changed 79 | TEST_ASSERT(rbt_size(rbt) == NUM_OF_ELEMENTS-i-1); 80 | } 81 | 82 | double time_insert = calc_time(cur_time); // calculate remove time 83 | 84 | // free memory used 85 | rbt_destroy(rbt); 86 | free(arr); 87 | 88 | // report time taken 89 | printf("\n\nDelete took %f seconds to complete\n", time_insert); 90 | } 91 | 92 | void test_traversal(void) 93 | { 94 | // create rbt 95 | RBTree rbt = rbt_create(compareFunction, free); 96 | 97 | time_t t; 98 | srand((unsigned) time(&t)); 99 | 100 | int* arr = create_shuffled_array(NUM_OF_ELEMENTS); 101 | 102 | for (uint32_t i = 0; i < NUM_OF_ELEMENTS; i++) 103 | rbt_insert(rbt, createData(arr[i])); 104 | 105 | // test forward traversal 106 | int value = 0; 107 | for (RBTreeNode node = rbt_first(rbt); node != NULL; node = rbt_find_next(node), value++) 108 | TEST_ASSERT( *((int*)rbt_node_value(node)) == value); 109 | 110 | // test backward traversal 111 | value = NUM_OF_ELEMENTS-1; 112 | for (RBTreeNode node = rbt_last(rbt); node != NULL; node = rbt_find_previous(node), value--) 113 | TEST_ASSERT( *((int*)rbt_node_value(node)) == value); 114 | 115 | // free memory used 116 | rbt_destroy(rbt); 117 | free(arr); 118 | } 119 | 120 | TEST_LIST = { 121 | { "create", test_create }, 122 | { "insert", test_insert }, 123 | { "remove", test_remove }, 124 | { "traversal", test_traversal }, 125 | { NULL, NULL } 126 | }; 127 | -------------------------------------------------------------------------------- /tests/test_Stack.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../lib/ADT.h" 3 | #include "./include/common.h" 4 | 5 | #define NUM_OF_ELEMENTS 1000000 6 | 7 | void test_create(void) 8 | { 9 | Stack st = stack_create(free); 10 | TEST_ASSERT(st != NULL); 11 | TEST_ASSERT(stack_size(st) == 0 && is_stack_empty(st)); 12 | stack_destroy(st); 13 | } 14 | 15 | void test_push(void) 16 | { 17 | // create stack 18 | Stack st = stack_create(free); 19 | 20 | time_t t; 21 | srand((unsigned) time(&t)); 22 | 23 | int* arr = create_random_array(NUM_OF_ELEMENTS); 24 | 25 | clock_t cur_time = clock(); 26 | 27 | for (uint32_t i = 0; i < NUM_OF_ELEMENTS; i++) 28 | { 29 | // push the value 30 | stack_push(st, createData(arr[i])); 31 | 32 | // the size has changed 33 | TEST_ASSERT(stack_size(st) == i+1); 34 | } 35 | 36 | double time_insert = calc_time(cur_time); // calculate insert time 37 | 38 | // free memory used 39 | stack_destroy(st); 40 | free(arr); 41 | 42 | // report time taken 43 | printf("\n\nPush took %f seconds to complete\n", time_insert); 44 | } 45 | 46 | void test_pop(void) 47 | { 48 | // create stack 49 | Stack st = stack_create(free); 50 | 51 | time_t t; 52 | srand((unsigned) time(&t)); 53 | 54 | int* arr = create_random_array(NUM_OF_ELEMENTS); 55 | 56 | clock_t cur_time = clock(); 57 | 58 | for (uint32_t i = 0; i < NUM_OF_ELEMENTS; i++) 59 | stack_push(st, createData(arr[i])); 60 | 61 | int* element = NULL; 62 | for (uint32_t i = NUM_OF_ELEMENTS-1; i > 0; i--) 63 | { 64 | // pop the value 65 | element = stack_pop(st); 66 | TEST_ASSERT( *element == arr[i]); 67 | free(element); 68 | 69 | // the size has changed 70 | TEST_ASSERT(stack_size(st) == i); 71 | } 72 | 73 | double time_insert = calc_time(cur_time); // calculate insert time 74 | 75 | // free memory used 76 | stack_destroy(st); 77 | free(arr); 78 | 79 | // report time taken 80 | printf("\n\nPop took %f seconds to complete\n", time_insert); 81 | } 82 | 83 | TEST_LIST = { 84 | { "create", test_create }, 85 | { "push", test_push }, 86 | { "pop", test_pop }, 87 | { NULL, NULL } 88 | }; 89 | -------------------------------------------------------------------------------- /tests/test_UndirectedGraph.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../lib/ADT.h" 4 | 5 | // function prototype 6 | void visit(Vertex x); 7 | 8 | int main(void) 9 | { 10 | // create graph 11 | undir_graph A = ug_create(5, visit); 12 | 13 | // random graph 14 | ug_insert(A, 4, 0); 15 | ug_insert(A, 2, 1); 16 | ug_insert(A, 3, 2); 17 | ug_insert(A, 2, 0); 18 | 19 | // print graph 20 | ug_print(A); printf("\n"); 21 | 22 | // perfrom simple path check between vertices (3-1) 23 | ug_simplepathcheck(A, 3, 1); 24 | 25 | // free graph 26 | ug_destroy(A); 27 | 28 | return 0; 29 | } 30 | 31 | void visit(Vertex x) 32 | { 33 | printf("%d ", x); 34 | } -------------------------------------------------------------------------------- /tests/test_Vector.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../lib/ADT.h" 3 | #include "./include/common.h" 4 | 5 | #define NUM_OF_ELEMENTS 20000 6 | 7 | void test_create(void) 8 | { 9 | Vector vec = vector_create(free); 10 | TEST_ASSERT(vec != NULL); 11 | TEST_ASSERT(vector_size(vec) == 0 && is_vector_empty(vec)); 12 | vector_destroy(vec); 13 | } 14 | 15 | void test_push_back(void) 16 | { 17 | // create vector 18 | Vector vec = vector_create(free); 19 | 20 | time_t t; 21 | srand((unsigned) time(&t)); 22 | 23 | int* arr = create_random_array(NUM_OF_ELEMENTS); 24 | 25 | for (uint64_t i = 0; i < NUM_OF_ELEMENTS; i++) 26 | { 27 | // push the value 28 | vector_push_back(vec, createData(arr[i])); 29 | 30 | TEST_ASSERT(*((int*)vector_at(vec, i)) == arr[i]); 31 | 32 | // the size has changed 33 | TEST_ASSERT(vector_size(vec) == i+1); 34 | } 35 | 36 | // free memory used 37 | vector_destroy(vec); 38 | free(arr); 39 | } 40 | 41 | void test_clear_at(void) 42 | { 43 | // create vector 44 | Vector vec = vector_create(free); 45 | 46 | time_t t; 47 | srand((unsigned) time(&t)); 48 | 49 | int* arr = create_random_array(NUM_OF_ELEMENTS); 50 | 51 | for (uint64_t i = 0; i < NUM_OF_ELEMENTS; i++) 52 | vector_push_back(vec, createData(arr[i])); 53 | 54 | for (uint64_t i = 0; i < NUM_OF_ELEMENTS; i++) 55 | { 56 | // clear the value 57 | vector_clear_at(vec, i); 58 | 59 | TEST_ASSERT(vector_at(vec, i) == NULL); 60 | 61 | // the size has changed 62 | TEST_ASSERT(vector_size(vec) == NUM_OF_ELEMENTS-1-i); 63 | } 64 | 65 | // free memory used 66 | vector_destroy(vec); 67 | free(arr); 68 | } 69 | 70 | void test_search(void) 71 | { 72 | // create vector 73 | Vector vec = vector_create(free); 74 | 75 | time_t t; 76 | srand((unsigned) time(&t)); 77 | 78 | int* arr = create_random_array(NUM_OF_ELEMENTS); 79 | 80 | for (uint64_t i = 0; i < NUM_OF_ELEMENTS; i++) 81 | vector_push_back(vec, createData(arr[i])); 82 | 83 | clock_t cur_time = clock(); 84 | 85 | for (uint64_t i = 0; i < NUM_OF_ELEMENTS; i++) 86 | TEST_ASSERT(vector_search(vec, arr+i, compareFunction)); 87 | 88 | double time_insert = calc_time(cur_time); // calculate search time 89 | 90 | // free memory used 91 | vector_destroy(vec); 92 | free(arr); 93 | 94 | printf("\n\nSearch took %f seconds to complete\n", time_insert); 95 | } 96 | 97 | void test_sort(void) 98 | { 99 | // create vector 100 | Vector vec = vector_create(free); 101 | 102 | time_t t; 103 | srand((unsigned) time(&t)); 104 | 105 | int* arr = create_shuffled_array(NUM_OF_ELEMENTS); 106 | 107 | for (uint64_t i = 0; i < NUM_OF_ELEMENTS; i++) 108 | vector_push_back(vec, createData(arr[i])); 109 | 110 | // sort the vector 111 | vector_sort(vec, compareFunction); 112 | 113 | for (int i = 0; i < NUM_OF_ELEMENTS; i++) 114 | TEST_ASSERT(*((int*)vector_at(vec, i)) == i); 115 | 116 | // free memory used 117 | vector_destroy(vec); 118 | free(arr); 119 | } 120 | 121 | void test_binary_search(void) 122 | { 123 | // create vector 124 | Vector vec = vector_create(free); 125 | 126 | time_t t; 127 | srand((unsigned) time(&t)); 128 | 129 | int* arr = create_random_array(NUM_OF_ELEMENTS); 130 | 131 | for (uint64_t i = 0; i < NUM_OF_ELEMENTS; i++) 132 | vector_push_back(vec, createData(arr[i])); 133 | 134 | clock_t cur_time = clock(); 135 | 136 | // sort the vector 137 | vector_sort(vec, compareFunction); 138 | 139 | for (uint64_t i = 0; i < NUM_OF_ELEMENTS; i++) 140 | TEST_ASSERT(vector_binary_search(vec, arr+i, compareFunction)); 141 | 142 | double time_insert = calc_time(cur_time); // calculate binary search time 143 | 144 | // free memory used 145 | vector_destroy(vec); 146 | free(arr); 147 | 148 | printf("\n\nBinary search took %f seconds to complete\n", time_insert); 149 | } 150 | 151 | TEST_LIST = { 152 | { "create", test_create }, 153 | { "push back", test_push_back }, 154 | { "clear at", test_clear_at }, 155 | { "search", test_search }, 156 | { "sort", test_sort }, 157 | { "binary search", test_binary_search }, 158 | { NULL, NULL } 159 | }; 160 | -------------------------------------------------------------------------------- /tests/test_WeightedUndirectedGraph.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../lib/ADT.h" 4 | 5 | int main(void) 6 | { 7 | // create graph 8 | wu_graph A = wug_create(7); 9 | 10 | // the weighted undirected graph: 11 | // https://www.researchgate.net/profile/Soran-Saeed/publication/330778836/figure/fig2/AS:721420615168005@1549011486980/Example-of-Minimum-spanning-tree-11.jpg 12 | wug_insert(A, 1, 2, 1); 13 | wug_insert(A, 2, 3, 10); 14 | wug_insert(A, 2, 4, 15); 15 | wug_insert(A, 3, 4, 11); 16 | wug_insert(A, 4, 5, 6); 17 | wug_insert(A, 5, 6, 9); 18 | wug_insert(A, 6, 3, 2); 19 | wug_insert(A, 1, 6, 14); 20 | wug_insert(A, 1, 3, 9); 21 | 22 | // print the graph 23 | wug_print(A); 24 | 25 | // print minimum spanning tree 26 | printf("\nMinimum spanning tree edges:\n"); 27 | wug_minspantree(A); 28 | 29 | // free graph 30 | wug_destroy(A); 31 | 32 | return 0; 33 | } 34 | --------------------------------------------------------------------------------