├── 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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------