diff --git a/Foundation/c_UIntArray.c b/Foundation/c_UIntArray.c new file mode 100644 index 0000000..fcef272 --- /dev/null +++ b/Foundation/c_UIntArray.c @@ -0,0 +1,144 @@ +#include + +c_err_t c_UIntArray_Init(c_UIntArray_t* self, c_size_t capacity, c_Allocator_t* allocator) { + if (!self ) return C_ERR_PARAM; + self->allocator = (allocator!=NULL)?*allocator:c_DefaultAllocator; + self->capacity = capacity; + self->size = 0; + if (capacity > 0) { + self->array = c_Allocator_Alloc(&self->allocator, self->capacity * sizeof(*self->array)); + if (!self->array) { + self->capacity = 0; + return C_ERR_NOMEM; + } + }else { + self->array = NULL; + } + + return C_ERR_OK; +} + +void c_UIntArray_Destroy(c_UIntArray_t* self) { + if (!self) return; + if (self->array && self->allocator.free) { + c_Allocator_Free(&self->allocator, self->array); + self->array = NULL; + } + self->size = 0; + self->capacity = 0; +} + +c_err_t c_UIntArray_Resize(c_UIntArray_t* self, c_size_t new_capacity) { + if (!self) return C_ERR_PARAM; + if (new_capacity == self->capacity) return C_ERR_OK; + + // Boundary contract protection: Cap cannot compress below active item footprints + if (new_capacity < self->size) return C_ERR_PARAM; + + if (new_capacity == 0) { + if (self->array) { + c_Allocator_Free(&self->allocator, self->array); + self->array = NULL; + } + self->capacity = 0; + return C_ERR_OK; + } + + c_uint_t* new_ptr = NULL; + if (self->array) { + c_size_t old_size = self->capacity * sizeof(*self->array); + c_size_t new_size = new_capacity * sizeof(*self->array); + + new_ptr = c_Allocator_Realloc(&self->allocator, self->array, old_size, new_size); + } else { + new_ptr = c_Allocator_Alloc(&self->allocator, new_capacity * sizeof(*self->array)); + } + + if (!new_ptr) return C_ERR_NOMEM; + + self->array = new_ptr; + self->capacity = new_capacity; + return C_ERR_OK; +} + +c_err_t c_UIntArray_Append(c_UIntArray_t* self, c_uint_t value) { + if (!self) return C_ERR_PARAM; + + // Geometric resizing policy (doubling capacity on saturation) + if (self->size >= self->capacity) { + c_size_t next_cap = (self->capacity == 0) ? 4 : (self->capacity << 1); + c_err_t err = c_UIntArray_Resize(self, next_cap); + if (err != C_ERR_OK) return err; + } + + self->array[self->size++] = value; + return C_ERR_OK; +} + +c_err_t c_UIntArray_Set(c_UIntArray_t* self, c_size_t index, c_uint_t value) { + if (!self || index >= self->size) return C_ERR_PARAM; + + self->array[index] = value; + return C_ERR_OK; +} + +c_err_t c_UIntArray_Get(c_UIntArray_t* self, c_size_t index, c_uint_t* value) { + if (!self || !value || index >= self->size) return C_ERR_PARAM; + if (value) { + *value = self->array[index]; + } + return C_ERR_OK; +} + +c_err_t c_UIntArray_Remove(c_UIntArray_t* self, c_size_t index) { + if (!self || index >= self->size) return C_ERR_PARAM; + + c_size_t elements_to_move = self->size - index - 1; + if (elements_to_move > 0) { + memmove(&self->array[index], &self->array[index + 1], elements_to_move * sizeof(c_uint_t)); + } + + self->size--; + + if (self->size <= (self->capacity>>2)) { + return c_UIntArray_Resize(self, self->capacity >> 1); + } + + return C_ERR_OK; +} + + +c_err_t c_UIntArray_Copy(c_UIntArray_t* dest, c_UIntArray_t* src) { + if (!dest || !src) return C_ERR_PARAM; + if (dest == src) return C_SUCCESS; /* Self-copy protection */ + + c_size_t src_size = (c_size_t)c_UIntArray_GetSize(src); + + /* 1. If the source is empty, simply reset the destination size to 0 */ + if (src_size == 0) { + /* Assuming your array has a fast Clear or Resize capability to clear bounds */ + dest->size = 0; + return C_SUCCESS; + } + + c_size_t new_bytes = src_size * sizeof(c_uint_t); + + /* 2. Check if the destination has enough capacity. If not, resize it. */ + if (dest->capacity < src_size) { + /* Calculate how many bytes are needed */ + c_size_t old_bytes = dest->capacity * sizeof(c_uint_t); + + /* Reallocate memory via the destination's assigned allocator */ + c_uint_t* new_data = (c_uint_t*)c_Allocator_Realloc(&dest->allocator, dest->array, old_bytes, new_bytes); + if (!new_data) return C_ERR_NOMEM; + + dest->array = new_data; + dest->capacity = src_size; + } + + /* 3. Execute high-speed raw memory block cloning */ + memcpy(dest->array, src->array, new_bytes); + dest->size = src_size; + + return C_SUCCESS; +} diff --git a/Foundation/c_UIntArray.h b/Foundation/c_UIntArray.h new file mode 100644 index 0000000..eca7697 --- /dev/null +++ b/Foundation/c_UIntArray.h @@ -0,0 +1,54 @@ +#ifndef INCLUDED_C_UINTARRAY_H +#define INCLUDED_C_UINTARRAY_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +#ifndef INCLUDED_C_ALLOCATOR_H +#include +#endif /*INCLUDED_C_ALLOCATOR_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_uint_t* array; + c_size_t capacity; + c_size_t size; + c_Allocator_t allocator; +}c_UIntArray_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_UIntArray_Init(c_UIntArray_t* self, c_size_t capacity, c_Allocator_t* allocator); + +void c_UIntArray_Destroy(c_UIntArray_t* self); + +c_err_t c_UIntArray_Resize(c_UIntArray_t* self, c_size_t new_capacity); + +c_err_t c_UIntArray_Append(c_UIntArray_t* self, c_uint_t value); + +c_err_t c_UIntArray_Set(c_UIntArray_t* self, c_size_t index, c_uint_t value); + +c_err_t c_UIntArray_Get(c_UIntArray_t* self, c_size_t index, c_uint_t* value); + +c_err_t c_UIntArray_Remove(c_UIntArray_t* self, c_size_t index); + +C_STATIC_FORCE_INLINE +c_bool_t c_UIntArray_IsEmpty(c_UIntArray_t* self) { + if (!self) return C_TRUE; + return self->size==0; +} + +C_STATIC_FORCE_INLINE +c_size_t c_UIntArray_GetSize(c_UIntArray_t* self) { + if (!self) return 0; + return self->size; +} + +c_err_t c_UIntArray_Copy(c_UIntArray_t* dest, c_UIntArray_t* src); + +#endif /*INCLUDED_C_UINTARRAY_H*/ diff --git a/Graph/c_AcyclicLP.c b/Graph/c_AcyclicLP.c new file mode 100644 index 0000000..c0e6145 --- /dev/null +++ b/Graph/c_AcyclicLP.c @@ -0,0 +1,142 @@ +#include + +#define LP_SENTINEL ((c_size_t)-1) + +/* Private vertex relaxation handler routine optimized for maximum distance maximization */ +static void c_AcyclicLP_Relax(c_AcyclicLP_t* self, const c_EdgeWeightedDigraph_t* graph, c_size_t v) { + c_AdjList_t* adj = &graph->adj_list[v]; + c_size_t size = (c_size_t)c_AdjList_GetSize(adj); + + for (c_size_t i = 0; i < size; ++i) { + c_uint_t generic_edge_id = 0; + c_err_t err = c_AdjList_Get(adj, i, &generic_edge_id); + + if (err == C_SUCCESS) { + c_size_t edge_id = (c_size_t)generic_edge_id; + c_DirectedEdge_t* edge = &graph->edges_pool[edge_id]; + c_size_t w = edge->to; + + /* Maximization condition check: update if a longer path variation is intercepted */ + if (self->dist_to[w] < self->dist_to[v] + edge->weight) { + self->dist_to[w] = self->dist_to[v] + edge->weight; + self->edge_to[w] = edge_id; + self->from_vertex[w] = v; /* Record structural parent lookup */ + } + } + } +} + +c_err_t c_AcyclicLP_Init(c_AcyclicLP_t* self, c_EdgeWeightedDigraph_t* graph, c_size_t s, c_Allocator_t* allocator) { + if (!self || !graph || s >= graph->V) return C_ERR_PARAM; + + self->allocator = allocator ? *allocator : c_DefaultAllocator; + self->s = s; + self->V = graph->V; + + /* 1. Allocate essential tracker state buffers */ + self->edge_to = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + self->from_vertex = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + self->dist_to = (double*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(double)); + + if (!self->edge_to || !self->from_vertex || !self->dist_to) { + c_AcyclicLP_Destroy(self); + return C_ERR_NOMEM; + } + + /* Initialize metrics map to NEGATIVE INFINITY benchmarks */ + for (c_size_t v = 0; v < self->V; ++v) { + self->dist_to[v] = -DBL_MAX; + self->edge_to[v] = LP_SENTINEL; + self->from_vertex[v] = LP_SENTINEL; + } + self->dist_to[s] = 0.0; + + /* 2. Stack-Safe Kahn's algorithm setup to pull Topological Order profile elements */ + c_size_t* working_indegree = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + c_size_t* zero_in_degree_queue = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + if (!working_indegree || !zero_in_degree_queue) { + if (working_indegree) c_Allocator_Free(&self->allocator, working_indegree); + if (zero_in_degree_queue) c_Allocator_Free(&self->allocator, zero_in_degree_queue); + c_AcyclicLP_Destroy(self); + return C_ERR_NOMEM; + } + + c_size_t head = 0, tail = 0; + for (c_size_t v = 0; v < self->V; ++v) { + working_indegree[v] = c_EdgeWeightedDigraph_GetInDegree(graph, v); + if (working_indegree[v] == 0) { + zero_in_degree_queue[tail++] = v; + } + } + + /* 3. Run non-recursive scheduling pass across topologized nodes sequential blocks */ + while (head < tail) { + c_size_t u = zero_in_degree_queue[head++]; + + /* If vertex u is reachable from our tracking origin boundary source context, compute cuts */ + if (self->dist_to[u] > -DBL_MAX) { + c_AcyclicLP_Relax(self, graph, u); + } + + /* Decrement inner dependency states for downstream neighbor vertex arrays */ + c_AdjList_t* adj = &graph->adj_list[u]; + c_size_t size = (c_size_t)c_AdjList_GetSize(adj); + for (c_size_t i = 0; i < size; ++i) { + c_uint_t generic_edge_id = 0; + if (c_AdjList_Get(adj, i, &generic_edge_id) == C_SUCCESS) { + c_size_t w = graph->edges_pool[(c_size_t)generic_edge_id].to; + working_indegree[w]--; + if (working_indegree[w] == 0) { + zero_in_degree_queue[tail++] = w; + } + } + } + } + + c_Allocator_Free(&self->allocator, working_indegree); + c_Allocator_Free(&self->allocator, zero_in_degree_queue); + + return C_SUCCESS; +} + +void c_AcyclicLP_Destroy(c_AcyclicLP_t* self) { + if (!self) return; + if (self->edge_to) c_Allocator_Free(&self->allocator, self->edge_to); + if (self->from_vertex) c_Allocator_Free(&self->allocator, self->from_vertex); + if (self->dist_to) c_Allocator_Free(&self->allocator, self->dist_to); + + self->edge_to = NULL; + self->from_vertex = NULL; + self->dist_to = NULL; + self->V = 0; + self->s = 0; +} + +c_err_t c_AcyclicLP_PathTo(c_AcyclicLP_t* self, c_size_t v, c_VertexIdList_t* out_path) { + if (!self || !out_path || v >= self->V) return C_ERR_PARAM; + if (!c_AcyclicLP_HasPathTo(self, v)) return C_ERR_FAIL; + + c_size_t* edge_stack = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + if (!edge_stack) return C_ERR_NOMEM; + + c_size_t stack_size = 0; + c_size_t curr_v = v; + + while (curr_v != self->s) { + c_size_t edge_id = self->edge_to[curr_v]; + if (edge_id == LP_SENTINEL) break; + + edge_stack[stack_size++] = edge_id; + curr_v = self->from_vertex[curr_v]; + } + + c_err_t err = C_SUCCESS; + while (stack_size > 0) { + c_size_t target_edge_id = edge_stack[--stack_size]; + err = c_VertexIdList_Append(out_path, (c_uint_t)target_edge_id); + if (err != C_SUCCESS) break; + } + + c_Allocator_Free(&self->allocator, edge_stack); + return err; +} diff --git a/Graph/c_AcyclicLP.h b/Graph/c_AcyclicLP.h new file mode 100644 index 0000000..839d608 --- /dev/null +++ b/Graph/c_AcyclicLP.h @@ -0,0 +1,66 @@ +#ifndef INCLUDED_C_ACYCLICLP_H +#define INCLUDED_C_ACYCLICLP_H + +#ifndef INCLUDED_C_EDGEWEIGHTEDDIGRAPH_H +#include +#endif /*INCLUDED_C_EDGEWEIGHTEDDIGRAPH_H*/ + + +#ifndef INCLUDED_C_VERTEXIDLIST_H +#include +#endif /*INCLUDED_C_VERTEXIDLIST_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_size_t* edge_to; /* edge_to[w] = entering directed edge_id on longest path to w */ + c_size_t* from_vertex; /* from_vertex[w] = source vertex parent link on path to w */ + double* dist_to; /* dist_to[w] = cumulative longest path distance from source s to w */ + c_size_t s; /* The source vertex index */ + c_size_t V; /* Total number of vertices in the digraph */ + c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ +} c_AcyclicLP_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Computes a longest-paths tree from the source vertex 's' in a directed acyclic graph (DAG). + * @param allocator Explicit allocator context used to configure internal tracking state memory + */ +c_err_t c_AcyclicLP_Init(c_AcyclicLP_t* self, c_EdgeWeightedDigraph_t* graph, c_size_t s, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers safely. + */ +void c_AcyclicLP_Destroy(c_AcyclicLP_t* self); + +/** + * @brief Is there a directed path from the source vertex 's' to vertex 'v'? + */ +C_STATIC_FORCE_INLINE +c_bool_t c_AcyclicLP_HasPathTo(c_AcyclicLP_t* self, c_size_t v) { + if (!self || v >= self->V || !self->dist_to) return C_FALSE; + return self->dist_to[v] > -DBL_MAX; +} + +/** + * @brief Returns the distance of the longest path from the source vertex 's' to vertex 'v'. + * @return Distance value, or -DBL_MAX if unreachable / parameter error + */ +C_STATIC_FORCE_INLINE +double c_AcyclicLP_DistTo(c_AcyclicLP_t* self, c_size_t v) { + if (!self || v >= self->V || !self->dist_to) return -DBL_MAX; + return self->dist_to[v]; +} + +/** + * @brief Reconstructs the exact longest path from the source vertex 's' to vertex 'v' and appends it to out_path. + * @param out_path An initialized c_VertexIdList_t container to collect the sequence of global directed edge_ids. + */ +c_err_t c_AcyclicLP_PathTo(c_AcyclicLP_t* self, c_size_t v, c_VertexIdList_t* out_path); + + +#endif /*INCLUDED_C_ACYCLICLP_H*/ diff --git a/Graph/c_AcyclicLP.t.c b/Graph/c_AcyclicLP.t.c new file mode 100644 index 0000000..5f9cac2 --- /dev/null +++ b/Graph/c_AcyclicLP.t.c @@ -0,0 +1,55 @@ +#include "c_Test.h" +#include "c_EdgeWeightedDigraph.h" +#include "c_AcyclicLP.h" +#include "c_VertexIdList.h" + +TEST_CASE(test_acyclic_longest_paths_dag) { + c_EdgeWeightedDigraph_t g; + c_err_t err = c_EdgeWeightedDigraph_Init(&g, 4, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* + * Construct a valid DAG layout to measure longest path selection capabilities: + * 0 -> 1 (Weight: 2.0) [Edge 0] + * 0 -> 2 (Weight: 1.0) [Edge 1] + * 1 -> 2 (Weight: 5.0) [Edge 2] -> Path 0->1->2 total is 2.0 + 5.0 = 7.0 (Longer than direct 0->2 edge) + * 2 -> 3 (Weight: 3.0) [Edge 3] -> Path 0->1->2->3 total is 7.0 + 3.0 = 10.0 + */ + c_EdgeWeightedDigraph_AddEdge(&g, 0, 1, 2.0); + c_EdgeWeightedDigraph_AddEdge(&g, 0, 2, 1.0); + c_EdgeWeightedDigraph_AddEdge(&g, 1, 2, 5.0); + c_EdgeWeightedDigraph_AddEdge(&g, 2, 3, 3.0); + + c_AcyclicLP_t lp; + err = c_AcyclicLP_Init(&lp, &g, 0, &g.allocator); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Verify longest path evaluation weights values */ + ASSERT_TRUE(c_AcyclicLP_HasPathTo(&lp, 3)); + ASSERT_DOUBLE_EQ_MSG(7.0, c_AcyclicLP_DistTo(&lp, 2), "Critical intermediate length calculation mismatch"); + ASSERT_DOUBLE_EQ_MSG(10.0, c_AcyclicLP_DistTo(&lp, 3), "DAG critical terminal timeline calculation wrong"); + + /* Reconstruct edge sequence arrays elements */ + c_VertexIdList_t edge_path; + c_VertexIdList_Init(&edge_path, 0, 0); + + err = c_AcyclicLP_PathTo(&lp, 3, &edge_path); + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_LL_EQ(3, (c_size_t)c_VertexIdList_GetSize(&edge_path)); + + c_uint_t e_id = 0; + c_VertexIdList_Get(&edge_path, 0, &e_id); ASSERT_LL_EQ(0, e_id); /* Edge 0 (0->1) */ + c_VertexIdList_Get(&edge_path, 1, &e_id); ASSERT_LL_EQ(2, e_id); /* Edge 2 (1->2) */ + c_VertexIdList_Get(&edge_path, 2, &e_id); ASSERT_LL_EQ(3, e_id); /* Edge 3 (2->3) */ + + c_VertexIdList_Destroy(&edge_path); + c_AcyclicLP_Destroy(&lp); + c_EdgeWeightedDigraph_Destroy(&g); +} + +int main(void) { + TEST_START(AcyclicLP_DAG_Maximization_Suite); + RUN_TEST(test_acyclic_longest_paths_dag); + TEST_REPORT(); + RETURN_TEST_STATUS; +} diff --git a/Graph/c_AcyclicSP.c b/Graph/c_AcyclicSP.c new file mode 100644 index 0000000..8df5542 --- /dev/null +++ b/Graph/c_AcyclicSP.c @@ -0,0 +1,140 @@ +#include + +#define SP_SENTINEL ((c_size_t)-1) + +/* Private vertex relaxation handler routine */ +static void c_AcyclicSP_Relax(c_AcyclicSP_t* self, c_EdgeWeightedDigraph_t* graph, c_size_t v) { + c_AdjList_t* adj = &graph->adj_list[v]; + c_size_t size = (c_size_t)c_AdjList_GetSize(adj); + + for (c_size_t i = 0; i < size; ++i) { + c_uint_t generic_edge_id = 0; + c_err_t err = c_AdjList_Get(adj, i, &generic_edge_id); + + if (err == C_SUCCESS) { + c_size_t edge_id = (c_size_t)generic_edge_id; + c_DirectedEdge_t* edge = &graph->edges_pool[edge_id]; + c_size_t w = edge->to; + + if (self->dist_to[w] > self->dist_to[v] + edge->weight) { + self->dist_to[w] = self->dist_to[v] + edge->weight; + self->edge_to[w] = edge_id; + self->from_vertex[w] = v; /* Record parent step */ + } + } + } +} + +c_err_t c_AcyclicSP_Init(c_AcyclicSP_t* self, c_EdgeWeightedDigraph_t* graph, c_size_t s, c_Allocator_t* allocator) { + if (!self || !graph || s >= graph->V) return C_ERR_PARAM; + + self->allocator = allocator ? *allocator : c_DefaultAllocator; + self->s = s; + self->V = graph->V; + + /* 1. Allocate primary execution matrices arrays */ + self->edge_to = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + self->from_vertex = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + self->dist_to = (double*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(double)); + + if (!self->edge_to || !self->from_vertex || !self->dist_to) { + c_AcyclicSP_Destroy(self); + return C_ERR_NOMEM; + } + + for (c_size_t v = 0; v < self->V; ++v) { + self->dist_to[v] = DBL_MAX; + self->edge_to[v] = SP_SENTINEL; + self->from_vertex[v] = SP_SENTINEL; + } + self->dist_to[s] = 0.0; + + /* 2. Embedded Stack-Safe Kahn's Algorithm to establish Topological Order */ + c_size_t* working_indegree = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + c_size_t* zero_in_degree_queue = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + if (!working_indegree || !zero_in_degree_queue) { + if (working_indegree) c_Allocator_Free(&self->allocator, working_indegree); + if (zero_in_degree_queue) c_Allocator_Free(&self->allocator, zero_in_degree_queue); + c_AcyclicSP_Destroy(self); + return C_ERR_NOMEM; + } + + c_size_t head = 0, tail = 0; + for (c_size_t v = 0; v < self->V; ++v) { + working_indegree[v] = c_EdgeWeightedDigraph_GetInDegree(graph, v); + if (working_indegree[v] == 0) { + zero_in_degree_queue[tail++] = v; + } + } + + /* 3. Linear relaxation pass tracing vertices sequentially across topological indices */ + while (head < tail) { + c_size_t u = zero_in_degree_queue[head++]; + + /* If vertex u is reachable from source, relax its outgoing edges */ + if (self->dist_to[u] < DBL_MAX) { + c_AcyclicSP_Relax(self, graph, u); + } + + /* Decrement inner dependency states for all downstream neighbors */ + c_UIntArray_t* adj = &graph->adj_list[u]; + c_size_t size = (c_size_t)c_UIntArray_GetSize(adj); + for (c_size_t i = 0; i < size; ++i) { + c_uint_t generic_edge_id = 0; + if (c_UIntArray_Get(adj, i, &generic_edge_id) == C_SUCCESS) { + c_size_t w = graph->edges_pool[(c_size_t)generic_edge_id].to; + working_indegree[w]--; + if (working_indegree[w] == 0) { + zero_in_degree_queue[tail++] = w; + } + } + } + } + + c_Allocator_Free(&self->allocator, working_indegree); + c_Allocator_Free(&self->allocator, zero_in_degree_queue); + + return C_SUCCESS; +} + +void c_AcyclicSP_Destroy(c_AcyclicSP_t* self) { + if (!self) return; + if (self->edge_to) c_Allocator_Free(&self->allocator, self->edge_to); + if (self->from_vertex) c_Allocator_Free(&self->allocator, self->from_vertex); + if (self->dist_to) c_Allocator_Free(&self->allocator, self->dist_to); + + self->edge_to = NULL; + self->from_vertex = NULL; + self->dist_to = NULL; + self->V = 0; + self->s = 0; +} + +c_err_t c_AcyclicSP_PathTo(c_AcyclicSP_t* self, c_size_t v, c_VertexIdList_t* out_path) { + if (!self || !out_path || v >= self->V) return C_ERR_PARAM; + if (!c_AcyclicSP_HasPathTo(self, v)) return C_ERR_FAIL; + + c_size_t* edge_stack = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + if (!edge_stack) return C_ERR_NOMEM; + + c_size_t stack_size = 0; + c_size_t curr_v = v; + + while (curr_v != self->s) { + c_size_t edge_id = self->edge_to[curr_v]; + if (edge_id == SP_SENTINEL) break; + + edge_stack[stack_size++] = edge_id; + curr_v = self->from_vertex[curr_v]; + } + + c_err_t err = C_SUCCESS; + while (stack_size > 0) { + c_size_t target_edge_id = edge_stack[--stack_size]; + err = c_VertexIdList_Append(out_path, (c_uint_t)target_edge_id); + if (err != C_SUCCESS) break; + } + + c_Allocator_Free(&self->allocator, edge_stack); + return err; +} diff --git a/Graph/c_AcyclicSP.h b/Graph/c_AcyclicSP.h new file mode 100644 index 0000000..8f67eb6 --- /dev/null +++ b/Graph/c_AcyclicSP.h @@ -0,0 +1,66 @@ +#ifndef INCLUDED_C_ACYCLICSP_H +#define INCLUDED_C_ACYCLICSP_H + +#ifndef INCLUDED_C_EDGEWEIGHTEDDIGRAPH_H +#include +#endif /*INCLUDED_C_EDGEWEIGHTEDDIGRAPH_H*/ + + +#ifndef INCLUDED_C_VERTEXIDLIST_H +#include +#endif /*INCLUDED_C_VERTEXIDLIST_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_size_t* edge_to; /* edge_to[w] = entering directed edge_id on shortest path to w */ + c_size_t* from_vertex; /* from_vertex[w] = source vertex parent link on path to w */ + double* dist_to; /* dist_to[w] = cumulative shortest path distance from source s to w */ + c_size_t s; /* The source vertex index */ + c_size_t V; /* Total number of vertices in the digraph */ + c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ +} c_AcyclicSP_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Computes a shortest-paths tree from the source vertex 's' in a directed acyclic graph (DAG). + * @param allocator Explicit allocator context used to configure internal tracking state memory + */ +c_err_t c_AcyclicSP_Init(c_AcyclicSP_t* self, c_EdgeWeightedDigraph_t* graph, c_size_t s, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers safely. + */ +void c_AcyclicSP_Destroy(c_AcyclicSP_t* self); + +/** + * @brief Is there a directed path from the source vertex 's' to vertex 'v'? + */ +C_STATIC_FORCE_INLINE +c_bool_t c_AcyclicSP_HasPathTo(c_AcyclicSP_t* self, c_size_t v) { + if (!self || v >= self->V || !self->dist_to) return C_FALSE; + return self->dist_to[v] < DBL_MAX; +} + +/** + * @brief Returns the distance of the shortest path from the source vertex 's' to vertex 'v'. + * @return Distance value, or DBL_MAX if unreachable / parameter error + */ +C_STATIC_FORCE_INLINE +double c_AcyclicSP_DistTo(c_AcyclicSP_t* self, c_size_t v) { + if (!self || v >= self->V || !self->dist_to) return DBL_MAX; + return self->dist_to[v]; +} + +/** + * @brief Reconstructs the exact shortest path from the source vertex 's' to vertex 'v' and appends it to out_path. + * @param out_path An initialized c_VertexIdList_t container to collect the sequence of global directed edge_ids. + */ +c_err_t c_AcyclicSP_PathTo(c_AcyclicSP_t* self, c_size_t v, c_VertexIdList_t* out_path); + + +#endif /*INCLUDED_C_ACYCLICSP_H*/ diff --git a/Graph/c_AcyclicSP.t.c b/Graph/c_AcyclicSP.t.c new file mode 100644 index 0000000..f5f640d --- /dev/null +++ b/Graph/c_AcyclicSP.t.c @@ -0,0 +1,55 @@ +#include "c_Test.h" +#include "c_EdgeWeightedDigraph.h" +#include "c_AcyclicSP.h" +#include "c_VertexIdList.h" + +TEST_CASE(test_acyclic_shortest_paths_dag) { + c_EdgeWeightedDigraph_t g; + c_err_t err = c_EdgeWeightedDigraph_Init(&g, 4, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* + * Construct a valid DAG containing negative weights: + * 0 -> 1 (Weight: 2.0) [Edge 0] + * 0 -> 2 (Weight: 5.0) [Edge 1] + * 1 -> 2 (Weight: -4.0) [Edge 2] -> Path 0->1->2 total is 2.0 + (-4.0) = -2.0 + * 2 -> 3 (Weight: 1.0) [Edge 3] -> Path 0->1->2->3 total is -2.0 + 1.0 = -1.0 + */ + c_EdgeWeightedDigraph_AddEdge(&g, 0, 1, 2.0); + c_EdgeWeightedDigraph_AddEdge(&g, 0, 2, 5.0); + c_EdgeWeightedDigraph_AddEdge(&g, 1, 2, -4.0); + c_EdgeWeightedDigraph_AddEdge(&g, 2, 3, 1.0); + + c_AcyclicSP_t sp; + err = c_AcyclicSP_Init(&sp, &g, 0, &g.allocator); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Distance mappings verification */ + ASSERT_TRUE(c_AcyclicSP_HasPathTo(&sp, 3)); + ASSERT_DOUBLE_EQ_MSG(-2.0, c_AcyclicSP_DistTo(&sp, 2), "Negative distance processing check failed"); + ASSERT_DOUBLE_EQ_MSG(-1.0, c_AcyclicSP_DistTo(&sp, 3), "DAG terminal target computation failed"); + + /* Reconstruct edge collection paths sequence */ + c_VertexIdList_t edge_path; + c_VertexIdList_Init(&edge_path, 0, 0 ); + + err = c_AcyclicSP_PathTo(&sp, 3, &edge_path); + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_LL_EQ(3, (c_size_t)c_VertexIdList_GetSize(&edge_path)); + + c_uint_t e_id = 0; + c_VertexIdList_Get(&edge_path, 0, &e_id); ASSERT_LL_EQ(0, e_id); /* Edge 0 (0->1) */ + c_VertexIdList_Get(&edge_path, 1, &e_id); ASSERT_LL_EQ(2, e_id); /* Edge 2 (1->2) */ + c_VertexIdList_Get(&edge_path, 2, &e_id); ASSERT_LL_EQ(3, e_id); /* Edge 3 (2->3) */ + + c_VertexIdList_Destroy(&edge_path); + c_AcyclicSP_Destroy(&sp); + c_EdgeWeightedDigraph_Destroy(&g); +} + +int main(void) { + TEST_START(AcyclicSP_DAG_Optimization_Suite); + RUN_TEST(test_acyclic_shortest_paths_dag); + TEST_REPORT(); + RETURN_TEST_STATUS; +} diff --git a/Graph/c_AdjList.c b/Graph/c_AdjList.c index 3e0f906..2e3bfe1 100644 --- a/Graph/c_AdjList.c +++ b/Graph/c_AdjList.c @@ -1,109 +1 @@ #include - - -c_err_t c_AdjList_Init(c_AdjList_t* self, c_size_t capacity, c_Allocator_t* allocator) { - if (!self ) return C_ERR_PARAM; - self->allocator = (allocator!=NULL)?*allocator:c_DefaultAllocator; - self->capacity = capacity; - self->size = 0; - if (capacity > 0) { - self->array = c_Allocator_Alloc(&self->allocator, self->capacity * sizeof(*self->array)); - if (!self->array) { - self->capacity = 0; - return C_ERR_NOMEM; - } - }else { - self->array = NULL; - } - - return C_ERR_OK; -} - -void c_AdjList_Destroy(c_AdjList_t* self) { - if (!self) return; - if (self->array && self->allocator.free) { - c_Allocator_Free(&self->allocator, self->array); - self->array = NULL; - } - self->size = 0; - self->capacity = 0; -} - -c_err_t c_AdjList_Resize(c_AdjList_t* self, c_size_t new_capacity) { - if (!self) return C_ERR_PARAM; - if (new_capacity == self->capacity) return C_ERR_OK; - - // Boundary contract protection: Cap cannot compress below active item footprints - if (new_capacity < self->size) return C_ERR_PARAM; - - if (new_capacity == 0) { - if (self->array) { - c_Allocator_Free(&self->allocator, self->array); - self->array = NULL; - } - self->capacity = 0; - return C_ERR_OK; - } - - c_uint_t* new_ptr = NULL; - if (self->array) { - c_size_t old_size = self->capacity * sizeof(*self->array); - c_size_t new_size = new_capacity * sizeof(*self->array); - - new_ptr = c_Allocator_Realloc(&self->allocator, self->array, old_size, new_size); - } else { - new_ptr = c_Allocator_Alloc(&self->allocator, new_capacity * sizeof(*self->array)); - } - - if (!new_ptr) return C_ERR_NOMEM; - - self->array = new_ptr; - self->capacity = new_capacity; - return C_ERR_OK; -} - -c_err_t c_AdjList_Append(c_AdjList_t* self, c_uint_t value) { - if (!self) return C_ERR_PARAM; - - // Geometric resizing policy (doubling capacity on saturation) - if (self->size >= self->capacity) { - c_size_t next_cap = (self->capacity == 0) ? 4 : (self->capacity << 1); - c_err_t err = c_AdjList_Resize(self, next_cap); - if (err != C_ERR_OK) return err; - } - - self->array[self->size++] = value; - return C_ERR_OK; -} - -c_err_t c_AdjList_Set(c_AdjList_t* self, c_size_t index, c_uint_t value) { - if (!self || index >= self->size) return C_ERR_PARAM; - - self->array[index] = value; - return C_ERR_OK; -} - -c_err_t c_AdjList_Get(c_AdjList_t* self, c_size_t index, c_uint_t* value) { - if (!self || !value || index >= self->size) return C_ERR_PARAM; - if (value) { - *value = self->array[index]; - } - return C_ERR_OK; -} - -c_err_t c_AdjList_Remove(c_AdjList_t* self, c_size_t index) { - if (!self || index >= self->size) return C_ERR_PARAM; - - c_size_t elements_to_move = self->size - index - 1; - if (elements_to_move > 0) { - memmove(&self->array[index], &self->array[index + 1], elements_to_move * sizeof(c_uint_t)); - } - - self->size--; - - if (self->size <= (self->capacity>>2)) { - return c_AdjList_Resize(self, self->capacity >> 1); - } - - return C_ERR_OK; -} diff --git a/Graph/c_AdjList.h b/Graph/c_AdjList.h index ab43707..8790510 100644 --- a/Graph/c_AdjList.h +++ b/Graph/c_AdjList.h @@ -1,48 +1,32 @@ #ifndef INCLUDED_C_ADJLIST_H #define INCLUDED_C_ADJLIST_H -#ifndef INCLUDED_C_TYPES_H -#include -#endif /*INCLUDED_C_TYPES_H*/ +#ifndef INCLUDED_C_UINTARRAY_H +#include +#endif /*INCLUDED_C_UINTARRAY_H*/ -#ifndef INCLUDED_C_ALLOCATOR_H -#include -#endif /*INCLUDED_C_ALLOCATOR_H*/ /* ------------------------------------------------------------------------------------------------------------------ */ /* */ -typedef struct { - c_uint_t* array; - c_size_t capacity; - c_size_t size; - c_Allocator_t allocator; -}c_AdjList_t; +typedef c_UIntArray_t c_AdjList_t; + /* ------------------------------------------------------------------------------------------------------------------ */ /* */ +#define c_AdjList_Init c_UIntArray_Init +#define c_AdjList_Destroy c_UIntArray_Destroy +#define c_AdjList_Resize c_UIntArray_Resize +#define c_AdjList_Append c_UIntArray_Append +#define c_AdjList_Set c_UIntArray_Set +#define c_AdjList_Get c_UIntArray_Get +#define c_AdjList_Remove c_UIntArray_Remove +#define c_AdjList_IsEmpty c_UIntArray_IsEmpty +#define c_AdjList_GetSize c_UIntArray_GetSize +#define c_AdjList_Copy c_UIntArray_Copy -c_err_t c_AdjList_Init(c_AdjList_t* self, c_size_t capacity, c_Allocator_t* allocator); - -void c_AdjList_Destroy(c_AdjList_t* self); - -c_err_t c_AdjList_Resize(c_AdjList_t* self, c_size_t new_capacity); - -c_err_t c_AdjList_Append(c_AdjList_t* self, c_uint_t value); - -c_err_t c_AdjList_Set(c_AdjList_t* self, c_size_t index, c_uint_t value); - -c_err_t c_AdjList_Get(c_AdjList_t* self, c_size_t index, c_uint_t* value); - -c_err_t c_AdjList_Remove(c_AdjList_t* self, c_size_t index); - -C_STATIC_FORCE_INLINE -c_bool_t c_AdjList_IsEmpty(c_AdjList_t* self) { - if (!self) return C_TRUE; - return self->size==0; -} #endif /*INCLUDED_C_ADJLIST_H*/ diff --git a/Graph/c_BellmanFordSP.c b/Graph/c_BellmanFordSP.c new file mode 100644 index 0000000..cb32f5f --- /dev/null +++ b/Graph/c_BellmanFordSP.c @@ -0,0 +1,172 @@ +#include +#include +#include "c_DirectedCycle.h" + +#define SP_SENTINEL ((c_size_t)-1) + +/* Private Negative Cycle Scanner Helper Routine using an ad-hoc local graph projection */ +static void c_BellmanFord_FindNegativeCycle(c_BellmanFordSP_t* self) { + /* Create a temporary lightweight unweighted digraph to map our current edge_to routing tree */ + c_Digraph_t spt_graph; + c_err_t err = c_Digraph_Init(&spt_graph, self->V, &self->allocator); + if (err != C_SUCCESS) return; + + for (c_size_t v = 0; v < self->V; ++v) { + if (self->edge_to[v] != SP_SENTINEL) { + c_size_t parent = self->from_vertex[v]; + c_Digraph_AddEdge(&spt_graph, parent, v); + } + } + + /* Reuse your pre-built stack-safe directed cycle detector */ + c_DirectedCycle_t detector; + err = c_DirectedCycle_Init(&detector, &spt_graph, &self->allocator); + if (err == C_SUCCESS) { + if (c_DirectedCycle_HasCycle(&detector)) { + /* Safely clone the cycle path sequence back into our persistent collection structure */ + c_DirectedCycle_GetCycle(&detector, &self->cycle); + } + c_DirectedCycle_Destroy(&detector); + } + c_Digraph_Destroy(&spt_graph); +} + +/* Private FIFO relaxation subroutine */ +static void c_BellmanFord_Relax(c_BellmanFordSP_t* self, const c_EdgeWeightedDigraph_t* graph, c_size_t v, c_size_t* queue, c_size_t* tail, c_size_t max_q_cap) { + c_UIntArray_t* adj = &graph->adj_list[v]; + c_size_t size = (c_size_t)c_UIntArray_GetSize(adj); + + for (c_size_t i = 0; i < size; ++i) { + c_uint_t generic_edge_id = 0; + if (c_UIntArray_Get(adj, i, &generic_edge_id) != C_SUCCESS) continue; + + c_size_t edge_id = (c_size_t)generic_edge_id; + c_DirectedEdge_t* edge = &graph->edges_pool[edge_id]; + c_size_t w = edge->to; + + if (self->dist_to[w] > self->dist_to[v] + edge->weight) { + self->dist_to[w] = self->dist_to[v] + edge->weight; + self->edge_to[w] = edge_id; + self->from_vertex[w] = v; + + if (!self->on_queue[w]) { + queue[(*tail) % max_q_cap] = w; + (*tail)++; + self->on_queue[w] = C_TRUE; + } + } + + /* Periodically verify negative tree invariants every V edge relaxations */ + if (++self->cost_counter % self->V == 0) { + c_BellmanFord_FindNegativeCycle(self); + if (c_BellmanFordSP_HasNegativeCycle(self)) return; /* Stop early to optimize performance */ + } + } +} + +c_err_t c_BellmanFordSP_Init(c_BellmanFordSP_t* self, c_EdgeWeightedDigraph_t* graph, c_size_t s, c_Allocator_t* allocator) { + if (!self || !graph || s >= graph->V) return C_ERR_PARAM; + + self->allocator = allocator ? *allocator : c_DefaultAllocator; + self->s = s; + self->V = graph->V; + self->cost_counter = 0; + c_VertexIdList_Init(&self->cycle, 0, allocator); + + self->edge_to = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + self->from_vertex = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + self->dist_to = (double*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(double)); + self->on_queue = (c_bool_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_bool_t)); + + if (!self->edge_to || !self->from_vertex || !self->dist_to || !self->on_queue) { + c_BellmanFordSP_Destroy(self); + return C_ERR_NOMEM; + } + + for (c_size_t v = 0; v < self->V; ++v) { + self->dist_to[v] = DBL_MAX; + self->edge_to[v] = SP_SENTINEL; + self->from_vertex[v] = SP_SENTINEL; + self->on_queue[v] = C_FALSE; + } + self->dist_to[s] = 0.0; + + /* Allocate circular FIFO layout array queue bounded at capacity limit V + 1 */ + c_size_t max_q_cap = self->V + 1; + c_size_t* queue = (c_size_t*)c_Allocator_Calloc(&self->allocator, max_q_cap, sizeof(c_size_t)); + if (!queue) { + c_BellmanFordSP_Destroy(self); + return C_ERR_NOMEM; + } + + c_size_t head = 0; + c_size_t tail = 0; + + /* Enqueue source node */ + queue[tail++] = s; + self->on_queue[s] = C_TRUE; + + while (head < tail && !c_BellmanFordSP_HasNegativeCycle(self)) { + c_size_t v = queue[head % max_q_cap]; + head++; + self->on_queue[v] = C_FALSE; + + c_BellmanFord_Relax(self, graph, v, queue, &tail, max_q_cap); + } + + c_Allocator_Free(&self->allocator, queue); + return C_SUCCESS; +} + +void c_BellmanFordSP_Destroy(c_BellmanFordSP_t* self) { + if (!self) return; + if (self->edge_to) c_Allocator_Free(&self->allocator, self->edge_to); + if (self->from_vertex) c_Allocator_Free(&self->allocator, self->from_vertex); + if (self->dist_to) c_Allocator_Free(&self->allocator, self->dist_to); + if (self->on_queue) c_Allocator_Free(&self->allocator, self->on_queue); + + c_VertexIdList_Destroy(&self->cycle); + + self->edge_to = NULL; + self->from_vertex = NULL; + self->dist_to = NULL; + self->on_queue = NULL; + self->V = 0; + self->s = 0; + self->cost_counter = 0; +} + +c_err_t c_BellmanFordSP_PathTo(c_BellmanFordSP_t* self, c_size_t v, c_VertexIdList_t* out_path) { + if (!self || !out_path || v >= self->V) return C_ERR_PARAM; + if (c_BellmanFordSP_HasNegativeCycle(self) || !c_BellmanFordSP_HasPathTo(self, v)) return C_ERR_FAIL; + + c_size_t* edge_stack = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + if (!edge_stack) return C_ERR_NOMEM; + + c_size_t stack_size = 0; + c_size_t curr_v = v; + + while (curr_v != self->s) { + c_size_t edge_id = self->edge_to[curr_v]; + if (edge_id == SP_SENTINEL) break; + + edge_stack[stack_size++] = edge_id; + curr_v = self->from_vertex[curr_v]; + } + + c_err_t err = C_SUCCESS; + while (stack_size > 0) { + c_size_t target_edge_id = edge_stack[--stack_size]; + err = c_VertexIdList_Append(out_path, (c_uint_t)target_edge_id); + if (err != C_SUCCESS) break; + } + + c_Allocator_Free(&self->allocator, edge_stack); + return err; +} + +c_err_t c_BellmanFordSP_GetNegativeCycle(c_BellmanFordSP_t* self, c_VertexIdList_t* out_cycle) { + if (!self || !out_cycle) return C_ERR_PARAM; + if (!c_BellmanFordSP_HasNegativeCycle(self)) return C_ERR_FAIL; + return c_UIntArray_Copy(out_cycle, (c_UIntArray_t*)&self->cycle); +} diff --git a/Graph/c_BellmanFordSP.h b/Graph/c_BellmanFordSP.h new file mode 100644 index 0000000..037aa1f --- /dev/null +++ b/Graph/c_BellmanFordSP.h @@ -0,0 +1,81 @@ +#ifndef INCLUDED_C_BELLMANFORDSP_H +#define INCLUDED_C_BELLMANFORDSP_H + +#ifndef INCLUDED_C_EDGEWEIGHTEDDIGRAPH_H +#include +#endif /*INCLUDED_C_EDGEWEIGHTEDDIGRAPH_H*/ + + +#ifndef INCLUDED_C_VERTEXIDLIST_H +#include +#endif /*INCLUDED_C_VERTEXIDLIST_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_size_t* edge_to; /* edge_to[w] = entering directed edge_id on shortest path to w */ + c_size_t* from_vertex; /* from_vertex[w] = source vertex parent link on path to w */ + double* dist_to; /* dist_to[w] = cumulative distance from source s to w */ + c_bool_t* on_queue; /* on_queue[v] = is vertex v currently sitting inside the processing FIFO queue */ + c_VertexIdList_t cycle; /* Catches and stores the negative cycle path sequence if found */ + c_size_t cost_counter; /* Tracks number of relax calls to gauge periodic cycle check steps */ + c_size_t s; /* The source vertex index */ + c_size_t V; /* Total number of vertices in the digraph */ + c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ +} c_BellmanFordSP_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Computes a shortest-paths tree from the source vertex 's' in the edge-weighted digraph. + * @param allocator Explicit allocator context used to configure internal tracking state memory + */ +c_err_t c_BellmanFordSP_Init(c_BellmanFordSP_t* self, c_EdgeWeightedDigraph_t* graph, c_size_t s, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers safely. + */ +void c_BellmanFordSP_Destroy(c_BellmanFordSP_t* self); + +/** + * @brief Is there a directed path from the source vertex 's' to vertex 'v'? + */ +C_STATIC_FORCE_INLINE +c_bool_t c_BellmanFordSP_HasPathTo(c_BellmanFordSP_t* self, c_size_t v) { + if (!self || v >= self->V || !self->dist_to) return C_FALSE; + return self->dist_to[v] < DBL_MAX; +} + +/** + * @brief Returns the distance of the shortest path from the source vertex 's' to vertex 'v'. + */ +C_STATIC_FORCE_INLINE +double c_BellmanFordSP_DistTo(c_BellmanFordSP_t* self, c_size_t v) { + if (!self || v >= self->V || !self->dist_to) return DBL_MAX; + return self->dist_to[v]; +} + +/** + * @brief Does the edge-weighted digraph contain a negative cycle reachable from the source vertex? + */ +C_STATIC_FORCE_INLINE +c_bool_t c_BellmanFordSP_HasNegativeCycle(c_BellmanFordSP_t* self) { + if (!self) return C_FALSE; + return !c_VertexIdList_IsEmpty(&self->cycle); +} + +/** + * @brief Reconstructs the exact shortest path from the source vertex 's' to vertex 'v' and appends it to out_path. + */ +c_err_t c_BellmanFordSP_PathTo(c_BellmanFordSP_t* self, c_size_t v, c_VertexIdList_t* out_path); + +/** + * @brief Extracts the vertices forming the detected negative cycle loop if present. + */ +c_err_t c_BellmanFordSP_GetNegativeCycle(c_BellmanFordSP_t* self, c_VertexIdList_t* out_cycle); + + +#endif /*INCLUDED_C_BELLMANFORDSP_H*/ diff --git a/Graph/c_BellmanFordSP.t.c b/Graph/c_BellmanFordSP.t.c new file mode 100644 index 0000000..3d3f55a --- /dev/null +++ b/Graph/c_BellmanFordSP.t.c @@ -0,0 +1,70 @@ +#include "c_Test.h" +#include "c_EdgeWeightedDigraph.h" +#include "c_BellmanFordSP.h" + +TEST_CASE(test_bellman_ford_shortest_path_negative_weights) { + c_EdgeWeightedDigraph_t g; + c_EdgeWeightedDigraph_Init(&g, 4, NULL); + + /* Construct a graph with negative weights but NO negative cycles: + * 0 -> 1 (Weight: 5.0) + * 0 -> 2 (Weight: 2.0) + * 2 -> 1 (Weight: -4.0) -> Shortest path to 1 is 0->2->1 (Total: -2.0) + * 1 -> 3 (Weight: 3.0) -> Shortest path to 3 is 0->2->1->3 (Total: 1.0) + */ + c_EdgeWeightedDigraph_AddEdge(&g, 0, 1, 5.0); + c_EdgeWeightedDigraph_AddEdge(&g, 0, 2, 2.0); + c_EdgeWeightedDigraph_AddEdge(&g, 2, 1, -4.0); + c_EdgeWeightedDigraph_AddEdge(&g, 1, 3, 3.0); + + c_BellmanFordSP_t sp; + c_err_t err = c_BellmanFordSP_Init(&sp, &g, 0, &g.allocator); + ASSERT_INT_EQ(C_SUCCESS, err); + + ASSERT_FALSE(c_BellmanFordSP_HasNegativeCycle(&sp)); + ASSERT_TRUE(c_BellmanFordSP_HasPathTo(&sp, 3)); + ASSERT_DOUBLE_EQ_MSG(-2.0, c_BellmanFordSP_DistTo(&sp, 1), "Shortest path to 1 wrong"); + ASSERT_DOUBLE_EQ_MSG(1.0, c_BellmanFordSP_DistTo(&sp, 3), "Shortest path to 3 wrong"); + + c_BellmanFordSP_Destroy(&sp); + c_EdgeWeightedDigraph_Destroy(&g); +} + +TEST_CASE(test_bellman_ford_negative_cycle_detection) { + c_EdgeWeightedDigraph_t g; + c_EdgeWeightedDigraph_Init(&g, 3, NULL); + + /* Construct a graph with a negative cycle loop: + * 0 -> 1 (Weight: 2.0) + * 1 -> 2 (Weight: -1.0) + * 2 -> 1 (Weight: -2.0) -> Cycle 1->2->1 has total weight -3.0 (Negative Cycle!) + */ + c_EdgeWeightedDigraph_AddEdge(&g, 0, 1, 2.0); + c_EdgeWeightedDigraph_AddEdge(&g, 1, 2, -1.0); + c_EdgeWeightedDigraph_AddEdge(&g, 2, 1, -2.0); + + c_BellmanFordSP_t sp; + c_err_t err = c_BellmanFordSP_Init(&sp, &g, 0, &g.allocator); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* The negative cycle must be caught successfully */ + ASSERT_TRUE(c_BellmanFordSP_HasNegativeCycle(&sp)); + + c_VertexIdList_t cycle_loop; + c_VertexIdList_Init(&cycle_loop, 0, 0); + err = c_BellmanFordSP_GetNegativeCycle(&sp, &cycle_loop); + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_TRUE(c_VertexIdList_GetSize(&cycle_loop) > 0); + + c_VertexIdList_Destroy(&cycle_loop); + c_BellmanFordSP_Destroy(&sp); + c_EdgeWeightedDigraph_Destroy(&g); +} + +int main(void) { + TEST_START(BellmanFordSP_Engine_Suite); + RUN_TEST(test_bellman_ford_shortest_path_negative_weights); + RUN_TEST(test_bellman_ford_negative_cycle_detection); + TEST_REPORT(); + RETURN_TEST_STATUS; +} diff --git a/Graph/c_Bipartite.c b/Graph/c_Bipartite.c new file mode 100644 index 0000000..3a3c937 --- /dev/null +++ b/Graph/c_Bipartite.c @@ -0,0 +1,127 @@ +#include + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +// Internal deep structural validation walker checking color partitions +static void c_Bipartite_DFS_Internal(c_Bipartite_t* self, const c_Graph_t* G, c_VertexId_t v) { + self->marked[v] = C_TRUE; + + c_AdjList_t* list = c_Graph_GetAdjList((c_Graph_t*)G, v); + if (!list) return; + + // Cache-friendly sequential sweep over flat neighbor array blocks + for (c_size_t i = 0; i < list->size; i++) { + c_VertexId_t w = (c_VertexId_t)list->array[i]; + + // Short-circuit search operations if an odd cycle has already been populated + if (self->cycle.size > 0) return; + + if (!self->marked[w]) { + self->edge_to[w] = v; + self->color[w] = !self->color[v]; // Assign alternative inverted color mapping + c_Bipartite_DFS_Internal(self, G, w); + } + // If neighbor w is already marked and shares the same color, an odd cycle is confirmed + else if (self->color[w] == self->color[v]) { + self->is_bipartite = C_FALSE; + + // Reconstruct the odd-length cycle path using back-tracing loops + c_VertexIdList_t temp_stack; + if (c_VertexIdList_Init(&temp_stack, 8, &self->allocator) != C_SUCCESS) return; + + c_size_t x = v; + while (x != w && x != G->V) { + c_VertexIdList_Append(&temp_stack, (c_uint_t)x); + x = self->edge_to[x]; + } + c_VertexIdList_Append(&temp_stack, (c_uint_t)w); + c_VertexIdList_Append(&temp_stack, (c_uint_t)v); // Close cycle track frame + + // Invert elements to maintain correct chronological routing direction + for (c_size_t j = temp_stack.size; j > 0; j--) { + c_uint_t val; + c_VertexIdList_Get(&temp_stack, j - 1, &val); + c_VertexIdList_Append(&self->cycle, val); + } + + c_VertexIdList_Destroy(&temp_stack); + return; + } + } +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +c_err_t c_Bipartite_Init(c_Bipartite_t* self, const c_Graph_t* G, c_Allocator_t* allocator) { + if (!self || !G ) return C_ERR_PARAM; + + self->allocator = allocator?*allocator:c_DefaultAllocator; + self->is_bipartite = C_TRUE; + self->V = G->V; + self->marked = NULL; + self->color = NULL; + self->edge_to = NULL; + + if (c_UIntArray_Init(&self->cycle, 0, &self->allocator) != C_SUCCESS) { + return C_ERR_NOMEM; + } + + if (G->V == 0) return C_SUCCESS; + + self->marked = (c_bool_t*)c_Allocator_Alloc(&self->allocator, G->V * sizeof(*self->marked)); + self->color = (c_bool_t*)c_Allocator_Alloc(&self->allocator, G->V * sizeof(*self->color)); + self->edge_to = (c_size_t*)c_Allocator_Alloc(&self->allocator, G->V * sizeof(*self->edge_to)); + + if (!self->marked || !self->color || !self->edge_to) { + c_Bipartite_Destroy(self); + return C_ERR_NOMEM; + } + + memset(self->marked, 0, G->V * sizeof(*self->marked)); + memset(self->color, 0, G->V * sizeof(*self->color)); + for (c_size_t i = 0; i < G->V; i++) self->edge_to[i] = G->V; // Sentinel definition + + // Scan all clusters within graph partitions sequentially + for (c_VertexId_t v = 0; v < G->V; v++) { + if (!self->marked[v]) { + self->color[v] = C_FALSE; // Default baseline color option setting + c_Bipartite_DFS_Internal(self, G, v); + } + } + + return C_SUCCESS; +} + +void c_Bipartite_Destroy(c_Bipartite_t* self) { + if (!self) return; + + if (self->marked) c_Allocator_Free(&self->allocator, self->marked); + if (self->color) c_Allocator_Free(&self->allocator, self->color); + if (self->edge_to) c_Allocator_Free(&self->allocator, self->edge_to); + + c_UIntArray_Destroy(&self->cycle); + + self->marked = NULL; + self->color = NULL; + self->edge_to = NULL; + self->is_bipartite = C_FALSE; + self->V = 0; +} + +c_bool_t c_Bipartite_IsBipartite(const c_Bipartite_t* self) { + return self ? self->is_bipartite : C_FALSE; +} + +c_bool_t c_Bipartite_Color(const c_Bipartite_t* self, c_VertexId_t v) { + if (!self || v >= self->V || !self->color) return C_FALSE; + return self->color[v]; +} + +const c_VertexIdList_t* c_Bipartite_Cycle(const c_Bipartite_t* self) { + return self ? &self->cycle : NULL; +} + diff --git a/Graph/c_Bipartite.h b/Graph/c_Bipartite.h new file mode 100644 index 0000000..e3ce02f --- /dev/null +++ b/Graph/c_Bipartite.h @@ -0,0 +1,59 @@ +#ifndef INCLUDED_C_BIPARTITE_H +#define INCLUDED_C_BIPARTITE_H + +#ifndef INCLUDED_C_GRAPH_H +#include +#endif /*INCLUDED_C_GRAPH_H*/ + + +#ifndef INCLUDED_C_VERTEXIDLIST_H +#include +#endif /*INCLUDED_C_VERTEXIDLIST_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_bool_t* marked; // marked[v] = has vertex v been discovered? + c_bool_t* color; // color[v] = color assigned to vertex v (true/false) + c_size_t* edge_to; // edge_to[v] = last vertex on DFS path to v + c_VertexIdList_t cycle; // Stores an odd-length cycle if graph is not bipartite + c_bool_t is_bipartite; // Global bipartite validation status flag + c_size_t V; // Cached local vertex bound tracker + c_Allocator_t allocator; // Memory allocator reference instance copy +} c_Bipartite_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Determines whether an undirected graph is bipartite. + * @param self Pointer to the uninitialized bipartite tracking structure. + * @param G Pointer to the constant target graph object to verify. + * @param allocator Memory allocator instance pointer to deploy. + * @return C_SUCCESS on success, or an error status code on allocation failure. + */ +c_err_t c_Bipartite_Init(c_Bipartite_t* self, const c_Graph_t* G, c_Allocator_t* allocator); + +/** + * @brief Drops all internal allocation states within the bipartite instance safely. + */ +void c_Bipartite_Destroy(c_Bipartite_t* self); + +/** + * @brief Returns true if the graph is bipartite. + */ +c_bool_t c_Bipartite_IsBipartite(const c_Bipartite_t* self); + +/** + * @brief Returns the color assignment of vertex v. + */ +c_bool_t c_Bipartite_Color(const c_Bipartite_t* self, c_VertexId_t v); + +/** + * @brief Returns an odd-length cycle if the graph is not bipartite, or an empty list if it is. + */ +const c_VertexIdList_t* c_Bipartite_Cycle(const c_Bipartite_t* self); + +#endif /*INCLUDED_C_BIPARTITE_H*/ diff --git a/Graph/c_Bipartite.t.c b/Graph/c_Bipartite.t.c new file mode 100644 index 0000000..781cc0f --- /dev/null +++ b/Graph/c_Bipartite.t.c @@ -0,0 +1,59 @@ +#include "c_Bipartite.h" +#include "c_Test.h" + +#include +#include + +TEST_CASE(test_c_bipartite_classification) { + // Test Scenario A: Valid Bipartite Structure (Square structure: 0-1, 1-2, 2-3, 3-0) + c_Graph_t g_bipartite; + c_Graph_Init(&g_bipartite, 4, 0); + c_Graph_AddEdge(&g_bipartite, 0, 1); + c_Graph_AddEdge(&g_bipartite, 1, 2); + c_Graph_AddEdge(&g_bipartite, 2, 3); + c_Graph_AddEdge(&g_bipartite, 3, 0); + + c_Bipartite_t b1; + c_err_t err = c_Bipartite_Init(&b1, &g_bipartite, 0); + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_TRUE(c_Bipartite_IsBipartite(&b1)); + + // Neighbors must have matching complementary colored configurations + ASSERT_TRUE(c_Bipartite_Color(&b1, 0) != c_Bipartite_Color(&b1, 1)); + ASSERT_TRUE(c_Bipartite_Color(&b1, 0) == c_Bipartite_Color(&b1, 2)); + + // Test Scenario B: Non-Bipartite Structure (Triangle layout containing odd cycle: 0-1, 1-2, 2-0) + c_Graph_t g_invalid; + c_Graph_Init(&g_invalid, 3, 0); + c_Graph_AddEdge(&g_invalid, 0, 1); + c_Graph_AddEdge(&g_invalid, 1, 2); + c_Graph_AddEdge(&g_invalid, 2, 0); + + c_Bipartite_t b2; + err = c_Bipartite_Init(&b2, &g_invalid, 0); + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_FALSE(c_Bipartite_IsBipartite(&b2)); // Must fail check validation rules + + // Odd cycle array container details checking rules + const c_VertexIdList_t* cyc = c_Bipartite_Cycle(&b2); + ASSERT_PTR_NOT_NULL(cyc); + ASSERT_TRUE(cyc->size > 0); + + c_Bipartite_Destroy(&b1); + c_Bipartite_Destroy(&b2); + c_Graph_Destroy(&g_bipartite); + c_Graph_Destroy(&g_invalid); +} + + +int main(int argc, char** argv){ + + TEST_START(Component Tests); + + // Execution list configurations + RUN_TEST(test_c_bipartite_classification); + + TEST_REPORT(); + + RETURN_TEST_STATUS; +} diff --git a/Graph/c_BipartiteBFS.c b/Graph/c_BipartiteBFS.c new file mode 100644 index 0000000..db5cbec --- /dev/null +++ b/Graph/c_BipartiteBFS.c @@ -0,0 +1,168 @@ +#include + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +// Internal BFS helper routine to process a single connected component cluster +static void c_Bipartite_BFS_Internal(c_BipartiteBFS_t* self, const c_Graph_t* G, c_VertexId_t start) { + // We will use your c_VertexIdList_t dynamic array as an explicit FIFO queue block. + // To pop, we track a sliding 'head' cursor instead of physically shifting items. + c_VertexIdList_t queue; + if (c_VertexIdList_Init(&queue, 16, &self->allocator) != C_SUCCESS) return; + + self->marked[start] = C_TRUE; + self->color[start] = C_FALSE; // Initialize base level color assignment + if (c_VertexIdList_Append(&queue, (c_uint_t)start) != C_SUCCESS) { + c_VertexIdList_Destroy(&queue); + return; + } + + c_size_t queue_head = 0; + + while (queue_head < queue.size) { + // Dequeue structural element + c_VertexId_t v = (c_VertexId_t)queue.array[queue_head++]; + + c_AdjList_t* list = c_Graph_GetAdjList((c_Graph_t*)G, v); + if (!list) continue; + + // Cache-friendly sequential sweep over flat neighbor array blocks + for (c_size_t i = 0; i < list->size; i++) { + c_VertexId_t w = (c_VertexId_t)list->array[i]; + + if (!self->marked[w]) { + self->marked[w] = C_TRUE; + self->edge_to[w] = v; + self->color[w] = !self->color[v]; // Assign opposite color inversion mapping + if (c_VertexIdList_Append(&queue, (c_uint_t)w) != C_SUCCESS) { + c_VertexIdList_Destroy(&queue); + return; + } + } + // If neighbor w is discovered and has the same color, we've found an odd-length cycle! + else if (self->color[w] == self->color[v]) { + self->is_bipartite = C_FALSE; + + // Reconstruct the shortest odd-length cycle by back-tracing paths from v and w + // back to their lowest common ancestor (LCA) using edge_to coordinates. + c_VertexIdList_t path_v; + c_VertexIdList_t path_w; + if (c_VertexIdList_Init(&path_v, 8, &self->allocator) != C_SUCCESS) goto cleanup; + if (c_VertexIdList_Init(&path_w, 8, &self->allocator) != C_SUCCESS) { + c_VertexIdList_Destroy(&path_v); + goto cleanup; + } + + // Trace back route frames from v + c_size_t curr = v; + while (curr != G->V) { + c_VertexIdList_Append(&path_v, (c_uint_t)curr); + curr = self->edge_to[curr]; + } + + // Trace back route frames from w + curr = w; + while (curr != G->V) { + c_VertexIdList_Append(&path_w, (c_uint_t)curr); + curr = self->edge_to[curr]; + } + + // Find lowest common ancestor intersection boundary index + c_size_t p_v = path_v.size - 1; + c_size_t p_w = path_w.size - 1; + while (p_v > 0 && p_w > 0 && path_v.array[p_v - 1] == path_w.array[p_w - 1]) { + p_v--; + p_w--; + } + + // Build output sequence path layout out to self->cycle container: + // Format order flow: v -> ... -> LCA -> ... -> w -> v + for (c_size_t j = 0; j <= p_v; j++) { + c_VertexIdList_Append(&self->cycle, path_v.array[j]); + } + for (c_size_t j = p_w; j > 0; j--) { + c_VertexIdList_Append(&self->cycle, path_w.array[j - 1]); + } + c_VertexIdList_Append(&self->cycle, (c_uint_t)v); // Close cycle loop boundary + + c_VertexIdList_Destroy(&path_v); + c_VertexIdList_Destroy(&path_w); + c_VertexIdList_Destroy(&queue); + return; + } + } + } + +cleanup: + c_VertexIdList_Destroy(&queue); +} + +c_err_t c_BipartiteBFS_Init(c_BipartiteBFS_t* self, const c_Graph_t* G, c_Allocator_t* allocator) { + if (!self || !G) return C_ERR_PARAM; + + self->allocator = allocator?*allocator:c_DefaultAllocator; + self->is_bipartite = C_TRUE; + self->V = G->V; + self->marked = NULL; + self->color = NULL; + self->edge_to = NULL; + + if (c_VertexIdList_Init(&self->cycle, 0, &self->allocator) != C_SUCCESS) { + return C_ERR_NOMEM; + } + + if (G->V == 0) return C_SUCCESS; + + self->marked = (c_bool_t*)c_Allocator_Alloc(&self->allocator, G->V * sizeof(*self->marked)); + self->color = (c_bool_t*)c_Allocator_Alloc(&self->allocator, G->V * sizeof(*self->color)); + self->edge_to = (c_size_t*)c_Allocator_Alloc(&self->allocator, G->V * sizeof(*self->edge_to)); + + if (!self->marked || !self->color || !self->edge_to) { + c_BipartiteBFS_Destroy(self); + return C_ERR_NOMEM; + } + + memset(self->marked, 0, G->V * sizeof(*self->marked)); + memset(self->color, 0, G->V * sizeof(*self->color)); + for (c_size_t i = 0; i < G->V; i++) self->edge_to[i] = G->V; // Sentinel setting + + // Multi-component partition loop scanner sweeps + for (c_VertexId_t v = 0; v < G->V; v++) { + if (!self->marked[v]) { + c_Bipartite_BFS_Internal(self, G, v); + if (!self->is_bipartite) break; // Terminate early on odd cycle detection + } + } + + return C_SUCCESS; +} + +void c_BipartiteBFS_Destroy(c_BipartiteBFS_t* self) { + if (!self) return; + + if (self->marked) c_Allocator_Free(&self->allocator, self->marked); + if (self->color) c_Allocator_Free(&self->allocator, self->color); + if (self->edge_to) c_Allocator_Free(&self->allocator, self->edge_to); + + c_VertexIdList_Destroy(&self->cycle); + + self->marked = NULL; + self->color = NULL; + self->edge_to = NULL; + self->is_bipartite = C_FALSE; + self->V = 0; +} + +c_bool_t c_BipartiteBFS_IsBipartite(const c_BipartiteBFS_t* self) { + return self ? self->is_bipartite : C_FALSE; +} + +c_bool_t c_BipartiteBFS_Color(const c_BipartiteBFS_t* self, c_VertexId_t v) { + if (!self || v >= self->V || !self->color) return C_FALSE; + return self->color[v]; +} + +const c_VertexIdList_t* c_BipartiteBFS_Cycle(const c_BipartiteBFS_t* self) { + return self ? &self->cycle : NULL; +} + diff --git a/Graph/c_BipartiteBFS.h b/Graph/c_BipartiteBFS.h new file mode 100644 index 0000000..ebc521e --- /dev/null +++ b/Graph/c_BipartiteBFS.h @@ -0,0 +1,60 @@ +#ifndef INCLUDED_C_BIPARTITEBFS_H +#define INCLUDED_C_BIPARTITEBFS_H + +#ifndef INCLUDED_C_GRAPH_H +#include +#endif /*INCLUDED_C_GRAPH_H*/ + + +#ifndef INCLUDED_C_VERTEXIDLIST_H +#include +#endif /*INCLUDED_C_VERTEXIDLIST_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_bool_t* marked; // marked[v] = has vertex v been discovered? + c_bool_t* color; // color[v] = color assigned to vertex v (true/false) + c_size_t* edge_to; // edge_to[v] = last vertex on shortest path to v + c_VertexIdList_t cycle; // Stores the shortest odd-length cycle if found + c_bool_t is_bipartite; // Global bipartite validation status flag + c_size_t V; // Cached vertex count boundary + c_Allocator_t allocator; // Memory allocator copy +} c_BipartiteBFS_t; + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Determines whether an undirected graph is bipartite using Breadth-First Search. + * @param self Pointer to the uninitialized bipartite tracking structure. + * @param G Pointer to the constant target graph object to verify. + * @param allocator Memory allocator instance pointer to deploy. + * @return C_SUCCESS on success, or an error status code on allocation failure. + */ +c_err_t c_BipartiteBFS_Init(c_BipartiteBFS_t* self, const c_Graph_t* G, c_Allocator_t* allocator); + +/** + * @brief Drops all internal allocation states within the bipartite instance safely. + */ +void c_BipartiteBFS_Destroy(c_BipartiteBFS_t* self); + +/** + * @brief Returns true if the graph is bipartite. + */ +c_bool_t c_BipartiteBFS_IsBipartite(const c_BipartiteBFS_t* self); + +/** + * @brief Returns the color assignment of vertex v. + */ +c_bool_t c_BipartiteBFS_Color(const c_BipartiteBFS_t* self, c_VertexId_t v); + +/** + * @brief Returns the shortest odd-length cycle if the graph is not bipartite, or empty if it is. + */ +const c_VertexIdList_t* c_BipartiteBFS_Cycle(const c_BipartiteBFS_t* self); + +#endif /*INCLUDED_C_BIPARTITEBFS_H*/ diff --git a/Graph/c_BipartiteBFS.t.c b/Graph/c_BipartiteBFS.t.c new file mode 100644 index 0000000..8dd34fd --- /dev/null +++ b/Graph/c_BipartiteBFS.t.c @@ -0,0 +1,42 @@ +#include "c_BipartiteBFS.h" +#include "c_Test.h" +#include +#include + +TEST_CASE(test_c_bipartite_bfs_engine) { + // Graph configuration containing an odd cycle (Pentagon shape: 5 elements) + c_Graph_t g_odd; + c_Graph_Init(&g_odd, 5, 0); + c_Graph_AddEdge(&g_odd, 0, 1); + c_Graph_AddEdge(&g_odd, 1, 2); + c_Graph_AddEdge(&g_odd, 2, 3); + c_Graph_AddEdge(&g_odd, 3, 4); + c_Graph_AddEdge(&g_odd, 4, 0); // odd loop boundary closure + + c_BipartiteBFS_t search; + c_err_t err = c_BipartiteBFS_Init(&search, &g_odd, 0); + + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_FALSE(c_BipartiteBFS_IsBipartite(&search)); // Must identify non-bipartite structural properties + + // Verify shortest odd cycle path extraction + const c_VertexIdList_t* loop_cycle = c_BipartiteBFS_Cycle(&search); + ASSERT_PTR_NOT_NULL(loop_cycle); + ASSERT_TRUE(loop_cycle->size == 6); // A closed 5-vertex loop cycle holds 6 routing entries + + c_BipartiteBFS_Destroy(&search); + c_Graph_Destroy(&g_odd); +} + + +int main(int argc, char** argv){ + + TEST_START(Component Tests); + + // Execution list configurations + RUN_TEST(test_c_bipartite_bfs_engine); + + TEST_REPORT(); + + RETURN_TEST_STATUS; +} diff --git a/Graph/c_BoruvkaMST.c b/Graph/c_BoruvkaMST.c new file mode 100644 index 0000000..cc482ec --- /dev/null +++ b/Graph/c_BoruvkaMST.c @@ -0,0 +1,99 @@ +#include +#include "c_QuickFindUF.h" + +#define TC_SENTINEL ((c_size_t)-1) + +c_err_t c_BoruvkaMST_Init(c_BoruvkaMST_t* self, c_EdgeWeightedGraph_t* graph, c_Allocator_t* allocator) { + if (!self || !graph) return C_ERR_PARAM; + + self->allocator = allocator ? *allocator : c_DefaultAllocator; + self->weight = 0.0; + c_VertexIdList_Init(&self->mst_edges, 0, allocator); + + if (graph->V == 0 || graph->E == 0) return C_SUCCESS; + + /* 1. Initialize Union-Find to manage tree fragments */ + c_QuickFindUF_t uf; + c_err_t err = c_QuickFindUF_Init(&uf, graph->V, allocator); + if (err != C_SUCCESS) return err; + + /* 2. Allocate a tracking array to store the closest/cheapest edge ID for each component */ + c_size_t* closest_edge_to_component = (c_size_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_size_t)); + if (!closest_edge_to_component) { + c_QuickFindUF_Destroy(&uf); + return C_ERR_NOMEM; + } + + /* 3. Execute successive merging phases (At most log(V) passes) */ + /* Loop until either the tree reaches V - 1 edges or we can't find any more merging cuts */ + for (c_size_t stage = 1; stage < graph->V; stage *= 2) { + + /* Reset closest edge array entries before scanning the edge pool */ + for (c_size_t i = 0; i < graph->V; ++i) { + closest_edge_to_component[i] = TC_SENTINEL; + } + + /* Scan every undirected edge to discover the absolute minimum weight cuts for all existing components */ + for (c_size_t e = 0; e < graph->E; ++e) { + c_Edge_t* edge = &graph->edges_pool[e]; + c_size_t v = edge->v; + c_size_t w = edge->w; + + c_size_t comp_v = 0; + c_size_t comp_w = 0; + c_QuickFindUF_Find(&uf, v, &comp_v); + c_QuickFindUF_Find(&uf, w, &comp_w); + + /* If they already share the same component ID, adding this edge would create a cycle */ + if (comp_v == comp_w) continue; + + /* Check and update the closest edge for component v */ + if (closest_edge_to_component[comp_v] == TC_SENTINEL || + edge->weight < graph->edges_pool[closest_edge_to_component[comp_v]].weight) { + closest_edge_to_component[comp_v] = e; + } + + /* Check and update the closest edge for component w */ + if (closest_edge_to_component[comp_w] == TC_SENTINEL || + edge->weight < graph->edges_pool[closest_edge_to_component[comp_w]].weight) { + closest_edge_to_component[comp_w] = e; + } + } + + /* Collect and unify components using the best edges found in this phase */ + c_bool_t edges_added_this_phase = C_FALSE; + for (c_size_t i = 0; i < graph->V; ++i) { + c_size_t e = closest_edge_to_component[i]; + if (e != TC_SENTINEL) { + c_Edge_t* edge = &graph->edges_pool[e]; + c_size_t v = edge->v; + c_size_t w = edge->w; + + if (!c_QuickFindUF_IsConnected(&uf, v, w)) { + c_QuickFindUF_Union(&uf, v, w); + c_VertexIdList_Append(&self->mst_edges, (c_uint_t)e); + self->weight += edge->weight; + edges_added_this_phase = C_TRUE; + } + } + } + + /* If no new cross-component cuts were discovered, the spanning tree or forest is complete */ + if (!edges_added_this_phase) break; + } + + c_Allocator_Free(&self->allocator, closest_edge_to_component); + c_QuickFindUF_Destroy(&uf); + return C_SUCCESS; +} + +void c_BoruvkaMST_Destroy(c_BoruvkaMST_t* self) { + if (!self) return; + c_VertexIdList_Destroy(&self->mst_edges); + self->weight = 0.0; +} + +c_err_t c_BoruvkaMST_GetEdges(c_BoruvkaMST_t* self, c_VertexIdList_t* out_edges) { + if (!self || !out_edges) return C_ERR_PARAM; + return c_VertexIdList_Copy(out_edges, &self->mst_edges); +} diff --git a/Graph/c_BoruvkaMST.h b/Graph/c_BoruvkaMST.h new file mode 100644 index 0000000..f3d55bc --- /dev/null +++ b/Graph/c_BoruvkaMST.h @@ -0,0 +1,51 @@ +#ifndef INCLUDED_C_BORUVKAMST_H +#define INCLUDED_C_BORUVKAMST_H + +#ifndef INCLUDED_C_EDGEWEIGHTEDGRAPH_H +#include +#endif /*INCLUDED_C_EDGEWEIGHTEDGRAPH_H*/ + + +#ifndef INCLUDED_C_VERTEXIDLIST_H +#include +#endif /*INCLUDED_C_VERTEXIDLIST_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_VertexIdList_t mst_edges; /* Final optimized list of edge_ids inside the tree */ + double weight; /* Total minimum weight summation of the MST */ + c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ +} c_BoruvkaMST_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Computes a minimum spanning tree of an edge-weighted graph using Boruvka's algorithm. + * @param allocator Explicit allocator context used to configure internal tracking state memory + */ +c_err_t c_BoruvkaMST_Init(c_BoruvkaMST_t* self, c_EdgeWeightedGraph_t* graph, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers + */ +void c_BoruvkaMST_Destroy(c_BoruvkaMST_t* self); + +/** + * @brief Gets a copy of the edge ids included in the Minimum Spanning Tree. + */ +c_err_t c_BoruvkaMST_GetEdges(c_BoruvkaMST_t* self, c_VertexIdList_t* out_edges); + +/** + * @brief Returns the total weight summation of the MST. + */ +C_STATIC_FORCE_INLINE +double c_BoruvkaMST_Weight(c_BoruvkaMST_t* self) { + return self ? self->weight : 0.0; +} + + +#endif /*INCLUDED_C_BORUVKAMST_H*/ diff --git a/Graph/c_BoruvkaMST.t.c b/Graph/c_BoruvkaMST.t.c new file mode 100644 index 0000000..ada08bc --- /dev/null +++ b/Graph/c_BoruvkaMST.t.c @@ -0,0 +1,48 @@ +#include "c_Test.h" +#include "c_EdgeWeightedGraph.h" +#include "c_BoruvkaMST.h" +#include "c_VertexIdList.h" + +TEST_CASE(test_boruvka_mst_evaluation) { + c_EdgeWeightedGraph_t g; + c_err_t err = c_EdgeWeightedGraph_Init(&g, 4, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Construct a generic evaluation cyclic graph: + * 0 - 1 (Weight: 1.0) -> Expected in MST + * 1 - 2 (Weight: 2.0) -> Expected in MST + * 2 - 3 (Weight: 3.0) -> Expected in MST + * 3 - 0 (Weight: 4.0) -> Skipped + * 0 - 2 (Weight: 5.0) -> Skipped + */ + c_EdgeWeightedGraph_AddEdge(&g, 0, 1, 1.0); + c_EdgeWeightedGraph_AddEdge(&g, 1, 2, 2.0); + c_EdgeWeightedGraph_AddEdge(&g, 2, 3, 3.0); + c_EdgeWeightedGraph_AddEdge(&g, 3, 0, 4.0); + c_EdgeWeightedGraph_AddEdge(&g, 0, 2, 5.0); + + c_BoruvkaMST_t boruvka; + err = c_BoruvkaMST_Init(&boruvka, &g, 0); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Minimal spanning total weight sum must equal 1.0 + 2.0 + 3.0 = 6.0 */ + ASSERT_DOUBLE_EQ_MSG(6.0, c_BoruvkaMST_Weight(&boruvka), "Boruvka MST weight computation mismatch"); + + c_VertexIdList_t result_list; + c_VertexIdList_Init(&result_list, 0, 0); + + err = c_BoruvkaMST_GetEdges(&boruvka, &result_list); + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_INT_EQ(3, c_UIntArray_GetSize(&result_list)); + + c_VertexIdList_Destroy(&result_list); + c_BoruvkaMST_Destroy(&boruvka); + c_EdgeWeightedGraph_Destroy(&g); +} + +int main(void) { + TEST_START(Boruvka_MST_Verification_Suite); + RUN_TEST(test_boruvka_mst_evaluation); + TEST_REPORT(); + RETURN_TEST_STATUS; +} diff --git a/Graph/c_BreadthFirstDirectedPaths.c b/Graph/c_BreadthFirstDirectedPaths.c new file mode 100644 index 0000000..e9574f2 --- /dev/null +++ b/Graph/c_BreadthFirstDirectedPaths.c @@ -0,0 +1,113 @@ +#include + + +c_err_t c_BreadthFirstDirectedPaths_Init(c_BreadthFirstDirectedPaths_t* self, const c_Digraph_t* graph, c_size_t s, c_Allocator_t* allocator) { + if (!self || !graph || s >= graph->V) return C_ERR_PARAM; + + self->allocator = allocator ? *allocator : c_DefaultAllocator; + self->s = s; + + /* 1. Allocate tracking arrays */ + self->marked = (c_bool_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_bool_t)); + if (!self->marked) return C_ERR_NOMEM; + + self->edge_to = (c_size_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_size_t)); + if (!self->edge_to) { + c_Allocator_Free(&self->allocator, self->marked); + self->marked = NULL; + return C_ERR_NOMEM; + } + + self->dist_to = (c_size_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_size_t)); + if (!self->dist_to) { + c_Allocator_Free(&self->allocator, self->marked); + c_Allocator_Free(&self->allocator, self->edge_to); + self->marked = NULL; + self->edge_to = NULL; + return C_ERR_NOMEM; + } + + /* 2. Allocate an explicit circular/linear queue layout matching V bounds */ + c_size_t* queue = (c_size_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_size_t)); + if (!queue) { + c_BreadthFirstDirectedPaths_Destroy(self); + return C_ERR_NOMEM; + } + + c_size_t head = 0; + c_size_t tail = 0; + + /* Enqueue source */ + self->marked[s] = C_TRUE; + self->dist_to[s] = 0; + queue[tail++] = s; + + /* 3. BFS Main Loop Processing Engine */ + while (head < tail) { + c_size_t v = queue[head++]; + + c_AdjList_t* adj = &graph->adj_list[v]; + c_size_t size = (c_size_t)c_UIntArray_GetSize(adj); + + for (c_size_t i = 0; i < size; ++i) { + c_uint_t target_value = 0; + /* Safely query edge list element matching pointer specifications */ + c_err_t err = c_UIntArray_Get(adj, i, &target_value); + + if (err == C_SUCCESS) { + c_size_t w = (c_size_t)target_value; + if (!self->marked[w]) { + self->edge_to[w] = v; + self->dist_to[w] = self->dist_to[v] + 1; + self->marked[w] = C_TRUE; + queue[tail++] = w; /* Enqueue step */ + } + } + } + } + + c_Allocator_Free(&self->allocator, queue); + return C_SUCCESS; +} + +void c_BreadthFirstDirectedPaths_Destroy(c_BreadthFirstDirectedPaths_t* self) { + if (!self) return; + if (self->marked) { + c_Allocator_Free(&self->allocator, self->marked); + self->marked = NULL; + } + if (self->edge_to) { + c_Allocator_Free(&self->allocator, self->edge_to); + self->edge_to = NULL; + } + if (self->dist_to) { + c_Allocator_Free(&self->allocator, self->dist_to); + self->dist_to = NULL; + } + self->s = 0; +} + +c_err_t c_BreadthFirstDirectedPaths_PathTo(c_BreadthFirstDirectedPaths_t* self, c_size_t v, c_size_t total_V, c_AdjList_t* out_path) { + if (!self || !out_path || v >= total_V) return C_ERR_PARAM; + if (!c_BreadthFirstDirectedPaths_HasPathTo(self, v, total_V)) return C_ERR_FAIL; + + /* Temporary track trace stack array */ + c_size_t* reverse_stack = (c_size_t*)c_Allocator_Calloc(&self->allocator, total_V, sizeof(c_size_t)); + if (!reverse_stack) return C_ERR_NOMEM; + + c_size_t stack_size = 0; + for (c_size_t x = v; x != self->s; x = self->edge_to[x]) { + reverse_stack[stack_size++] = x; + } + reverse_stack[stack_size++] = self->s; + + c_err_t err = C_SUCCESS; + while (stack_size > 0) { + c_size_t current_vertex = reverse_stack[--stack_size]; + err = c_AdjList_Append(out_path, (c_uint_t)current_vertex); + if (err != C_SUCCESS) break; + } + + c_Allocator_Free(&self->allocator, reverse_stack); + return err; +} diff --git a/Graph/c_BreadthFirstDirectedPaths.h b/Graph/c_BreadthFirstDirectedPaths.h new file mode 100644 index 0000000..cdee2b3 --- /dev/null +++ b/Graph/c_BreadthFirstDirectedPaths.h @@ -0,0 +1,63 @@ +#ifndef INCLUDED_C_BREADTHFIRSTDIRECTEDPATHS_H +#define INCLUDED_C_BREADTHFIRSTDIRECTEDPATHS_H + +#ifndef INCLUDED_C_DIGRAPH_H +#include +#endif /*INCLUDED_C_DIGRAPH_H*/ + +#ifndef INCLUDED_C_VERTEXIDLIST_H +#include +#endif /*INCLUDED_C_VERTEXIDLIST_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_bool_t* marked; /* Visited tracking array (size of graph->V) */ + c_size_t* edge_to; /* edge_to[w] = last edge on shortest path from s to w */ + c_size_t* dist_to; /* dist_to[w] = number of edges on shortest path from s to w */ + c_size_t s; /* Source vertex */ + c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ +} c_BreadthFirstDirectedPaths_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Computes shortest directed paths from a single source vertex 's' using BFS + * @param allocator Explicit allocator context used to allocate internal routing arrays + */ +c_err_t c_BreadthFirstDirectedPaths_Init(c_BreadthFirstDirectedPaths_t* self, const c_Digraph_t* graph, c_size_t s, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers + */ +void c_BreadthFirstDirectedPaths_Destroy(c_BreadthFirstDirectedPaths_t* self); + +/** + * @brief Is there a directed path from the source 's' to vertex 'v'? + */ +C_STATIC_FORCE_INLINE +c_bool_t c_BreadthFirstDirectedPaths_HasPathTo(c_BreadthFirstDirectedPaths_t* self, c_size_t v, c_size_t total_V) { + if (!self || !self->marked || v >= total_V) return C_FALSE; + return self->marked[v]; +} + +/** + * @brief Returns the number of edges in a shortest path from the source 's' to vertex 'v' + * @return Distance value, or C_SIZE_MAX if unreachable / parameter error + */ +C_STATIC_FORCE_INLINE +c_size_t c_BreadthFirstDirectedPaths_DistTo(c_BreadthFirstDirectedPaths_t* self, c_size_t v, c_size_t total_V) { + if (!self || v >= total_V || !self->marked[v]) return C_SIZE_MAX; + return self->dist_to[v]; +} + +/** + * @brief Reconstructs the exact shortest path from source 's' to vertex 'v' and appends it to out_path + */ +c_err_t c_BreadthFirstDirectedPaths_PathTo(c_BreadthFirstDirectedPaths_t* self, c_size_t v, c_size_t total_V, c_VertexIdList_t* out_path); + + +#endif /*INCLUDED_C_BREADTHFIRSTDIRECTEDPATHS_H*/ diff --git a/Graph/c_BreadthFirstDirectedPaths.t.c b/Graph/c_BreadthFirstDirectedPaths.t.c new file mode 100644 index 0000000..5a17e85 --- /dev/null +++ b/Graph/c_BreadthFirstDirectedPaths.t.c @@ -0,0 +1,58 @@ +#include "c_BreadthFirstDirectedPaths.h" +#include "c_Test.h" +#include "c_Digraph.h" + +TEST_CASE(test_breadth_first_directed_paths) { + c_Digraph_t g; + c_Digraph_Init(&g, 4, NULL); + + /* Construct graph with multiple paths to 3: + * Path A (Longer): 0 -> 1 -> 2 -> 3 + * Path B (Shortest): 0 -> 3 + */ + c_Digraph_AddEdge(&g, 0, 1); + c_Digraph_AddEdge(&g, 1, 2); + c_Digraph_AddEdge(&g, 2, 3); + c_Digraph_AddEdge(&g, 0, 3); /* Short-circuit edge directly to 3 */ + + c_BreadthFirstDirectedPaths_t bfs_paths; + c_err_t err = c_BreadthFirstDirectedPaths_Init(&bfs_paths, &g, 0, &g.allocator); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Assert reachability and verified shortest path metrics */ + ASSERT_TRUE(c_BreadthFirstDirectedPaths_HasPathTo(&bfs_paths, 3, g.V)); + ASSERT_LL_EQ(1, c_BreadthFirstDirectedPaths_DistTo(&bfs_paths, 3, g.V)); /* Distance must be exactly 1 edge */ + + c_VertexIdList_t final_path; + c_VertexIdList_Init(&final_path, 0, NULL); + + err = c_BreadthFirstDirectedPaths_PathTo(&bfs_paths, 3, g.V, &final_path); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Path size should be 2 for the short circuit route: [0, 3] */ + ASSERT_INT_EQ(2, c_VertexIdList_GetSize(&final_path)); + + c_uint_t val = 0; + c_VertexIdList_Get(&final_path, 0, &val); + ASSERT_LL_EQ(0, val); + + c_VertexIdList_Get(&final_path, 1, &val); + ASSERT_LL_EQ(3, val); + + c_VertexIdList_Destroy(&final_path); + c_BreadthFirstDirectedPaths_Destroy(&bfs_paths); + c_Digraph_Destroy(&g); +} + + + +int main(int argc, char** argv){ + TEST_START(Component Tests); + + // Execution list configurations + RUN_TEST(test_breadth_first_directed_paths); + + TEST_REPORT(); + + RETURN_TEST_STATUS; +} diff --git a/Graph/c_CC.c b/Graph/c_CC.c new file mode 100644 index 0000000..ba47ad3 --- /dev/null +++ b/Graph/c_CC.c @@ -0,0 +1,110 @@ +#include + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +// Internal deep structural trace walker that marks vertices within a cluster component +static void c_CC_DFS_Internal(c_CC_t* self, const c_Graph_t* G, c_VertexId_t v, c_VertexId_t current_id) { + self->marked[v] = C_TRUE; + self->id[v] = current_id; + self->size[current_id]++; + + // Access the array-backed adjacency list through the internal graph helper + c_AdjList_t* list = c_Graph_GetAdjList((c_Graph_t*)G, v); + if (!list) return; + + // Cache-friendly sequential sweep over flat neighbor array blocks + for (c_size_t i = 0; i < list->size; i++) { + c_VertexId_t w = (c_VertexId_t)list->array[i]; + if (!self->marked[w]) { + c_CC_DFS_Internal(self, G, w, current_id); + } + } +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_CC_Init(c_CC_t* self, const c_Graph_t* G, c_Allocator_t* allocator) { + if (!self || !G ) { + return C_ERR_PARAM; + } + + self->allocator = allocator?*allocator:c_DefaultAllocator; + self->count = 0; + self->marked = NULL; + self->id = NULL; + self->size = NULL; + self->V = G->V; + + if (G->V == 0) { + return C_SUCCESS; + } + + // Allocate structural metadata array blocks via your custom allocator abstraction layer + self->marked = (c_bool_t*)c_Allocator_Alloc(&self->allocator, G->V * sizeof(*self->marked)); + self->id = (c_size_t*)c_Allocator_Alloc(&self->allocator, G->V * sizeof(*self->id)); + self->size = (c_size_t*)c_Allocator_Alloc(&self->allocator, G->V * sizeof(*self->size)); + + if (!self->marked || !self->id || !self->size) { + c_CC_Destroy(self); + return C_ERR_NOMEM; + } + + // Zero out state maps cleanly + memset(self->marked, 0, G->V * sizeof(*self->marked)); + memset(self->id, 0, G->V * sizeof(*self->id)); + memset(self->size, 0, G->V * sizeof(*self->size)); + + // Run partition sweeps over all available unvisited structural nodes + for (c_VertexId_t v = 0; v < G->V; v++) { + if (!self->marked[v]) { + c_CC_DFS_Internal(self, G, v, self->count); + self->count++; + } + } + + return C_SUCCESS; +} + +void c_CC_Destroy(c_CC_t* self) { + if (!self) return; + + if (self->marked) c_Allocator_Free(&self->allocator, self->marked); + if (self->id) c_Allocator_Free(&self->allocator, self->id); + if (self->size) c_Allocator_Free(&self->allocator, self->size); + + self->marked = NULL; + self->id = NULL; + self->size = NULL; + self->count = 0; + self->V = 0; +} + +c_bool_t c_CC_Connected(const c_CC_t* self, c_VertexId_t v, c_VertexId_t w) { + // Parameter boundary filter protection using the internally cached V + if (!self || v >= self->V || w >= self->V || !self->id) { + return C_FALSE; + } + return self->id[v] == self->id[w]; +} + +c_size_t c_CC_Id(const c_CC_t* self, c_VertexId_t v) { + if (!self || v >= self->V || !self->id) { + return 0; // Return safe default index bounds if lookup fails + } + return self->id[v]; +} + +c_size_t c_CC_Size(const c_CC_t* self, c_VertexId_t v) { + if (!self || v >= self->V || !self->id || !self->size) { + return 0; + } + return self->size[self->id[v]]; +} + +c_size_t c_CC_Count(const c_CC_t* self) { + if (!self) return 0; + return self->count; +} diff --git a/Graph/c_CC.h b/Graph/c_CC.h new file mode 100644 index 0000000..6026bf1 --- /dev/null +++ b/Graph/c_CC.h @@ -0,0 +1,56 @@ +#ifndef INCLUDED_C_CC_H +#define INCLUDED_C_CC_H + +#ifndef INCLUDED_C_GRAPH_H +#include +#endif /*INCLUDED_C_GRAPH_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_bool_t* marked; // marked[v] = has vertex v been discovered? + c_size_t* id; // id[v] = connected component identifier id of v + c_size_t* size; // size[id] = number of vertices in component id + c_size_t count; // Total number of isolated connected components + c_size_t V; + c_Allocator_t allocator; // Memory allocator reference instance copy +} c_CC_t; + + +/** + * @brief Computes the connected components partition of an undirected graph. + * @param self Pointer to the uninitialized connected components state tracker. + * @param G Pointer to the constant target graph object to process. + * @param allocator Memory allocator instance pointer to deploy. + * @return C_SUCCESS on success, or an error status code on allocation failure. + */ +c_err_t c_CC_Init(c_CC_t* self, const c_Graph_t* G, c_Allocator_t* allocator); + +/** + * @brief Drops all internal allocation states within the CC structural instance safely. + */ +void c_CC_Destroy(c_CC_t* self); + +/** + * @brief Are vertices v and w in the same connected component cluster? + */ +c_bool_t c_CC_Connected(const c_CC_t* self, c_VertexId_t v, c_VertexId_t w); + +/** + * @brief Returns the component identifier id containing vertex v (ranges from 0 to Count()-1). + */ +c_size_t c_CC_Id(const c_CC_t* self, c_VertexId_t v); + +/** + * @brief Returns the number of vertices in the connected component cluster containing vertex v. + */ +c_size_t c_CC_Size(const c_CC_t* self, c_VertexId_t v); + +/** + * @brief Returns the total number of connected components clusters in the graph. + */ +c_size_t c_CC_Count(const c_CC_t* self); + +#endif /*INCLUDED_C_CC_H*/ diff --git a/Graph/c_CC.t.c b/Graph/c_CC.t.c new file mode 100644 index 0000000..13900f0 --- /dev/null +++ b/Graph/c_CC.t.c @@ -0,0 +1,59 @@ +#include "c_CC.h" +#include "c_Test.h" + +#include +#include + +TEST_CASE(test_c_cc_streamlined_queries) { + c_Graph_t graph; + c_Graph_Init(&graph, 6, 0); + + // Component 0: Vertices {0, 1, 2} + c_Graph_AddEdge(&graph, 0, 1); + c_Graph_AddEdge(&graph, 1, 2); + + // Component 1: Vertices {3, 4} + c_Graph_AddEdge(&graph, 3, 4); + + // Component 2: Vertex {5} (Isolated) + + c_CC_t cc; + c_err_t err = c_CC_Init(&cc, &graph, 0); + ASSERT_INT_EQ(C_SUCCESS, err); + + // Verify your clean standalone queries (Notice: no graph pointers needed here!) + ASSERT_LL_EQ(3, c_CC_Count(&cc)); + + // Test connectivity groupings + ASSERT_TRUE(c_CC_Connected(&cc, 0, 2)); + ASSERT_TRUE(c_CC_Connected(&cc, 3, 4)); + ASSERT_FALSE(c_CC_Connected(&cc, 1, 5)); + + // Test partition tracking sizes + ASSERT_LL_EQ(3, c_CC_Size(&cc, 0)); // Component 0 contains 3 vertices + ASSERT_LL_EQ(2, c_CC_Size(&cc, 3)); // Component 1 contains 2 vertices + ASSERT_LL_EQ(1, c_CC_Size(&cc, 5)); // Component 2 contains 1 vertex + + // Test unique component ID mapping invariants + ASSERT_LL_EQ(c_CC_Id(&cc, 0), c_CC_Id(&cc, 2)); + ASSERT_TRUE(c_CC_Id(&cc, 1) != c_CC_Id(&cc, 4)); + + // Test out of bounds security protection filters + ASSERT_FALSE(c_CC_Connected(&cc, 0, 999)); + ASSERT_LL_EQ(0, c_CC_Id(&cc, 999)); + + c_CC_Destroy(&cc); + c_Graph_Destroy(&graph); +} + + +int main(int argc, char** argv){ + TEST_START(c_CC Component Tests); + + // Execution list configurations + RUN_TEST(test_c_cc_streamlined_queries); + + TEST_REPORT(); + + RETURN_TEST_STATUS; +} diff --git a/Graph/c_CPM.c b/Graph/c_CPM.c new file mode 100644 index 0000000..b9f1a20 --- /dev/null +++ b/Graph/c_CPM.c @@ -0,0 +1,50 @@ +#include + + +c_err_t c_CPM_Init(c_CPM_t* self, c_size_t num_tasks, const double* task_durations, c_Allocator_t* allocator) { + if (!self || num_tasks == 0 || !task_durations) return C_ERR_PARAM; + + self->allocator = allocator ? *allocator : c_DefaultAllocator; + self->num_tasks = num_tasks; + + /* Internal node architecture layout mapping: + * - Task i start node: i + * - Task i finish node: i + num_tasks + * - Virtual source node: 2 * num_tasks + * - Virtual sink node: 2 * num_tasks + 1 + */ + self->source_node = 2 * num_tasks; + self->sink_node = 2 * num_tasks + 1; + self->V = 2 * num_tasks + 2; + + return C_SUCCESS; +} + + +c_err_t c_CPM_AddDependency(c_CPM_t* self, c_EdgeWeightedDigraph_t* working_graph, c_size_t prereq_task, c_size_t target_task) { + if (!self || !working_graph || prereq_task >= self->num_tasks || target_task >= self->num_tasks) return C_ERR_PARAM; + + /* Connect the FINISH node of prereq to the START node of target */ + return c_EdgeWeightedDigraph_AddEdge(working_graph, prereq_task + self->num_tasks, target_task, 0.0); +} + +c_err_t c_CPM_Calculate(c_CPM_t* self, c_EdgeWeightedDigraph_t* working_graph) { + if (!self || !working_graph) return C_ERR_PARAM; + + /* Execute the linear Acyclic Longest Path pass from our virtual source anchor */ + return c_AcyclicLP_Init(&self->lp_engine, working_graph, self->source_node, &self->allocator); +} + +void c_CPM_Destroy(c_CPM_t* self) { + if (!self) return; + c_AcyclicLP_Destroy(&self->lp_engine); + self->num_tasks = 0; + self->source_node = 0; + self->sink_node = 0; + self->V = 0; +} + +c_err_t c_CPM_GetCriticalPath(c_CPM_t* self, c_VertexIdList_t* out_critical_edges) { + if (!self || !out_critical_edges) return C_ERR_PARAM; + return c_AcyclicLP_PathTo(&self->lp_engine, self->sink_node, out_critical_edges); +} diff --git a/Graph/c_CPM.h b/Graph/c_CPM.h new file mode 100644 index 0000000..c08b53f --- /dev/null +++ b/Graph/c_CPM.h @@ -0,0 +1,78 @@ +#ifndef INCLUDED_C_CPM_H +#define INCLUDED_C_CPM_H + +#ifndef INCLUDED_C_EDGEWEIGHTEDDIGRAPH_H +#include +#endif /*INCLUDED_C_EDGEWEIGHTEDDIGRAPH_H*/ + + +#ifndef INCLUDED_C_ACYCLICLP_H +#include +#endif /*INCLUDED_C_ACYCLICLP_H*/ + + +#ifndef INCLUDED_C_VERTEXIDLIST_H +#include +#endif /*INCLUDED_C_VERTEXIDLIST_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_AcyclicLP_t lp_engine; /* Inner longest-path engine */ + c_size_t num_tasks; /* Raw number of user tasks */ + c_size_t source_node; /* Virtual source node index */ + c_size_t sink_node; /* Virtual sink node index */ + c_size_t V; /* Total inner nodes count (2 * num_tasks + 2) */ + c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ +} c_CPM_t; + + +/** + * @brief Initializes and executes the Critical Path Method scheduler. + * @param task_durations Array of doubles containing durations for each task (size = num_tasks) + * @param allocator Explicit allocator context used to configure internal tracking state memory + */ +c_err_t c_CPM_Init(c_CPM_t* self, c_size_t num_tasks, const double* task_durations, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers safely. + */ +void c_CPM_Destroy(c_CPM_t* self); + +/** + * @brief Adds a dependency constraint indicating that 'prereq_task' must finish before 'target_task' can start. + */ +c_err_t c_CPM_AddDependency(c_CPM_t* self, c_EdgeWeightedDigraph_t* working_graph, c_size_t prereq_task, c_size_t target_task); + +/** + * @brief Finalizes the calculation after all dependencies are added. + */ +c_err_t c_CPM_Calculate(c_CPM_t* self, c_EdgeWeightedDigraph_t* working_graph); + +/** + * @brief Returns the minimum total project duration. + */ +C_STATIC_FORCE_INLINE +double c_CPM_GetProjectDuration(c_CPM_t* self) { + if (!self) return 0.0; + return c_AcyclicLP_DistTo(&self->lp_engine, self->sink_node); +} + +/** + * @brief Returns the Early Start (ES) time for a given task. + */ +C_STATIC_FORCE_INLINE +double c_CPM_GetEarlyStart(c_CPM_t* self, c_size_t task_id) { + if (!self || task_id >= self->num_tasks) return 0.0; + return c_AcyclicLP_DistTo(&self->lp_engine, task_id); +} + +/** + * @brief Extracts the sequence of global edge IDs that form the critical path. + */ +c_err_t c_CPM_GetCriticalPath(c_CPM_t* self, c_VertexIdList_t* out_critical_edges); + + +#endif /*INCLUDED_C_CPM_H*/ diff --git a/Graph/c_CPM.t.c b/Graph/c_CPM.t.c new file mode 100644 index 0000000..eaf287d --- /dev/null +++ b/Graph/c_CPM.t.c @@ -0,0 +1,87 @@ +#include "c_Test.h" +#include "c_CPM.h" + +/* External declaration of helper from c_CPM.c */ +static c_err_t c_CPM_CreateWorkingGraph(c_CPM_t* self, const double* task_durations, c_EdgeWeightedDigraph_t* out_graph) { + c_err_t err = c_EdgeWeightedDigraph_Init(out_graph, self->V, &self->allocator); + if (err != C_SUCCESS) return err; + + for (c_size_t i = 0; i < self->num_tasks; ++i) { + /* Connect Start node i to Finish node (i + num_tasks) using duration weight */ + err = c_EdgeWeightedDigraph_AddEdge(out_graph, i, i + self->num_tasks, task_durations[i]); + if (err != C_SUCCESS) goto fallback; + + /* Hook virtual network source up to task start nodes */ + err = c_EdgeWeightedDigraph_AddEdge(out_graph, self->source_node, i, 0.0); + if (err != C_SUCCESS) goto fallback; + + /* Hook task finish nodes down into virtual destination sink node */ + err = c_EdgeWeightedDigraph_AddEdge(out_graph, i + self->num_tasks, self->sink_node, 0.0); + if (err != C_SUCCESS) goto fallback; + } + + return C_SUCCESS; + + fallback: + c_EdgeWeightedDigraph_Destroy(out_graph); + return err; +} + +TEST_CASE(test_critical_path_method_scheduling) { + /* Let's model a 3-task project layout: + * Task 0: Duration 5.0 days + * Task 1: Duration 10.0 days + * Task 2: Duration 3.0 days + * Constraint: Task 0 and Task 1 can start immediately in parallel. + * Constraint: Task 2 depends on Task 0 (Task 0 must finish before Task 2 starts). + */ + double durations[3] = {5.0, 10.0, 3.0}; + + c_CPM_t cpm; + c_err_t err = c_CPM_Init(&cpm, 3, durations, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + c_EdgeWeightedDigraph_t working_graph; + err = c_CPM_CreateWorkingGraph(&cpm, durations, &working_graph); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Establish dependency constraint: Task 0 must finish before Task 2 begins */ + err = c_CPM_AddDependency(&cpm, &working_graph, 0, 2); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Run calculations */ + err = c_CPM_Calculate(&cpm, &working_graph); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* + * Let's trace timelines: + * Path A (via Task 0 & 2): Duration = 5.0 (Task 0) + 3.0 (Task 2) = 8.0 days. + * Path B (via Task 1): Duration = 10.0 days. + * The total project duration is governed by the longest path (critical bottleneck) = 10.0 days. + */ + ASSERT_DOUBLE_EQ_MSG(10.0, c_CPM_GetProjectDuration(&cpm), "Project duration calculation failed"); + + /* Verify Early Start milestones */ + ASSERT_DOUBLE_EQ_MSG(0.0, c_CPM_GetEarlyStart(&cpm, 0), "Task 0 should start at day 0"); + ASSERT_DOUBLE_EQ_MSG(0.0, c_CPM_GetEarlyStart(&cpm, 1), "Task 1 should start at day 0"); + ASSERT_DOUBLE_EQ_MSG(5.0, c_CPM_GetEarlyStart(&cpm, 2), "Task 2 should start at day 5 after Task 0 finishes"); + + /* Reconstruct critical path edges sequence tokens */ + c_VertexIdList_t critical_path; + c_VertexIdList_Init(&critical_path, 0, 0); + + err = c_CPM_GetCriticalPath(&cpm, &critical_path); + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_TRUE(c_VertexIdList_GetSize(&critical_path) > 0); + + c_VertexIdList_Destroy(&critical_path); + c_CPM_Destroy(&cpm); + c_EdgeWeightedDigraph_Destroy(&working_graph); +} + +int main(void) { + TEST_START(CPM_ProjectManagement_Suite); + RUN_TEST(test_critical_path_method_scheduling); + TEST_REPORT(); + RETURN_TEST_STATUS; +} diff --git a/Graph/c_Cycle.c b/Graph/c_Cycle.c new file mode 100644 index 0000000..7a39e9f --- /dev/null +++ b/Graph/c_Cycle.c @@ -0,0 +1,112 @@ +#include + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +// Internal deep structural trace walker looking for loops +static void c_Cycle_DFS_Internal(c_Cycle_t* self, const c_Graph_t* G, c_VertexId_t v, c_VertexId_t u) { + self->marked[v] = C_TRUE; + + c_AdjList_t* list = c_Graph_GetAdjList((c_Graph_t*)G, v); + if (!list) return; + + // Cache-friendly sequential sweep over flat neighbor array blocks + for (c_size_t i = 0; i < list->size; i++) { + const c_VertexId_t w = (c_VertexId_t)list->array[i]; + + // Short-circuit search operations if a cycle has already been populated + if (self->cycle.size > 0) return; + + if (!self->marked[w]) { + self->edge_to[w] = v; + c_Cycle_DFS_Internal(self, G, w, v); + } + // Undirected graph cycle criteria: w is visited AND w is not the direct parent u + else if (w != u) { + self->has_cycle = C_TRUE; + + // Reconstruct the cycle path back through the path logs + c_VertexIdList_t temp_stack; + if (c_VertexIdList_Init(&temp_stack, 8, &self->allocator) != C_SUCCESS) return; + + c_VertexId_t x = v; + while (x != w && x != G->V) { + c_VertexIdList_Append(&temp_stack, (c_uint_t)x); + x = self->edge_to[x]; + } + c_VertexIdList_Append(&temp_stack, (c_uint_t)w); + c_VertexIdList_Append(&temp_stack, (c_uint_t)v); // Close cycle track frame + + // Invert elements to maintain chronological loop sequence (w -> ... -> v -> w) + for (c_size_t j = temp_stack.size; j > 0; j--) { + c_VertexId_t val; + c_VertexIdList_Get(&temp_stack, j - 1, &val); + c_VertexIdList_Append(&self->cycle, val); + } + + c_VertexIdList_Destroy(&temp_stack); + return; + } + } +} + +c_err_t c_Cycle_Init(c_Cycle_t* self, const c_Graph_t* G, c_Allocator_t* allocator) { + if (!self || !G ) return C_ERR_PARAM; + + self->allocator = allocator?*allocator:c_DefaultAllocator; + self->has_cycle = C_FALSE; + self->V = G->V; + self->marked = NULL; + self->edge_to = NULL; + + if (c_VertexIdList_Init(&self->cycle, 0, &self->allocator) != C_SUCCESS) { + return C_ERR_NOMEM; + } + + if (G->V == 0) return C_SUCCESS; + + self->marked = (c_bool_t*)c_Allocator_Alloc(&self->allocator, G->V * sizeof(c_bool_t)); + self->edge_to = (c_size_t*)c_Allocator_Alloc(&self->allocator, G->V * sizeof(c_size_t)); + + if (!self->marked || !self->edge_to) { + c_Cycle_Destroy(self); + return C_ERR_NOMEM; + } + + memset(self->marked, 0, G->V * sizeof(c_bool_t)); + for (c_size_t i = 0; i < G->V; i++) self->edge_to[i] = G->V; // Sentinel setup + + // Multi-component partition loop scans + for (c_VertexId_t v = 0; v < G->V; v++) { + if (!self->marked[v]) { + c_Cycle_DFS_Internal(self, G, v, G->V); // Pass sentinel as initial parent + if (self->has_cycle) break; + } + } + + return C_SUCCESS; +} + +void c_Cycle_Destroy(c_Cycle_t* self) { + if (!self) return; + + if (self->marked) c_Allocator_Free(&self->allocator, self->marked); + if (self->edge_to) c_Allocator_Free(&self->allocator, self->edge_to); + + c_VertexIdList_Destroy(&self->cycle); + + self->marked = NULL; + self->edge_to = NULL; + self->has_cycle = C_FALSE; + self->V = 0; +} + +c_bool_t c_Cycle_HasCycle(const c_Cycle_t* self) { + return self ? self->has_cycle : C_FALSE; +} + +const c_VertexIdList_t* c_Cycle_Path(const c_Cycle_t* self) { + return self ? &self->cycle : NULL; +} + diff --git a/Graph/c_Cycle.h b/Graph/c_Cycle.h new file mode 100644 index 0000000..2081a57 --- /dev/null +++ b/Graph/c_Cycle.h @@ -0,0 +1,53 @@ +#ifndef INCLUDED_C_CYCLE_H +#define INCLUDED_C_CYCLE_H + +#ifndef INCLUDED_C_GRAPH_H +#include +#endif /*INCLUDED_C_GRAPH_H*/ + + +#ifndef INCLUDED_C_VERTEXIDLIST_H +#include +#endif /*INCLUDED_C_VERTEXIDLIST_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_bool_t* marked; // marked[v] = has vertex v been visited? + c_size_t* edge_to; // edge_to[v] = previous vertex on DFS path to v + c_VertexIdList_t cycle; // Stores the path sequence of the detected cycle + c_bool_t has_cycle; // Global structural cycle status flag + c_size_t V; // Cached local vertex dimension boundary + c_Allocator_t allocator; // Memory allocator copy +} c_Cycle_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Computes whether the undirected graph contains a cycle. + * @param self Pointer to the uninitialized cycle tracking structure. + * @param G Pointer to the constant target graph object to verify. + * @param allocator Memory allocator instance pointer to deploy. + * @return C_SUCCESS on success, or an error status code on allocation failure. + */ +c_err_t c_Cycle_Init(c_Cycle_t* self, const c_Graph_t* G, c_Allocator_t* allocator); + +/** + * @brief Drops all internal allocation states within the cycle instance safely. + */ +void c_Cycle_Destroy(c_Cycle_t* self); + +/** + * @brief Does the graph contain at least one cycle? + */ +c_bool_t c_Cycle_HasCycle(const c_Cycle_t* self); + +/** + * @brief Returns the vertex sequence forming the cycle path, or empty if acyclic. + */ +const c_VertexIdList_t* c_Cycle_Path(const c_Cycle_t* self); + +#endif /*INCLUDED_C_CYCLE_H*/ diff --git a/Graph/c_Cycle.t.c b/Graph/c_Cycle.t.c new file mode 100644 index 0000000..ca5c246 --- /dev/null +++ b/Graph/c_Cycle.t.c @@ -0,0 +1,58 @@ +#include "c_Cycle.h" +#include "c_Test.h" +#include +#include + +TEST_CASE(test_c_cycle_detection) { + // Test Scenario A: Acyclic Graph (Tree structure: 0-1, 1-2, 1-3) + c_Graph_t g_tree; + c_Graph_Init(&g_tree, 4, 0); + c_Graph_AddEdge(&g_tree, 0, 1); + c_Graph_AddEdge(&g_tree, 1, 2); + c_Graph_AddEdge(&g_tree, 1, 3); + + c_Cycle_t finder1; + c_err_t err = c_Cycle_Init(&finder1, &g_tree, 0); + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_FALSE(c_Cycle_HasCycle(&finder1)); // Tree has no cycle + ASSERT_LL_EQ(0, c_Cycle_Path(&finder1)->size); + + // Test Scenario B: Cyclic Graph (Triangle network: 0-1, 1-2, 2-0) + c_Graph_t g_cyclic; + c_Graph_Init(&g_cyclic, 3, 0); + c_Graph_AddEdge(&g_cyclic, 0, 1); + c_Graph_AddEdge(&g_cyclic, 1, 2); + c_Graph_AddEdge(&g_cyclic, 2, 0); + + c_Cycle_t finder2; + err = c_Cycle_Init(&finder2, &g_cyclic, 0); + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_TRUE(c_Cycle_HasCycle(&finder2)); // Must flag true + + // Verify properties of the closed cycle path trail + const c_VertexIdList_t* path = c_Cycle_Path(&finder2); + ASSERT_PTR_NOT_NULL(path); + ASSERT_TRUE(path->size == 4); // Closed triangle sequence holds 4 indices: e.g. 0 -> 1 -> 2 -> 0 + + c_uint_t start_node, end_node; + c_VertexIdList_Get((c_VertexIdList_t*)path, 0, &start_node); + c_VertexIdList_Get((c_VertexIdList_t*)path, path->size - 1, &end_node); + ASSERT_LL_EQ(start_node, end_node); // Path must wrap back to start element + + c_Cycle_Destroy(&finder1); + c_Cycle_Destroy(&finder2); + c_Graph_Destroy(&g_tree); + c_Graph_Destroy(&g_cyclic); +} + + +int main(int argc, char** argv){ + TEST_START(Component Tests); + + // Execution list configurations + RUN_TEST(test_c_cycle_detection); + + TEST_REPORT(); + + RETURN_TEST_STATUS; +} diff --git a/Graph/c_DepthFirstDirectedPaths.c b/Graph/c_DepthFirstDirectedPaths.c new file mode 100644 index 0000000..b19482e --- /dev/null +++ b/Graph/c_DepthFirstDirectedPaths.c @@ -0,0 +1,85 @@ +#include + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +/* Private recursive engine helper */ +static void c_DepthFirstDirectedPaths_Traverse(c_DepthFirstDirectedPaths_t* self, const c_Digraph_t* graph, c_size_t v) { + self->marked[v] = C_TRUE; + + c_AdjList_t* adj = &graph->adj_list[v]; + c_size_t size = (c_size_t)c_UIntArray_GetSize(adj); + + for (c_size_t i = 0; i < size; ++i) { + c_uint_t target_value = 0; + /* Using the safe signature to extract the neighbor vertex index */ + c_err_t err = c_UIntArray_Get(adj, i, &target_value); + + if (err == C_SUCCESS) { + c_size_t w = (c_size_t)target_value; + if (!self->marked[w]) { + self->edge_to[w] = v; /* Record parent routing step link */ + c_DepthFirstDirectedPaths_Traverse(self, graph, w); + } + } + } +} + +c_err_t c_DepthFirstDirectedPaths_Init(c_DepthFirstDirectedPaths_t* self, const c_Digraph_t* graph, c_size_t s, c_Allocator_t* allocator) { + if (!self || !graph || s >= graph->V) return C_ERR_PARAM; + + self->allocator = allocator ? *allocator : c_DefaultAllocator; + self->s = s; + + self->marked = (c_bool_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_bool_t)); + if (!self->marked) return C_ERR_NOMEM; + + self->edge_to = (c_size_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_size_t)); + if (!self->edge_to) { + c_Allocator_Free(&self->allocator, self->marked); + self->marked = NULL; + return C_ERR_NOMEM; + } + + c_DepthFirstDirectedPaths_Traverse(self, graph, s); + return C_SUCCESS; +} + +void c_DepthFirstDirectedPaths_Destroy(c_DepthFirstDirectedPaths_t* self) { + if (!self) return; + + if (self->marked) { + c_Allocator_Free(&self->allocator, self->marked); + self->marked = NULL; + } + if (self->edge_to) { + c_Allocator_Free(&self->allocator, self->edge_to); + self->edge_to = NULL; + } + self->s = 0; +} + +c_err_t c_DepthFirstDirectedPaths_PathTo(c_DepthFirstDirectedPaths_t* self, c_size_t v, c_size_t total_V, c_VertexIdList_t* out_path) { + if (!self || !out_path || v >= total_V) return C_ERR_PARAM; + if (!c_DepthFirstDirectedPaths_HasPathTo(self, v, total_V)) return C_ERR_FAIL; + + c_size_t* reverse_stack = (c_size_t*)c_Allocator_Calloc(&self->allocator, total_V, sizeof(c_size_t)); + if (!reverse_stack) return C_ERR_NOMEM; + + c_size_t stack_size = 0; + for (c_size_t x = v; x != self->s; x = self->edge_to[x]) { + reverse_stack[stack_size++] = x; + } + reverse_stack[stack_size++] = self->s; + + c_err_t err = C_SUCCESS; + while (stack_size > 0) { + c_size_t current_vertex = reverse_stack[--stack_size]; + err = c_VertexIdList_Append(out_path, (c_uint_t)current_vertex); + if (err != C_SUCCESS) break; + } + + c_Allocator_Free(&self->allocator, reverse_stack); + return err; +} diff --git a/Graph/c_DepthFirstDirectedPaths.h b/Graph/c_DepthFirstDirectedPaths.h new file mode 100644 index 0000000..324da41 --- /dev/null +++ b/Graph/c_DepthFirstDirectedPaths.h @@ -0,0 +1,53 @@ +#ifndef INCLUDED_C_DEPTHFIRSTDIRECTEDPATHS_H +#define INCLUDED_C_DEPTHFIRSTDIRECTEDPATHS_H + +#ifndef INCLUDED_C_DIGRAPH_H +#include +#endif /*INCLUDED_C_DIGRAPH_H*/ + +#ifndef INCLUDED_C_VERTEXIDLIST_H +#include +#endif /*INCLUDED_C_VERTEXIDLIST_H*/ + + + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_bool_t* marked; /* Visited tracking array (size of graph->V) */ + c_size_t* edge_to; /* edge_to[w] = last edge on path from s to w */ + c_size_t s; /* Source vertex */ + c_Allocator_t allocator; /* Copied allocator from the graph for isolation */ +} c_DepthFirstDirectedPaths_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Computes a directed path tree from a single source vertex 's' using DFS + */ +c_err_t c_DepthFirstDirectedPaths_Init(c_DepthFirstDirectedPaths_t* self, const c_Digraph_t* graph, c_size_t s, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers + */ +void c_DepthFirstDirectedPaths_Destroy(c_DepthFirstDirectedPaths_t* self); + +/** + * @brief Is there a directed path from the source 's' to vertex 'v'? + */ +C_STATIC_FORCE_INLINE c_bool_t c_DepthFirstDirectedPaths_HasPathTo(c_DepthFirstDirectedPaths_t* self, c_size_t v, c_size_t total_V) { + if (!self || !self->marked || v >= total_V) return C_FALSE; + return self->marked[v]; +} + +/** + * @brief Reconstructs the exact path from source 's' to vertex 'v' and appends it to out_path + * @param out_path An initialized c_VertexIdList_t container to collect the sequence + */ +c_err_t c_DepthFirstDirectedPaths_PathTo(c_DepthFirstDirectedPaths_t* self, c_size_t v, c_size_t total_V, c_VertexIdList_t* out_path); + + +#endif /*INCLUDED_C_DEPTHFIRSTDIRECTEDPATHS_H*/ diff --git a/Graph/c_DepthFirstDirectedPaths.t.c b/Graph/c_DepthFirstDirectedPaths.t.c new file mode 100644 index 0000000..de7a927 --- /dev/null +++ b/Graph/c_DepthFirstDirectedPaths.t.c @@ -0,0 +1,88 @@ +#include "c_DepthFirstDirectedPaths.h" +#include "c_Test.h" +#include +#include + + +TEST_CASE(test_depth_first_directed_paths_complete) { + c_Digraph_t g; + + /* 1. 初始化一个拥有 6 个顶点的有向图,使用默认分配器 */ + c_err_t err = c_Digraph_Init(&g, 6, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* + * 2. 构建测试图拓扑结构: + * 0 -> 1 -> 2 -> 5 + * 0 -> 3 -> 4 + * 5 是孤立终点,4 是另一个路径的终点 + */ + c_Digraph_AddEdge(&g, 0, 1); + c_Digraph_AddEdge(&g, 1, 2); + c_Digraph_AddEdge(&g, 2, 5); + c_Digraph_AddEdge(&g, 0, 3); + c_Digraph_AddEdge(&g, 3, 4); + + /* 3. 初始化 DFS 路径搜索引擎(起点设为 0,并显式传入图的分配器) */ + c_DepthFirstDirectedPaths_t paths; + err = c_DepthFirstDirectedPaths_Init(&paths, &g, 0, &g.allocator); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* 4. 测试连通性判定 (HasPathTo) */ + ASSERT_TRUE(c_DepthFirstDirectedPaths_HasPathTo(&paths, 5, g.V)); + ASSERT_TRUE(c_DepthFirstDirectedPaths_HasPathTo(&paths, 4, g.V)); + + /* 假设存在一个没有被任何边指向的独立节点(比如把图扩大或测试不连通) */ + /* 由于我们只初始化了 6 个节点,若查询范围外的节点或已知断开的路径: */ + // 假设不存在 0 到某个未连接节点的情况,这里测试起点本身 + ASSERT_TRUE(c_DepthFirstDirectedPaths_HasPathTo(&paths, 0, g.V)); + + /* 5. 提取并验证从 0 到 5 的完整路径 */ + c_VertexIdList_t path_to_5; + c_VertexIdList_Init(&path_to_5, 0, NULL); /* 这里映射的是 c_UIntArray_Init */ + + err = c_DepthFirstDirectedPaths_PathTo(&paths, 5, g.V, &path_to_5); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* 预期路径长度为 4: [0, 1, 2, 5] */ + c_size_t path_size = (c_size_t)c_VertexIdList_GetSize(&path_to_5); + ASSERT_LL_EQ(4, path_size); + + /* 使用最新的安全 c_VertexIdList_Get 接口验证路径节点顺序 */ + c_uint_t val = 0; + + err = c_VertexIdList_Get(&path_to_5, 0, &val); + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_LL_EQ(0, val); + + err = c_VertexIdList_Get(&path_to_5, 1, &val); + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_LL_EQ(1, val); + + err = c_VertexIdList_Get(&path_to_5, 2, &val); + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_LL_EQ(2, val); + + err = c_VertexIdList_Get(&path_to_5, 3, &val); + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_LL_EQ(5, val); + + /* 6. 清理所有分配的资源 */ + c_VertexIdList_Destroy(&path_to_5); + c_DepthFirstDirectedPaths_Destroy(&paths); + c_Digraph_Destroy(&g); +} + +int main(void) { + /* 启动测试集 */ + TEST_START(DepthFirstDirectedPaths_Tests); + + /* 运行具体的测试用例 */ + RUN_TEST(test_depth_first_directed_paths_complete); + + /* 打印测试汇总报告 */ + TEST_REPORT(); + + /* 返回测试状态代码码(0 代表全部通过,1 代表有失败) */ + RETURN_TEST_STATUS; +} diff --git a/Graph/c_DepthFirstOrder.c b/Graph/c_DepthFirstOrder.c new file mode 100644 index 0000000..d31dc78 --- /dev/null +++ b/Graph/c_DepthFirstOrder.c @@ -0,0 +1,96 @@ +#include + + +/* Private recursive engine helper */ +static void c_DepthFirstOrder_DFS(c_DepthFirstOrder_t* self, c_Digraph_t* graph, c_size_t v) { + self->marked[v] = C_TRUE; + + /* A. Pre-order: append vertex before visiting neighbors */ + c_VertexIdList_Append(&self->pre_order, (c_uint_t)v); + + c_AdjList_t* adj = &graph->adj_list[v]; + c_size_t size = (c_size_t)c_AdjList_GetSize(adj); + + for (c_size_t i = 0; i < size; ++i) { + c_uint_t target_value = 0; + c_err_t err = c_AdjList_Get(adj, i, &target_value); + + if (err == C_SUCCESS) { + c_size_t w = (c_size_t)target_value; + if (!self->marked[w]) { + c_DepthFirstOrder_DFS(self, graph, w); + } + } + } + + /* B. Post-order: append vertex after visiting all its outgoing branches */ + c_VertexIdList_Append(&self->post_order, (c_uint_t)v); +} + +c_err_t c_DepthFirstOrder_Init(c_DepthFirstOrder_t* self, c_Digraph_t* graph, c_Allocator_t* allocator) { + if (!self || !graph) return C_ERR_PARAM; + + self->allocator = allocator ? *allocator : c_DefaultAllocator; + self->marked = (c_bool_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_bool_t)); + if (!self->marked) return C_ERR_NOMEM; + + c_VertexIdList_Init(&self->pre_order, 0, allocator); + c_VertexIdList_Init(&self->post_order, 0, allocator); + c_VertexIdList_Init(&self->reverse_post, 0, allocator); + + /* Run DFS across all vertices to guarantee full coverage of disconnected graph structures */ + for (c_size_t v = 0; v < graph->V; ++v) { + if (!self->marked[v]) { + c_DepthFirstOrder_DFS(self, graph, v); + } + } + + /* C. Build Reverse Post-Order by reading the post-order sequence backwards */ + c_size_t post_size = (c_size_t)c_VertexIdList_GetSize(&self->post_order); + for (c_size_t i = post_size; i > 0; --i) { + c_uint_t val = 0; + c_err_t err = c_VertexIdList_Get(&self->post_order, i - 1, &val); + if (err == C_SUCCESS) { + c_VertexIdList_Append(&self->reverse_post, val); + } + } + + return C_SUCCESS; +} + +void c_DepthFirstOrder_Destroy(c_DepthFirstOrder_t* self) { + if (!self) return; + if (self->marked) { + c_Allocator_Free(&self->allocator, self->marked); + self->marked = NULL; + } + c_VertexIdList_Destroy(&self->pre_order); + c_VertexIdList_Destroy(&self->post_order); + c_VertexIdList_Destroy(&self->reverse_post); +} + +static c_err_t CopyVertexIdList(c_VertexIdList_t* src, c_VertexIdList_t* dest) { + if (!src || !dest) return C_ERR_PARAM; + c_size_t size = (c_size_t)c_VertexIdList_GetSize((c_VertexIdList_t*)src); + for (c_size_t i = 0; i < size; ++i) { + c_uint_t val = 0; + c_err_t err = c_VertexIdList_Get((c_VertexIdList_t*)src, i, &val); + if (err == C_SUCCESS) { + c_err_t app_err = c_VertexIdList_Append(dest, val); + if (app_err != C_SUCCESS) return app_err; + } + } + return C_SUCCESS; +} + +c_err_t c_DepthFirstOrder_GetPre(c_DepthFirstOrder_t* self, c_VertexIdList_t* out_list) { + return CopyVertexIdList(&self->pre_order, out_list); +} + +c_err_t c_DepthFirstOrder_GetPost(c_DepthFirstOrder_t* self, c_VertexIdList_t* out_list) { + return CopyVertexIdList(&self->post_order, out_list); +} + +c_err_t c_DepthFirstOrder_GetReversePost(c_DepthFirstOrder_t* self, c_VertexIdList_t* out_list) { + return CopyVertexIdList(&self->reverse_post, out_list); +} diff --git a/Graph/c_DepthFirstOrder.h b/Graph/c_DepthFirstOrder.h new file mode 100644 index 0000000..3491a93 --- /dev/null +++ b/Graph/c_DepthFirstOrder.h @@ -0,0 +1,55 @@ +#ifndef INCLUDED_C_DEPTHFIRSTORDER_H +#define INCLUDED_C_DEPTHFIRSTORDER_H + +#ifndef INCLUDED_C_DIGRAPH_H +#include +#endif /*INCLUDED_C_DIGRAPH_H*/ + + +#ifndef INCLUDED_C_VERTEXIDLIST_H +#include +#endif /*INCLUDED_C_VERTEXIDLIST_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_bool_t* marked; /* Visited tracking array (size of graph->V) */ + c_VertexIdList_t pre_order; /* Vertices in pre-order sequence */ + c_VertexIdList_t post_order; /* Vertices in post-order sequence */ + c_VertexIdList_t reverse_post; /* Vertices in reverse post-order sequence */ + c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ +} c_DepthFirstOrder_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Computes pre-order, post-order, and reverse post-order traversals for a digraph. + * @param allocator Explicit allocator context used to allocate internal arrays and lists + */ +c_err_t c_DepthFirstOrder_Init(c_DepthFirstOrder_t* self, c_Digraph_t* graph, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers + */ +void c_DepthFirstOrder_Destroy(c_DepthFirstOrder_t* self); + +/** + * @brief Gets a copy of the pre-order vertex list. + */ +c_err_t c_DepthFirstOrder_GetPre(c_DepthFirstOrder_t* self, c_VertexIdList_t* out_list); + +/** + * @brief Gets a copy of the post-order vertex list. + */ +c_err_t c_DepthFirstOrder_GetPost(c_DepthFirstOrder_t* self, c_VertexIdList_t* out_list); + +/** + * @brief Gets a copy of the reverse post-order vertex list (Topological order candidate). + */ +c_err_t c_DepthFirstOrder_GetReversePost(c_DepthFirstOrder_t* self, c_VertexIdList_t* out_list); + + +#endif /*INCLUDED_C_DEPTHFIRSTORDER_H*/ diff --git a/Graph/c_DepthFirstOrder.t.c b/Graph/c_DepthFirstOrder.t.c new file mode 100644 index 0000000..faaaf8b --- /dev/null +++ b/Graph/c_DepthFirstOrder.t.c @@ -0,0 +1,63 @@ +#include "c_Test.h" +#include "c_Digraph.h" +#include "c_DepthFirstOrder.h" +#include "c_VertexIdList.h" + +TEST_CASE(test_depth_first_order_streams) { + c_Digraph_t g; + c_err_t err = c_Digraph_Init(&g, 3, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Construct a simple DAG structure: 0 -> 1 -> 2 */ + c_Digraph_AddEdge(&g, 0, 1); + c_Digraph_AddEdge(&g, 1, 2); + + c_DepthFirstOrder_t order; + err = c_DepthFirstOrder_Init(&order, &g, 0); + ASSERT_INT_EQ(C_SUCCESS, err); + + c_VertexIdList_t pre, post, rev_post; + c_VertexIdList_Init(&pre,0, 0); + c_VertexIdList_Init(&post,0, 0); + c_VertexIdList_Init(&rev_post,0, 0); + + ASSERT_INT_EQ(C_SUCCESS, c_DepthFirstOrder_GetPre(&order, &pre)); + ASSERT_INT_EQ(C_SUCCESS, c_DepthFirstOrder_GetPost(&order, &post)); + ASSERT_INT_EQ(C_SUCCESS, c_DepthFirstOrder_GetReversePost(&order, &rev_post)); + + /* Verify Pre-order (Should be 0, 1, 2) */ + c_uint_t val = 0; + c_VertexIdList_Get(&pre, 0, &val); ASSERT_LL_EQ(0, val); + c_VertexIdList_Get(&pre, 1, &val); ASSERT_LL_EQ(1, val); + c_VertexIdList_Get(&pre, 2, &val); ASSERT_LL_EQ(2, val); + + /* Verify Post-order (Should be 2, 1, 0) */ + c_VertexIdList_Get(&post, 0, &val); ASSERT_LL_EQ(2, val); + c_VertexIdList_Get(&post, 1, &val); ASSERT_LL_EQ(1, val); + c_VertexIdList_Get(&post, 2, &val); ASSERT_LL_EQ(0, val); + + /* Verify Reverse Post-order / Topological Candidate (Should be 0, 1, 2) */ + c_VertexIdList_Get(&rev_post, 0, &val); ASSERT_LL_EQ(0, val); + c_VertexIdList_Get(&rev_post, 1, &val); ASSERT_LL_EQ(1, val); + c_VertexIdList_Get(&rev_post, 2, &val); ASSERT_LL_EQ(2, val); + + c_VertexIdList_Destroy(&pre); + c_VertexIdList_Destroy(&post); + c_VertexIdList_Destroy(&rev_post); + c_DepthFirstOrder_Destroy(&order); + c_Digraph_Destroy(&g); +} + +int main(void) { + /* 启动测试集 */ + TEST_START(Tests); + + /* 运行具体的测试用例 */ + RUN_TEST(test_depth_first_order_streams); + + /* 打印测试汇总报告 */ + TEST_REPORT(); + + /* 返回测试状态代码码(0 代表全部通过,1 代表有失败) */ + RETURN_TEST_STATUS; +} \ No newline at end of file diff --git a/Graph/c_DepthFirstSearch.c b/Graph/c_DepthFirstSearch.c new file mode 100644 index 0000000..494cbfc --- /dev/null +++ b/Graph/c_DepthFirstSearch.c @@ -0,0 +1,127 @@ +#include + + +// Internal deep structural trace walker running down stack frames +static void c_DFS_Internal(c_DepthFirstSearch_t* self, const c_Graph_t* G, c_size_t v) { + self->marked[v] = C_TRUE; + self->count++; + + const c_AdjList_t* list = c_Graph_GetAdjList((c_Graph_t*)G, v); + if (!list) return; + + // High cache-locality contiguous array iteration scan + for (c_size_t i = 0; i < list->size; i++) { + c_uint_t w = list->array[i]; + if (!self->marked[w]) { + self->edge_to[w] = v; // Trace trace track back identity path + c_DFS_Internal(self, G, w); + } + } +} + +c_err_t c_DepthFirstSearch_Init(c_DepthFirstSearch_t* self, const c_Graph_t* G, c_size_t s, c_Allocator_t* allocator) { + if (!self || !G || s >= G->V) { + return C_ERR_PARAM; + } + + self->allocator = allocator?*allocator:c_DefaultAllocator; + self->source = s; + self->count = 0; + self->marked = NULL; + self->edge_to = NULL; + + if (G->V == 0) { + return C_SUCCESS; + } + + // Allocate continuous block space arrays + self->marked = (c_bool_t*)c_Allocator_Alloc(&self->allocator, G->V * sizeof(c_bool_t)); + self->edge_to = (c_size_t*)c_Allocator_Alloc(&self->allocator, G->V * sizeof(c_size_t)); + + if (!self->marked || !self->edge_to) { + c_DepthFirstSearch_Destroy(self); + return C_ERR_NOMEM; + } + + // Zero out buffers initialization + memset(self->marked, 0, G->V * sizeof(c_bool_t)); + for (c_size_t i = 0; i < G->V; i++) { + self->edge_to[i] = G->V; // Use G->V dimension size integer as path sentinel + } + + // Run structural execution tracing + c_DFS_Internal(self, G, s); + + return C_SUCCESS; +} + +void c_DepthFirstSearch_Destroy(c_DepthFirstSearch_t* self) { + if (!self) return; + + if (self->marked) c_Allocator_Free(&self->allocator, self->marked); + if (self->edge_to) c_Allocator_Free(&self->allocator, self->edge_to); + + self->marked = NULL; + self->edge_to = NULL; + self->count = 0; + self->source = 0; +} + +c_bool_t c_DepthFirstSearch_HasPathTo(const c_DepthFirstSearch_t* self, c_size_t v) { + if (!self || !self->marked) { + return C_FALSE; + } + return self->marked[v]; +} + +c_size_t c_DepthFirstSearch_Count(const c_DepthFirstSearch_t* self) { + if (!self) return 0; + return self->count; +} + +c_err_t c_DepthFirstSearch_PathTo(const c_DepthFirstSearch_t* self, c_size_t v, c_VertexIdList_t* path) { + if (!self || !path) { + return C_ERR_PARAM; + } + + // Step 1: Invariant validation check - verify if a real route mapping path exists + if (!c_DepthFirstSearch_HasPathTo(self, v)) { + return C_ERR_FAIL; + } + + // Ensure the output container list starts completely empty + path->size = 0; + + // Step 2: Backtrack from destination 'v' to root 'source' using edge_to routes + c_size_t current = v; + while (current != self->source) { + c_err_t err = c_VertexIdList_Append(path, (c_uint_t)current); + if (err != C_SUCCESS) { + path->size = 0; // Reset allocation sequence tracks on failure + return err; + } + current = self->edge_to[current]; + } + + // Append the starting source root identity node to close the tracking frame + c_err_t err = c_VertexIdList_Append(path, (c_uint_t)self->source); + if (err != C_SUCCESS) { + path->size = 0; + return err; + } + + // Step 3: Mirror inversion optimization step. + // Because backtracking collects indices in reverse order (v -> source), + // we reverse the array to restore a chronological (source -> v) pipeline format. + c_size_t left = 0; + c_size_t right = path->size - 1; + while (left < right) { + c_uint_t temp = path->array[left]; + path->array[left] = path->array[right]; + path->array[right] = temp; + left++; + right--; + } + + return C_SUCCESS; +} diff --git a/Graph/c_DepthFirstSearch.h b/Graph/c_DepthFirstSearch.h new file mode 100644 index 0000000..53d5fbe --- /dev/null +++ b/Graph/c_DepthFirstSearch.h @@ -0,0 +1,62 @@ +#ifndef INCLUDED_C_DEPTHFIRSTSEARCH_H +#define INCLUDED_C_DEPTHFIRSTSEARCH_H + +#ifndef INCLUDED_C_GRAPH_H +#include +#endif /*INCLUDED_C_GRAPH_H*/ + +#ifndef INCLUDED_C_VERTEXIDLIST_H +#include +#endif /*INCLUDED_C_VERTEXIDLIST_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_bool_t* marked; // marked[v] = true if v is reachable from source + c_size_t* edge_to; // edge_to[v] = last vertex on path from source to v + c_size_t count; // Total number of vertices connected to source + c_size_t source; // Source root vertex index + c_Allocator_t allocator; // Memory allocator reference instance copy +} c_DepthFirstSearch_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Computes the vertices connected to a source vertex in a graph. + * @param self Pointer to the uninitialized search state structure. + * @param G Pointer to the constant target graph object to analyze. + * @param s The source root vertex index. + * @param allocator Memory allocator instance pointer to deploy. + * @return C_SUCCESS on success, or an error status code on allocation failure. + */ +c_err_t c_DepthFirstSearch_Init(c_DepthFirstSearch_t* self, const c_Graph_t* G, c_size_t s, c_Allocator_t* allocator); + +/** + * @brief Drops all internal allocation states within the search instance safely. + */ +void c_DepthFirstSearch_Destroy(c_DepthFirstSearch_t* self); + +/** + * @brief Is there a path between the source vertex and vertex v? + */ +c_bool_t c_DepthFirstSearch_HasPathTo(const c_DepthFirstSearch_t* self, c_size_t v); + +/** + * @brief Returns the total number of vertices structurally connected to the source vertex. + */ +c_size_t c_DepthFirstSearch_Count(const c_DepthFirstSearch_t* self); + + +/** + * @brief Recovers a path track from the source vertex to target v. + * @param self Pointer to the computed query state instance. + * @param v The target destination vertex identifier. + * @param path Pointer to an allocated list structure to hold the path trail. + * @return C_SUCCESS on successful extraction, C_ERR_FAIL if no path exists, or parameter error code. + */ +c_err_t c_DepthFirstSearch_PathTo(const c_DepthFirstSearch_t* self, c_size_t v, c_VertexIdList_t* path); + +#endif /*INCLUDED_C_DEPTHFIRSTSEARCH_H*/ diff --git a/Graph/c_DepthFirstSearch.t.c b/Graph/c_DepthFirstSearch.t.c new file mode 100644 index 0000000..7c5bdb0 --- /dev/null +++ b/Graph/c_DepthFirstSearch.t.c @@ -0,0 +1,96 @@ +#include "c_DepthFirstSearch.h" +#include "c_Test.h" +#include +#include + +TEST_CASE(test_depth_first_search_object) { + c_Graph_t graph; + c_Graph_Init(&graph, 6, 0); + + // Connected Cluster 1 (Triangle component + stem): 0-1, 1-2, 2-0, 2-3 + c_Graph_AddEdge(&graph, 0, 1); + c_Graph_AddEdge(&graph, 1, 2); + c_Graph_AddEdge(&graph, 2, 0); + c_Graph_AddEdge(&graph, 2, 3); + + // Connected Cluster 2 (Isolated pairing component): 4-5 + c_Graph_AddEdge(&graph, 4, 5); + + // Initialize the Search framework tracking from Source root 0 + c_DepthFirstSearch_t dfs; + c_err_t err = c_DepthFirstSearch_Init(&dfs, &graph, 0, 0); + + ASSERT_INT_EQ(C_SUCCESS, err); + + // Connected metrics checking rules validation: Source 0 can discover 0, 1, 2, 3 -> count is 4 + ASSERT_LL_EQ(4, c_DepthFirstSearch_Count(&dfs)); + + // Verify component connectivity mappings + ASSERT_TRUE(c_DepthFirstSearch_HasPathTo(&dfs, 3)); + ASSERT_TRUE(c_DepthFirstSearch_HasPathTo(&dfs, 1)); + + // Nodes 4 and 5 are completely unreachable from Source 0 + ASSERT_FALSE(c_DepthFirstSearch_HasPathTo(&dfs, 4)); + ASSERT_FALSE(c_DepthFirstSearch_HasPathTo(&dfs, 5)); + + // Cleanup resources + c_DepthFirstSearch_Destroy(&dfs); + c_Graph_Destroy(&graph); +} + +TEST_CASE(test_dfs_path_to_extraction) { + c_Graph_t graph; + c_Graph_Init(&graph, 5, 0); + + // Create a snake graph path network with a tail: 0-1, 1-2, 2-3, 4 (isolated) + c_Graph_AddEdge(&graph, 0, 1); + c_Graph_AddEdge(&graph, 1, 2); + c_Graph_AddEdge(&graph, 2, 3); + + // Initialize depth tracing calculation from root node zero (0) + c_DepthFirstSearch_t dfs; + c_DepthFirstSearch_Init(&dfs, &graph, 0, 0); + + // Instantiate our destination identifier list vector + c_VertexIdList_t route; + c_VertexIdList_Init(&route, 4, 0); + + // Test 1: Extract valid route trajectory tracking out to vertex 3 + c_err_t err = c_DepthFirstSearch_PathTo(&dfs, 3, &route); + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_LL_EQ(4, route.size); // Path length should be exactly 4 hops: [0, 1, 2, 3] + + // Verify correct forward sequence mapping layout elements + c_uint_t node; + c_VertexIdList_Get(&route, 0, &node); ASSERT_LL_EQ(0, node); + c_VertexIdList_Get(&route, 1, &node); ASSERT_LL_EQ(1, node); + c_VertexIdList_Get(&route, 2, &node); ASSERT_LL_EQ(2, node); + c_VertexIdList_Get(&route, 3, &node); ASSERT_LL_EQ(3, node); + + // Test 2: Unreachable destination error boundary isolation checks + c_VertexIdList_t bad_route; + c_VertexIdList_Init(&bad_route, 4, 0); + + c_err_t fail_err = c_DepthFirstSearch_PathTo(&dfs, 4, &bad_route); + ASSERT_INT_EQ_MSG(C_ERR_FAIL, fail_err, "Isolated un-routed paths must return failure status mapping"); + ASSERT_LL_EQ(0, bad_route.size); + + // Clean structural tracker allocations + c_VertexIdList_Destroy(&route); + c_VertexIdList_Destroy(&bad_route); + c_DepthFirstSearch_Destroy(&dfs); + c_Graph_Destroy(&graph); +} + + +int main(int argc, char** argv){ + TEST_START(Component Tests); + + // Execution list configurations + RUN_TEST(test_depth_first_search_object); + RUN_TEST(test_dfs_path_to_extraction); + + TEST_REPORT(); + + RETURN_TEST_STATUS; +} diff --git a/Graph/c_Digraph.c b/Graph/c_Digraph.c new file mode 100644 index 0000000..62afb07 --- /dev/null +++ b/Graph/c_Digraph.c @@ -0,0 +1,242 @@ +#include + +c_err_t c_Digraph_Init(c_Digraph_t* self, c_size_t V, c_Allocator_t* alloc) { + if (!self) return C_ERR_PARAM; + + /* 1. 复制或初始化分配器 */ + self->allocator = alloc ? *alloc : c_DefaultAllocator; + self->V = V; + self->E = 0; + self->adj_list = NULL; + self->indegree = NULL; + + if (V > 0) { + /* 2. 分配邻接表骨架内存 */ + self->adj_list = (c_AdjList_t*)c_Allocator_Calloc(&self->allocator, V, sizeof(c_AdjList_t)); + if (!self->adj_list) return C_ERR_NOMEM; + + /* 3. 分配入度计数数组内存 */ + self->indegree = (c_size_t*)c_Allocator_Calloc(&self->allocator, V, sizeof(c_size_t)); + if (!self->indegree) { + c_Allocator_Free(&self->allocator, self->adj_list); + self->adj_list = NULL; + return C_ERR_NOMEM; + } + + /* 4. 初始化每个顶点的邻接表 */ + for (c_size_t i = 0; i < V; ++i) { + c_AdjList_Init(&self->adj_list[i], 0, alloc); + } + } + + return C_SUCCESS; +} + +void c_Digraph_Destroy(c_Digraph_t* self) { + if (!self) return; + + if (self->adj_list) { + for (c_size_t i = 0; i < self->V; ++i) { + c_AdjList_Destroy(&self->adj_list[i]); + } + c_Allocator_Free(&self->allocator, self->adj_list); + self->adj_list = NULL; + } + + if (self->indegree) { + c_Allocator_Free(&self->allocator, self->indegree); + self->indegree = NULL; + } + + self->V = 0; + self->E = 0; +} + +c_err_t c_Digraph_AddEdge(c_Digraph_t* self, c_size_t from, c_size_t to) { + if (!self || from >= self->V || to >= self->V) { + return C_ERR_PARAM; + } + + /* 往 from 节点的邻接表末尾追加 to 节点 */ + c_err_t err = c_AdjList_Append(&self->adj_list[from], (c_uint_t)to); + if (err == C_SUCCESS) { + self->E++; + self->indegree[to]++; /* O(1) 同步更新入度计数 */ + } + return err; +} + +c_err_t c_Digraph_Resize(c_Digraph_t* self, c_size_t new_V) { + if (!self) return C_ERR_PARAM; + if (new_V == self->V) return C_SUCCESS; + + if (new_V == 0) { + c_Digraph_Destroy(self); + return C_SUCCESS; + } + + /* 1. 如果新尺寸变小,需先释放多余顶点的数组资源 */ + if (new_V < self->V) { + for (c_size_t i = new_V; i < self->V; ++i) { + /* 注意:如果被删除的顶点包含出边,可能会导致其他顶点的入度不准确 */ + /* 健壮的做法是在销毁前遍历这些出边,给对应目标的入度做减法 */ + c_AdjList_t* adj = &self->adj_list[i]; + c_size_t size = (c_size_t)c_AdjList_GetSize(adj); + for (c_size_t j = 0; j < size; ++j) { + c_size_t target; + if (c_AdjList_Get(adj, j, &target)!=C_ERR_OK) { + continue; + } + if (target < new_V) { + self->indegree[target]--; + self->E--; + } + } + c_AdjList_Destroy(&self->adj_list[i]); + } + } + + /* 2. 重新调整邻接表骨架内存空间 */ + c_AdjList_t* new_list = (c_AdjList_t*)c_Allocator_Realloc( + &self->allocator, self->adj_list, + self->V * sizeof(c_AdjList_t), new_V * sizeof(c_AdjList_t) + ); + if (!new_list) return C_ERR_NOMEM; + self->adj_list = new_list; + + /* 3. 重新调整入度数组内存空间 */ + c_size_t* new_indegree = (c_size_t*)c_Allocator_Realloc( + &self->allocator, self->indegree, + self->V * sizeof(c_size_t), new_V * sizeof(c_size_t) + ); + if (!new_indegree) return C_ERR_NOMEM; + self->indegree = new_indegree; + + /* 4. 如果新尺寸变大,初始化新增加顶点的邻接表与入度计数 */ + if (new_V > self->V) { + for (c_size_t i = self->V; i < new_V; ++i) { + memset(&self->adj_list[i], 0, sizeof(c_AdjList_t)); + c_AdjList_Init(&self->adj_list[i], 0, &self->allocator); + self->indegree[i] = 0; + } + } + + self->V = new_V; + return C_SUCCESS; +} + + +c_size_t c_Digraph_GetDegree(c_Digraph_t* self, c_size_t v, c_bool_t out_degree_only) { + if (!self || v >= self->V) return 0; + + /* 1. 出度 (Out-Degree):直接获取该顶点邻接表的元素数量 */ + if (out_degree_only) { + return (c_size_t)c_AdjList_GetSize(&self->adj_list[v]); + } + + /* 2. 入度 (In-Degree):需要遍历整个图,统计有多少条边指向 v */ + return self->indegree[v]; +} + +c_err_t c_Digraph_Reverse(c_Digraph_t* self, c_Digraph_t* out_reversed) { + if (!self || !out_reversed) return C_ERR_PARAM; + + c_err_t err = c_Digraph_Init(out_reversed, self->V, &self->allocator); + if (err != C_SUCCESS) return err; + + for (c_size_t u = 0; u < self->V; ++u) { + c_AdjList_t* adj = &self->adj_list[u]; + c_size_t size = (c_size_t)c_AdjList_GetSize(adj); + + for (c_size_t i = 0; i < size; ++i) { + c_size_t v; + if (c_AdjList_Get(adj, i, &v)!=C_ERR_OK) { + continue; + } + err = c_Digraph_AddEdge(out_reversed, v, u); + if (err != C_SUCCESS) { + c_Digraph_Destroy(out_reversed); + return err; + } + } + } + + return C_SUCCESS; +} + + +/* ================================================================================================================== */ +/* Unweighted Digraph Edge Query Primitive */ + +c_err_t c_Digraph_GetEdge(c_Digraph_t* self, c_size_t from, c_size_t to, c_size_t * edge_idx) { + if (!self ) return C_ERR_PARAM; + if (from >= self->V) return C_ERR_OUTOFBOUND; + + c_AdjList_t* adj = &self->adj_list[from]; + c_size_t size = (c_size_t)c_UIntArray_GetSize(adj); + c_uint_t target_value = 0; + for (c_size_t i=0; i= self->V || to >= self->V) return C_ERR_OUTOFBOUND; + + c_AdjList_t* adj = &self->adj_list[from]; + c_size_t size = (c_size_t)c_UIntArray_GetSize(adj); + c_bool_t found = C_FALSE; + + /* Locate the target destination 'to' inside the out-list array */ + for (c_size_t i = 0; i < size; ++i) { + c_uint_t generic_entry = 0; + if (c_UIntArray_Get(adj, i, &generic_entry) == C_SUCCESS) { + if ((c_size_t)generic_entry == to) { + /* Remove the entry element via your underlying array interface */ + c_UIntArray_Remove(adj, i); + found = C_TRUE; + break; + } + } + } + + if (found) { + if (self->E > 0) self->E--; + self->indegree[to]--; + return C_SUCCESS; + } + + return C_ERR_NOTFOUND; +} + +c_bool_t c_Digraph_HasEdge(const c_Digraph_t* self, c_size_t from, c_size_t to) { + if (!self || from >= self->V || to >= self->V) return C_FALSE; + + const c_AdjList_t* adj = &self->adj_list[from]; + c_size_t size = (c_size_t)c_UIntArray_GetSize((c_UIntArray_t*)adj); + + /* Scan the adjacency out-list of vertex 'from' */ + for (c_size_t i = 0; i < size; ++i) { + c_uint_t generic_val = 0; + /* Safely extract the adjacent vertex using your pointer specifications */ + c_err_t err = c_UIntArray_Get((c_UIntArray_t*)adj, i, &generic_val); + + if (err == C_SUCCESS && (c_size_t)generic_val == to) { + return C_TRUE; /* Edge found */ + } + } + + return C_FALSE; /* Edge does not exist */ +} diff --git a/Graph/c_Digraph.h b/Graph/c_Digraph.h new file mode 100644 index 0000000..f0ed82c --- /dev/null +++ b/Graph/c_Digraph.h @@ -0,0 +1,78 @@ +#ifndef INCLUDED_C_DIGRAPH_H +#define INCLUDED_C_DIGRAPH_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +#ifndef INCLUDED_C_ALLOCATOR_H +#include +#endif /*INCLUDED_C_ALLOCATOR_H*/ + +#ifndef INCLUDED_C_ADJLIST_H +#include +#endif /*INCLUDED_C_ADJLIST_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_size_t V; + c_size_t E; + c_AdjList_t* adj_list; + c_size_t* indegree; + c_Allocator_t allocator; +}c_Digraph_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_Digraph_Init(c_Digraph_t* self, c_size_t V, c_Allocator_t* alloc); + +void c_Digraph_Destroy(c_Digraph_t* self); + +c_err_t c_Digraph_AddEdge(c_Digraph_t* self, c_size_t from, c_size_t to); + +c_err_t c_Digraph_RemoveEdge(c_Digraph_t* self, c_size_t from, c_size_t to); + +c_err_t c_Digraph_GetEdge(c_Digraph_t* self, c_size_t from, c_size_t to, c_size_t * edge_idx); + +c_bool_t c_Digraph_HasEdge(const c_Digraph_t* self, c_size_t from, c_size_t to); + +c_err_t c_Digraph_Resize(c_Digraph_t* self, c_size_t new_V); + +c_size_t c_Digraph_GetDegree(c_Digraph_t* self, c_size_t v, c_bool_t out_degree_only); + +c_err_t c_Digraph_Reverse(c_Digraph_t* self, c_Digraph_t* out_reversed); + + +C_STATIC_FORCE_INLINE +c_size_t c_Digraph_GetV(const c_Digraph_t* self) { + return self ? self->V : 0; +} + +C_STATIC_FORCE_INLINE +c_size_t c_Digraph_GetE(const c_Digraph_t* self) { + return self ? self->E : 0; +} + +C_STATIC_FORCE_INLINE +c_AdjList_t* c_Digraph_GetAdj(const c_Digraph_t* self, c_size_t v) { + if (!self || v >= self->V) return NULL; + return &(self->adj_list[v]); +} + +C_STATIC_FORCE_INLINE +c_size_t c_Digraph_GetOutDegree(c_Digraph_t* self, c_size_t v) { + if (!self || v >=self->V) return 0; + return c_AdjList_GetSize(&self->adj_list[v]); +} + +C_STATIC_FORCE_INLINE +c_size_t c_Digraph_GetInDegree(c_Digraph_t* self, c_size_t v) { + if (!self || v >=self->V) return 0; + return self->indegree[v]; +} + +#endif /*INCLUDED_C_DIGRAPH_H*/ diff --git a/Graph/c_Digraph.t.c b/Graph/c_Digraph.t.c new file mode 100644 index 0000000..bd19b50 --- /dev/null +++ b/Graph/c_Digraph.t.c @@ -0,0 +1,136 @@ +#include "c_Digraph.h" +#include "c_Test.h" +#include +#include + + +/* 使用编写的 TEST_CASE 声明测试函数 */ +TEST_CASE(test_digraph_basic_and_degree) { + c_Digraph_t g; + c_err_t err = c_Digraph_Init(&g, 4, NULL); /* 4个顶点的图 */ + + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_INT_EQ(4, c_Digraph_GetV(&g)); + ASSERT_INT_EQ(0, c_Digraph_GetE(&g)); + + /* 添加有向边: 0->1, 0->2, 1->2 */ + c_Digraph_AddEdge(&g, 0, 1); + c_Digraph_AddEdge(&g, 0, 2); + c_Digraph_AddEdge(&g, 1, 2); + + ASSERT_INT_EQ(3, c_Digraph_GetE(&g)); + + /* 测试度数计算:0号节点的出度应为2,入度应为0 */ + ASSERT_INT_EQ(2, c_Digraph_GetDegree(&g, 0, C_TRUE)); /* 出度 */ + ASSERT_INT_EQ(0, c_Digraph_GetDegree(&g, 0, C_FALSE)); /* 入度 */ + + /* 测试度数计算:2号节点的出度应为0,入度应为2 (由0和1指向它) */ + ASSERT_INT_EQ(0, c_Digraph_GetDegree(&g, 2, C_TRUE)); + ASSERT_INT_EQ(2, c_Digraph_GetDegree(&g, 2, C_FALSE)); + + c_Digraph_Destroy(&g); +} + +TEST_CASE(test_digraph_reverse) { + c_Digraph_t g; + c_Digraph_Init(&g, 3, NULL); + c_Digraph_AddEdge(&g, 0, 1); /* 0 -> 1 */ + c_Digraph_AddEdge(&g, 1, 2); /* 1 -> 2 */ + + c_Digraph_t rev; + c_err_t err = c_Digraph_Reverse(&g, &rev); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* 反向图验证:原图 0->1,反向图应为 1->0 ;即0号节点入度应变为1,出度变为0 */ + ASSERT_INT_EQ(0, c_Digraph_GetDegree(&rev, 0, C_TRUE)); + ASSERT_INT_EQ(1, c_Digraph_GetDegree(&rev, 0, C_FALSE)); + + /* 反向图 2->1 ;即2号节点出度应变为1 */ + ASSERT_INT_EQ(1, c_Digraph_GetDegree(&rev, 2, C_TRUE)); + + c_Digraph_Destroy(&g); + c_Digraph_Destroy(&rev); +} + +TEST_CASE(test_unweighted_digraph_edge_existence) { + c_Digraph_t g; + c_err_t err = c_Digraph_Init(&g, 3, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Add edge 0 -> 1 */ + c_Digraph_AddEdge(&g, 0, 1); + + /* 1. Verify existence of the added edge */ + ASSERT_TRUE(c_Digraph_HasEdge(&g, 0, 1)); + + /* 2. Verify non-existence of reverse edge (1 -> 0) since it's a directed graph */ + ASSERT_FALSE(c_Digraph_HasEdge(&g, 1, 0)); + + /* 3. Verify non-existence of an unadded edge (0 -> 2) */ + ASSERT_FALSE(c_Digraph_HasEdge(&g, 0, 2)); + + /* 4. Verify that out-of-bounds inputs safely return C_FALSE instead of crashing */ + ASSERT_FALSE(c_Digraph_HasEdge(&g, 0, 99)); + ASSERT_FALSE(c_Digraph_HasEdge(&g, 99, 1)); + + c_Digraph_Destroy(&g); +} + +TEST_CASE(test_unweighted_digraph_get_edge_index) { + c_Digraph_t g; + c_err_t err = c_Digraph_Init(&g, 4, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Construct directed edges out from vertex 0: + * 0 -> 2 (inserted first, expected index 0) + * 0 -> 3 (inserted second, expected index 1) + */ + c_Digraph_AddEdge(&g, 0, 2); + c_Digraph_AddEdge(&g, 0, 3); + + c_size_t resolved_idx = 0; + + /* 1. Verify retrieval of the first inserted edge */ + err = c_Digraph_GetEdge(&g, 0, 2, &resolved_idx); + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_LL_EQ(0, resolved_idx); + + /* 2. Verify retrieval of the second inserted edge */ + err = c_Digraph_GetEdge(&g, 0, 3, &resolved_idx); + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_LL_EQ(1, resolved_idx); + + /* 3. Verify that non-existent edges return C_ERR_NOTFOUND */ + err = c_Digraph_GetEdge(&g, 0, 1, &resolved_idx); + ASSERT_INT_EQ(C_ERR_NOTFOUND, err); + + /* 4. Verify that reverse paths in directed graphs are correctly treated as non-existent */ + err = c_Digraph_GetEdge(&g, 2, 0, &resolved_idx); + ASSERT_INT_EQ(C_ERR_NOTFOUND, err); + + /* 5. Verify that out-of-bounds parameters are safely caught */ + err = c_Digraph_GetEdge(&g, 0, 99, &resolved_idx); + ASSERT_INT_EQ(C_ERR_OUTOFBOUND, err); + + err = c_Digraph_GetEdge(&g, 99, 2, &resolved_idx); + ASSERT_INT_EQ(C_ERR_OUTOFBOUND, err); + + c_Digraph_Destroy(&g); +} + + +int main(void) { + TEST_START(Digraph_DataStructure_Tests); + + /* 运行测试用例 */ + RUN_TEST(test_digraph_basic_and_degree); + RUN_TEST(test_digraph_reverse); + RUN_TEST(test_unweighted_digraph_edge_existence); + RUN_TEST(test_unweighted_digraph_get_edge_index); + + /* 输出总报告 */ + TEST_REPORT(); + + /* 阻断并返回状态代码 */ + RETURN_TEST_STATUS; +} diff --git a/Graph/c_DijkstraAllPairsSP.c b/Graph/c_DijkstraAllPairsSP.c new file mode 100644 index 0000000..d6aa4c1 --- /dev/null +++ b/Graph/c_DijkstraAllPairsSP.c @@ -0,0 +1,58 @@ +#include + +c_err_t c_DijkstraAllPairsSP_Init(c_DijkstraAllPairsSP_t* self, c_EdgeWeightedDigraph_t* graph, c_Allocator_t* allocator) { + if (!self || !graph) return C_ERR_PARAM; + + self->allocator = allocator ? *allocator : c_DefaultAllocator; + self->V = graph->V; + self->sp_matrix = NULL; + + if (self->V == 0) return C_SUCCESS; + + /* 1. Allocate an array of DijkstraSP structures */ + self->sp_matrix = (c_DijkstraSP_t*)c_Allocator_Calloc( + &self->allocator, + self->V, + sizeof(c_DijkstraSP_t) + ); + if (!self->sp_matrix) return C_ERR_NOMEM; + + /* 2. Run a full single-source Dijkstra search engine out from each vertex */ + for (c_size_t v = 0; v < self->V; ++v) { + c_err_t err = c_DijkstraSP_Init(&self->sp_matrix[v], graph, v, allocator); + + if (err != C_SUCCESS) { + /* Atomic Rollback: Destroy previously allocated instances to prevent OOM memory leaks */ + for (c_size_t i = 0; i < v; ++i) { + c_DijkstraSP_Destroy(&self->sp_matrix[i]); + } + c_Allocator_Free(&self->allocator, self->sp_matrix); + self->sp_matrix = NULL; + self->V = 0; + return err; + } + } + + return C_SUCCESS; +} + +void c_DijkstraAllPairsSP_Destroy(c_DijkstraAllPairsSP_t* self) { + if (!self) return; + + if (self->sp_matrix) { + /* Safely deallocate every single active search instance */ + for (c_size_t i = 0; i < self->V; ++i) { + c_DijkstraSP_Destroy(&self->sp_matrix[i]); + } + c_Allocator_Free(&self->allocator, self->sp_matrix); + self->sp_matrix = NULL; + } + self->V = 0; +} + +c_err_t c_DijkstraAllPairsSP_Path(c_DijkstraAllPairsSP_t* self, c_size_t u, c_size_t v, c_VertexIdList_t* out_path) { + if (!self || !self->sp_matrix || u >= self->V || v >= self->V || !out_path) return C_ERR_PARAM; + + /* Safely delegate extraction to the underlying single-source engine */ + return c_DijkstraSP_PathTo(&self->sp_matrix[u], v, out_path); +} diff --git a/Graph/c_DijkstraAllPairsSP.h b/Graph/c_DijkstraAllPairsSP.h new file mode 100644 index 0000000..44ac161 --- /dev/null +++ b/Graph/c_DijkstraAllPairsSP.h @@ -0,0 +1,68 @@ +#ifndef INCLUDED_C_DIJKSTRAALLPAIRSSP_H +#define INCLUDED_C_DIJKSTRAALLPAIRSSP_H + +#ifndef INCLUDED_C_EDGEWEIGHTEDDIGRAPH_H +#include +#endif /*INCLUDED_C_EDGEWEIGHTEDDIGRAPH_H*/ + + +#ifndef INCLUDED_C_DIJKSTRASP_H +#include +#endif /*INCLUDED_C_DIJKSTRASP_H*/ + + +#ifndef INCLUDED_C_VERTEXIDLIST_H +#include +#endif /*INCLUDED_C_VERTEXIDLIST_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_DijkstraSP_t* sp_matrix; /* Dynamic array of DijkstraSP engines (size of graph->V) */ + c_size_t V; /* Total number of vertices in the digraph */ + c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ +} c_DijkstraAllPairsSP_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Computes all-pairs shortest paths in an edge-weighted digraph. + * @param allocator Explicit allocator context used to configure internal tracking state memory + */ +c_err_t c_DijkstraAllPairsSP_Init(c_DijkstraAllPairsSP_t* self, c_EdgeWeightedDigraph_t* graph, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers and all embedded single-source engines safely. + */ +void c_DijkstraAllPairsSP_Destroy(c_DijkstraAllPairsSP_t* self); + +/** + * @brief Is there a directed path from vertex 'u' to vertex 'v'? + */ +C_STATIC_FORCE_INLINE +c_bool_t c_DijkstraAllPairsSP_HasPath(c_DijkstraAllPairsSP_t* self, c_size_t u, c_size_t v) { + if (!self || !self->sp_matrix || u >= self->V || v >= self->V) return C_FALSE; + return c_DijkstraSP_HasPathTo(&self->sp_matrix[u], v); +} + +/** + * @brief Returns the distance of the shortest path from vertex 'u' to vertex 'v'. + * @return Distance value, or DBL_MAX if unreachable / parameter error + */ +C_STATIC_FORCE_INLINE +double c_DijkstraAllPairsSP_Dist(c_DijkstraAllPairsSP_t* self, c_size_t u, c_size_t v) { + if (!self || !self->sp_matrix || u >= self->V || v >= self->V) return DBL_MAX; + return c_DijkstraSP_DistTo(&self->sp_matrix[u], v); +} + +/** + * @brief Reconstructs the exact shortest path from vertex 'u' to vertex 'v' and appends it to out_path. + * @param out_path An initialized c_VertexIdList_t container to collect the sequence of global directed edge_ids. + */ +c_err_t c_DijkstraAllPairsSP_Path(c_DijkstraAllPairsSP_t* self, c_size_t u, c_size_t v, c_VertexIdList_t* out_path); + + +#endif /*INCLUDED_C_DIJKSTRAALLPAIRSSP_H*/ diff --git a/Graph/c_DijkstraAllPairsSP.t.c b/Graph/c_DijkstraAllPairsSP.t.c new file mode 100644 index 0000000..3971921 --- /dev/null +++ b/Graph/c_DijkstraAllPairsSP.t.c @@ -0,0 +1,60 @@ +#include "c_Test.h" +#include "c_EdgeWeightedDigraph.h" +#include "c_DijkstraAllPairsSP.h" +#include "c_VertexIdList.h" + +TEST_CASE(test_dijkstra_all_pairs_sp_matrix) { + c_EdgeWeightedDigraph_t g; + c_err_t err = c_EdgeWeightedDigraph_Init(&g, 4, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Construct an evaluation network topology: + * 0 -> 1 (Weight: 5.0) [Edge 0] + * 0 -> 2 (Weight: 1.0) [Edge 1] + * 2 -> 1 (Weight: 2.0) [Edge 2] -> Path 0->2->1 total is 3.0 + * 1 -> 3 (Weight: 1.0) [Edge 3] -> Path 2->1->3 total is 3.0, Path 0->2->1->3 total is 4.0 + */ + c_EdgeWeightedDigraph_AddEdge(&g, 0, 1, 5.0); + c_EdgeWeightedDigraph_AddEdge(&g, 0, 2, 1.0); + c_EdgeWeightedDigraph_AddEdge(&g, 2, 1, 2.0); + c_EdgeWeightedDigraph_AddEdge(&g, 1, 3, 1.0); + + c_DijkstraAllPairsSP_t all_pairs; + err = c_DijkstraAllPairsSP_Init(&all_pairs, &g, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Verify random multi-source/multi-target query pairs */ + ASSERT_TRUE(c_DijkstraAllPairsSP_HasPath(&all_pairs, 0, 3)); + ASSERT_DOUBLE_EQ_MSG(4.0, c_DijkstraAllPairsSP_Dist(&all_pairs, 0, 3), "0 -> 3 shortest path wrong"); + + ASSERT_TRUE(c_DijkstraAllPairsSP_HasPath(&all_pairs, 2, 3)); + ASSERT_DOUBLE_EQ_MSG(3.0, c_DijkstraAllPairsSP_Dist(&all_pairs, 2, 3), "2 -> 3 shortest path wrong"); + + /* Backward direction from 3 to 0 must remain unlinked/unreachable */ + ASSERT_FALSE(c_DijkstraAllPairsSP_HasPath(&all_pairs, 3, 0)); + ASSERT_DOUBLE_EQ_MSG(DBL_MAX, c_DijkstraAllPairsSP_Dist(&all_pairs, 3, 0), "Unreachable distance check failed"); + + /* Verify path extraction trace */ + c_VertexIdList_t extracted_path; + c_VertexIdList_Init(&extracted_path, 0, 0); + + err = c_DijkstraAllPairsSP_Path(&all_pairs, 0, 3, &extracted_path); + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_LL_EQ(3, (c_size_t)c_VertexIdList_GetSize(&extracted_path)); + + c_uint_t edge_token = 0; + c_VertexIdList_Get(&extracted_path, 0, &edge_token); ASSERT_LL_EQ(1, edge_token); /* Edge 1 (0->2) */ + c_VertexIdList_Get(&extracted_path, 1, &edge_token); ASSERT_LL_EQ(2, edge_token); /* Edge 2 (2->1) */ + c_VertexIdList_Get(&extracted_path, 2, &edge_token); ASSERT_LL_EQ(3, edge_token); /* Edge 3 (1->3) */ + + c_VertexIdList_Destroy(&extracted_path); + c_DijkstraAllPairsSP_Destroy(&all_pairs); + c_EdgeWeightedDigraph_Destroy(&g); +} + +int main(void) { + TEST_START(DijkstraAllPairsSP_Matrix_Suite); + RUN_TEST(test_dijkstra_all_pairs_sp_matrix); + TEST_REPORT(); + RETURN_TEST_STATUS; +} diff --git a/Graph/c_DijkstraSP.c b/Graph/c_DijkstraSP.c new file mode 100644 index 0000000..0e0d925 --- /dev/null +++ b/Graph/c_DijkstraSP.c @@ -0,0 +1,133 @@ +#include "c_DijkstraSP.h" +#include "c_IndexMinPQ.h" + + +#define SP_SENTINEL ((c_size_t)-1) + +static int c_Dijkstra_DistCompare(const void* a, const void* b, void* args) { + double dist_a = *(const double*)a; + double dist_b = *(const double*)b; + (void)args; + return (dist_a > dist_b) - (dist_a < dist_b); +} + +static void c_Dijkstra_Relax(c_DijkstraSP_t* self, c_EdgeWeightedDigraph_t* graph, c_size_t v, c_IndexMinPQ_t* pq) { + c_AdjList_t* adj = &graph->adj_list[v]; + c_size_t size = (c_size_t)c_AdjList_GetSize(adj); + + for (c_size_t i = 0; i < size; ++i) { + c_uint_t generic_edge_id = 0; + c_err_t err = c_AdjList_Get(adj, i, &generic_edge_id); + + if (err == C_SUCCESS) { + c_size_t edge_id = (c_size_t)generic_edge_id; + c_DirectedEdge_t* edge = &graph->edges_pool[edge_id]; + c_size_t w = edge->to; + + if (edge->weight < 0.0) continue; + + if (self->dist_to[w] > self->dist_to[v] + edge->weight) { + self->dist_to[w] = self->dist_to[v] + edge->weight; + self->edge_to[w] = edge_id; + self->from_vertex[w] = v; /* Record parent vertex node step */ + + if (c_IndexMinPQ_Contains(pq, w)) { + c_IndexMinPQ_Change(pq, w, &self->dist_to[w]); + } else { + c_IndexMinPQ_Push(pq, w, &self->dist_to[w]); + } + } + } + } +} + +c_err_t c_DijkstraSP_Init(c_DijkstraSP_t* self, c_EdgeWeightedDigraph_t* graph, c_size_t s, c_Allocator_t* allocator) { + if (!self || !graph || s >= graph->V) return C_ERR_PARAM; + + self->allocator = allocator ? *allocator : c_DefaultAllocator; + self->s = s; + self->V = graph->V; + + self->edge_to = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + self->from_vertex = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + self->dist_to = (double*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(double)); + + if (!self->edge_to || !self->from_vertex || !self->dist_to) { + c_DijkstraSP_Destroy(self); + return C_ERR_NOMEM; + } + + for (c_size_t v = 0; v < self->V; ++v) { + self->dist_to[v] = DBL_MAX; + self->edge_to[v] = SP_SENTINEL; + self->from_vertex[v] = SP_SENTINEL; + } + self->dist_to[s] = 0.0; + + c_IndexMinPQ_t pq; + c_err_t err = c_IndexMinPQ_Init(&pq, self->V, sizeof(double) + , c_Dijkstra_DistCompare, NULL, allocator); + if (err != C_SUCCESS) { + c_DijkstraSP_Destroy(self); + return err; + } + + c_IndexMinPQ_Push(&pq, s, &self->dist_to[s]); + + while (pq.size > 0) { + c_size_t v = 0; + c_IndexMinPQ_Pop(&pq, &v); + c_Dijkstra_Relax(self, graph, v, &pq); + } + + c_IndexMinPQ_Destroy(&pq); + return C_SUCCESS; +} + +void c_DijkstraSP_Destroy(c_DijkstraSP_t* self) { + if (!self) return; + if (self->edge_to) c_Allocator_Free(&self->allocator, self->edge_to); + if (self->from_vertex) c_Allocator_Free(&self->allocator, self->from_vertex); + if (self->dist_to) c_Allocator_Free(&self->allocator, self->dist_to); + + self->edge_to = NULL; + self->from_vertex = NULL; + self->dist_to = NULL; + self->V = 0; + self->s = 0; +} + +/* ================================================================================================================== */ +/* Exact Signature Re-implementation Match */ + +c_err_t c_DijkstraSP_PathTo(c_DijkstraSP_t* self, c_size_t v, c_VertexIdList_t* out_path) { + if (!self || !out_path || v >= self->V) return C_ERR_PARAM; + if (!c_DijkstraSP_HasPathTo(self, v)) return C_ERR_FAIL; + + /* 1. Allocate an execution trace stack bounded tightly by V nodes */ + c_size_t* edge_stack = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + if (!edge_stack) return C_ERR_NOMEM; + + c_size_t stack_size = 0; + c_size_t curr_v = v; + + /* 2. Intercept and step backward securely through parent coordinates */ + while (curr_v != self->s) { + c_size_t edge_id = self->edge_to[curr_v]; + if (edge_id == SP_SENTINEL) break; /* Safety firewall anchor checkpoint */ + + edge_stack[stack_size++] = edge_id; + curr_v = self->from_vertex[curr_v]; /* Step backward to source node coordinate */ + } + + /* 3. Flush stack tokens in proper forward direction down into the collector array */ + c_err_t err = C_SUCCESS; + while (stack_size > 0) { + c_size_t target_edge_id = edge_stack[--stack_size]; + err = c_VertexIdList_Append(out_path, (c_uint_t)target_edge_id); + if (err != C_SUCCESS) break; + } + + c_Allocator_Free(&self->allocator, edge_stack); + return err; +} diff --git a/Graph/c_DijkstraSP.h b/Graph/c_DijkstraSP.h new file mode 100644 index 0000000..9cd81fa --- /dev/null +++ b/Graph/c_DijkstraSP.h @@ -0,0 +1,66 @@ +#ifndef INCLUDED_C_DIJKSTRASP_H +#define INCLUDED_C_DIJKSTRASP_H + +#ifndef INCLUDED_C_EDGEWEIGHTEDDIGRAPH_H +#include +#endif /*INCLUDED_C_EDGEWEIGHTEDDIGRAPH_H*/ + +#ifndef INCLUDED_C_VERTEXIDLIST_H +#include +#endif /*INCLUDED_C_VERTEXIDLIST_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_size_t* edge_to; /* edge_to[w] = entering directed edge_id on shortest path to w */ + c_size_t* from_vertex; /* from_vertex[w] = source vertex parent link on path to w */ + double* dist_to; /* dist_to[w] = cumulative distance tracking matrix */ + c_size_t s; /* The starting source vertex index */ + c_size_t V; /* Total number of vertices in the digraph */ + c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ +} c_DijkstraSP_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Computes a shortest-paths tree from the source vertex 's' in the edge-weighted digraph. + * @param allocator Explicit allocator context used to configure internal tracking state memory + */ +c_err_t c_DijkstraSP_Init(c_DijkstraSP_t* self, c_EdgeWeightedDigraph_t* graph, c_size_t s, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers safely. + */ +void c_DijkstraSP_Destroy(c_DijkstraSP_t* self); + +/** + * @brief Is there a directed path from the source vertex 's' to vertex 'v'? + */ +C_STATIC_FORCE_INLINE +c_bool_t c_DijkstraSP_HasPathTo(c_DijkstraSP_t* self, c_size_t v) { + if (!self || v >= self->V || !self->dist_to) return C_FALSE; + return self->dist_to[v] < DBL_MAX; +} + +/** + * @brief Returns the distance of the shortest path from the source vertex 's' to vertex 'v'. + * @return Distance value, or DBL_MAX if unreachable / parameter error + */ +C_STATIC_FORCE_INLINE +double c_DijkstraSP_DistTo(c_DijkstraSP_t* self, c_size_t v) { + if (!self || v >= self->V || !self->dist_to) return DBL_MAX; + return self->dist_to[v]; +} + +/** + * @brief Reconstructs the exact shortest path from the source vertex 's' to vertex 'v' and appends it to out_path. + * @param out_path An initialized c_VertexIdList_t container to collect the sequence of global directed edge_ids. + */ +c_err_t c_DijkstraSP_PathTo(c_DijkstraSP_t* self, c_size_t v, c_VertexIdList_t* out_path); + + + +#endif /*INCLUDED_C_DIJKSTRASP_H*/ diff --git a/Graph/c_DijkstraSP.t.c b/Graph/c_DijkstraSP.t.c new file mode 100644 index 0000000..930ac72 --- /dev/null +++ b/Graph/c_DijkstraSP.t.c @@ -0,0 +1,60 @@ +#include "c_Test.h" +#include "c_EdgeWeightedDigraph.h" +#include "c_DijkstraSP.h" + + +TEST_CASE(test_dijkstra_path_extraction) { + c_EdgeWeightedDigraph_t g; + c_err_t err = c_EdgeWeightedDigraph_Init(&g, 4, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Setup paths: + * 0 -> 1 (Weight: 5.0) [Edge ID: 0] + * 0 -> 2 (Weight: 1.0) [Edge ID: 1] + * 2 -> 1 (Weight: 2.0) [Edge ID: 2] + * 1 -> 3 (Weight: 1.0) [Edge ID: 3] + * True Shortest path from 0 to 3 is: 0 -> 2 -> 1 -> 3 (Total Weight = 4.0) + */ + c_EdgeWeightedDigraph_AddEdge(&g, 0, 1, 5.0); + c_EdgeWeightedDigraph_AddEdge(&g, 0, 2, 1.0); + c_EdgeWeightedDigraph_AddEdge(&g, 2, 1, 2.0); + c_EdgeWeightedDigraph_AddEdge(&g, 1, 3, 1.0); + + c_DijkstraSP_t sp; + err = c_DijkstraSP_Init(&sp, &g, 0, &g.allocator); + ASSERT_INT_EQ(C_SUCCESS, err); + + ASSERT_TRUE(c_DijkstraSP_HasPathTo(&sp, 3)); + ASSERT_DOUBLE_EQ_MSG(4.0, c_DijkstraSP_DistTo(&sp, 3), "Shortest distance mapping check failed"); + + /* Create list to intercept path edge tokens */ + c_VertexIdList_t edge_path; + c_VertexIdList_Init(&edge_path, 0, 0); + + /* Run the re-implemented signature format */ + err = c_DijkstraSP_PathTo(&sp, 3, &edge_path); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Expected edge sequence size should be 3 components long */ + c_size_t path_len = (c_size_t)c_VertexIdList_GetSize(&edge_path); + ASSERT_LL_EQ(3, path_len); + + /* Extract and verify raw edge pool identity markers sequentially */ + c_uint_t e_id = 0; + + c_VertexIdList_Get(&edge_path, 0, &e_id); ASSERT_LL_EQ(1, e_id); /* Edge ID 1 (0->2) */ + c_VertexIdList_Get(&edge_path, 1, &e_id); ASSERT_LL_EQ(2, e_id); /* Edge ID 2 (2->1) */ + c_VertexIdList_Get(&edge_path, 2, &e_id); ASSERT_LL_EQ(3, e_id); /* Edge ID 3 (1->3) */ + + c_VertexIdList_Destroy(&edge_path); + c_DijkstraSP_Destroy(&sp); + c_EdgeWeightedDigraph_Destroy(&g); +} + + +int main(void) { + TEST_START(DijkstraSP_ShortestPath_Suite); + RUN_TEST(test_dijkstra_path_extraction); + TEST_REPORT(); + RETURN_TEST_STATUS; +} diff --git a/Graph/c_DijkstraUndirectedSP.c b/Graph/c_DijkstraUndirectedSP.c new file mode 100644 index 0000000..b5e1a8b --- /dev/null +++ b/Graph/c_DijkstraUndirectedSP.c @@ -0,0 +1,130 @@ +#include +#include "c_IndexMinPQ.h" + +#define SP_SENTINEL ((c_size_t)-1) + +static int c_DijkstraUndirected_DistCompare(const void* a, const void* b, void* args) { + double dist_a = *(const double*)a; + double dist_b = *(const double*)b; + (void)args; + return (dist_a > dist_b) - (dist_a < dist_b); +} + +static void c_DijkstraUndirected_Relax(c_DijkstraUndirectedSP_t* self, c_EdgeWeightedGraph_t* graph, c_size_t v, c_IndexMinPQ_t* pq) { + c_AdjList_t* adj = &graph->adj_list[v]; + c_size_t size = (c_size_t)c_AdjList_GetSize(adj); + + for (c_size_t i = 0; i < size; ++i) { + c_uint_t generic_edge_id = 0; + c_err_t err = c_AdjList_Get(adj, i, &generic_edge_id); + + if (err == C_SUCCESS) { + c_size_t edge_id = (c_size_t)generic_edge_id; + c_Edge_t* edge = &graph->edges_pool[edge_id]; + + /* Resolve the other endpoint of the undirected edge relative to v */ + c_size_t w = (edge->v == v) ? edge->w : edge->v; + + /* Guard against negative edge weights which break Dijkstra's structural invariants */ + if (edge->weight < 0.0) continue; + + if (self->dist_to[w] > self->dist_to[v] + edge->weight) { + self->dist_to[w] = self->dist_to[v] + edge->weight; + self->edge_to[w] = edge_id; + self->from_vertex[w] = v; /* Record vertex node transition step */ + + if (c_IndexMinPQ_Contains(pq, w)) { + c_IndexMinPQ_Change(pq, w, &self->dist_to[w]); + } else { + c_IndexMinPQ_Push(pq, w, &self->dist_to[w]); + } + } + } + } +} + +c_err_t c_DijkstraUndirectedSP_Init(c_DijkstraUndirectedSP_t* self, c_EdgeWeightedGraph_t* graph, c_size_t s, c_Allocator_t* allocator) { + if (!self || !graph || s >= graph->V) return C_ERR_PARAM; + + self->allocator = allocator ? *allocator : c_DefaultAllocator; + self->s = s; + self->V = graph->V; + + self->edge_to = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + self->from_vertex = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + self->dist_to = (double*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(double)); + + if (!self->edge_to || !self->from_vertex || !self->dist_to) { + c_DijkstraUndirectedSP_Destroy(self); + return C_ERR_NOMEM; + } + + for (c_size_t v = 0; v < self->V; ++v) { + self->dist_to[v] = DBL_MAX; + self->edge_to[v] = SP_SENTINEL; + self->from_vertex[v] = SP_SENTINEL; + } + self->dist_to[s] = 0.0; + + c_IndexMinPQ_t pq; + c_err_t err = c_IndexMinPQ_Init(&pq, self->V, sizeof(double), c_DijkstraUndirected_DistCompare, NULL, allocator); + if (err != C_SUCCESS) { + c_DijkstraUndirectedSP_Destroy(self); + return err; + } + + c_IndexMinPQ_Push(&pq, s, &self->dist_to[s]); + + while (pq.size > 0) { + c_size_t v = 0; + c_IndexMinPQ_Pop(&pq, &v); + c_DijkstraUndirected_Relax(self, graph, v, &pq); + } + + c_IndexMinPQ_Destroy(&pq); + return C_SUCCESS; +} + +void c_DijkstraUndirectedSP_Destroy(c_DijkstraUndirectedSP_t* self) { + if (!self) return; + if (self->edge_to) c_Allocator_Free(&self->allocator, self->edge_to); + if (self->from_vertex) c_Allocator_Free(&self->allocator, self->from_vertex); + if (self->dist_to) c_Allocator_Free(&self->allocator, self->dist_to); + + self->edge_to = NULL; + self->from_vertex = NULL; + self->dist_to = NULL; + self->V = 0; + self->s = 0; +} + +c_err_t c_DijkstraUndirectedSP_PathTo(c_DijkstraUndirectedSP_t* self, c_size_t v, c_VertexIdList_t* out_path) { + if (!self || !out_path || v >= self->V) return C_ERR_PARAM; + if (!c_DijkstraUndirectedSP_HasPathTo(self, v)) return C_ERR_FAIL; + + c_size_t* edge_stack = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + if (!edge_stack) return C_ERR_NOMEM; + + c_size_t stack_size = 0; + c_size_t curr_v = v; + + /* Trace backward via recorded source transitions */ + while (curr_v != self->s) { + c_size_t edge_id = self->edge_to[curr_v]; + if (edge_id == SP_SENTINEL) break; + + edge_stack[stack_size++] = edge_id; + curr_v = self->from_vertex[curr_v]; + } + + /* Flip and append forward into output vertex ID list */ + c_err_t err = C_SUCCESS; + while (stack_size > 0) { + c_size_t target_edge_id = edge_stack[--stack_size]; + err = c_VertexIdList_Append(out_path, (c_uint_t)target_edge_id); + if (err != C_SUCCESS) break; + } + + c_Allocator_Free(&self->allocator, edge_stack); + return err; +} diff --git a/Graph/c_DijkstraUndirectedSP.h b/Graph/c_DijkstraUndirectedSP.h new file mode 100644 index 0000000..fbd7bbe --- /dev/null +++ b/Graph/c_DijkstraUndirectedSP.h @@ -0,0 +1,65 @@ +#ifndef INCLUDED_C_DIJKSTRAUNDIRECTEDSP_H +#define INCLUDED_C_DIJKSTRAUNDIRECTEDSP_H + +#ifndef INCLUDED_C_EDGEWEIGHTEDGRAPH_H +#include +#endif /*INCLUDED_C_EDGEWEIGHTEDGRAPH_H*/ + + +#ifndef INCLUDED_C_VERTEXIDLIST_H +#include +#endif /*INCLUDED_C_VERTEXIDLIST_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_size_t* edge_to; /* edge_to[w] = entering edge_id on shortest path to w */ + c_size_t* from_vertex; /* from_vertex[w] = source vertex parent link on path to w */ + double* dist_to; /* dist_to[w] = cumulative distance from source to w */ + c_size_t s; /* The starting source vertex index */ + c_size_t V; /* Total number of vertices in the graph */ + c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ +} c_DijkstraUndirectedSP_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Computes a shortest-paths tree from the source vertex 's' in the edge-weighted undirected graph. + * @param allocator Explicit allocator context used to configure internal tracking state memory + */ +c_err_t c_DijkstraUndirectedSP_Init(c_DijkstraUndirectedSP_t* self, c_EdgeWeightedGraph_t* graph, c_size_t s, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers safely. + */ +void c_DijkstraUndirectedSP_Destroy(c_DijkstraUndirectedSP_t* self); + +/** + * @brief Is there a path from the source vertex 's' to vertex 'v'? + */ +C_STATIC_FORCE_INLINE +c_bool_t c_DijkstraUndirectedSP_HasPathTo(c_DijkstraUndirectedSP_t* self, c_size_t v) { + if (!self || v >= self->V || !self->dist_to) return C_FALSE; + return self->dist_to[v] < DBL_MAX; +} + +/** + * @brief Returns the distance of the shortest path from the source vertex 's' to vertex 'v'. + */ +C_STATIC_FORCE_INLINE +double c_DijkstraUndirectedSP_DistTo(c_DijkstraUndirectedSP_t* self, c_size_t v) { + if (!self || v >= self->V || !self->dist_to) return DBL_MAX; + return self->dist_to[v]; +} + +/** + * @brief Reconstructs the exact shortest path from the source vertex 's' to vertex 'v' and appends it to out_path. + * @param out_path An initialized c_VertexIdList_t container to collect the sequence of global edge_ids. + */ +c_err_t c_DijkstraUndirectedSP_PathTo(c_DijkstraUndirectedSP_t* self, c_size_t v, c_VertexIdList_t* out_path); + + +#endif /*INCLUDED_C_DIJKSTRAUNDIRECTEDSP_H*/ diff --git a/Graph/c_DijkstraUndirectedSP.t.c b/Graph/c_DijkstraUndirectedSP.t.c new file mode 100644 index 0000000..70bc962 --- /dev/null +++ b/Graph/c_DijkstraUndirectedSP.t.c @@ -0,0 +1,63 @@ +#include "c_Test.h" +#include "c_EdgeWeightedGraph.h" +#include "c_DijkstraUndirectedSP.h" +#include "c_VertexIdList.h" + +TEST_CASE(test_dijkstra_undirected_paths) { + c_EdgeWeightedGraph_t g; + c_err_t err = c_EdgeWeightedGraph_Init(&g, 4, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Construct an undirected graph: + * 0 - 1 (Weight: 5.0) [Edge 0] + * 0 - 2 (Weight: 1.0) [Edge 1] + * 2 - 1 (Weight: 1.0) [Edge 2] -> Path 0-2-1 total is 2.0 + * 1 - 3 (Weight: 2.0) [Edge 3] -> Path 0-2-1-3 total is 4.0 + */ + c_EdgeWeightedGraph_AddEdge(&g, 0, 1, 5.0); + c_EdgeWeightedGraph_AddEdge(&g, 0, 2, 1.0); + c_EdgeWeightedGraph_AddEdge(&g, 2, 1, 1.0); + c_EdgeWeightedGraph_AddEdge(&g, 1, 3, 2.0); + + c_DijkstraUndirectedSP_t sp; + err = c_DijkstraUndirectedSP_Init(&sp, &g, 0, &g.allocator); + ASSERT_INT_EQ(C_SUCCESS, err); + + ASSERT_TRUE(c_DijkstraUndirectedSP_HasPathTo(&sp, 3)); + ASSERT_DOUBLE_EQ_MSG(4.0, c_DijkstraUndirectedSP_DistTo(&sp, 3), "Shortest undirected distance failed"); + + c_VertexIdList_t edge_path; + c_VertexIdList_Init(&edge_path, 0, 0); + + err = c_DijkstraUndirectedSP_PathTo(&sp, 3, &edge_path); + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_LL_EQ(3, (c_size_t)c_VertexIdList_GetSize(&edge_path)); + + c_uint_t e_id = 0; + c_VertexIdList_Get(&edge_path, 0, &e_id); ASSERT_LL_EQ(1, e_id); /* Edge 1 (0-2) */ + c_Edge_t edge; + c_EdgeWeightedGraph_GetEdge(&g, e_id, &edge); + ASSERT_LL_EQ(0, edge.v); + ASSERT_LL_EQ(2, edge.w); + c_VertexIdList_Get(&edge_path, 1, &e_id); ASSERT_LL_EQ(2, e_id); /* Edge 2 (2-1) */ + c_EdgeWeightedGraph_GetEdge(&g, e_id, &edge); + ASSERT_LL_EQ(2, edge.v); + ASSERT_LL_EQ(1, edge.w); + c_VertexIdList_Get(&edge_path, 2, &e_id); ASSERT_LL_EQ(3, e_id); /* Edge 3 (1-3) */ + c_EdgeWeightedGraph_GetEdge(&g, e_id, &edge); + ASSERT_LL_EQ(1, edge.v); + ASSERT_LL_EQ(3, edge.w); + + + + c_VertexIdList_Destroy(&edge_path); + c_DijkstraUndirectedSP_Destroy(&sp); + c_EdgeWeightedGraph_Destroy(&g); +} + +int main(void) { + TEST_START(DijkstraUndirectedSP_Suite); + RUN_TEST(test_dijkstra_undirected_paths); + TEST_REPORT(); + RETURN_TEST_STATUS; +} diff --git a/Graph/c_DirectedCycle.c b/Graph/c_DirectedCycle.c new file mode 100644 index 0000000..555ff3a --- /dev/null +++ b/Graph/c_DirectedCycle.c @@ -0,0 +1,108 @@ +#include + + +/* Private recursive engine helper */ +static void c_DirectedCycle_DFS(c_DirectedCycle_t* self, const c_Digraph_t* graph, c_size_t v) { + self->on_stack[v] = C_TRUE; + self->marked[v] = C_TRUE; + + c_AdjList_t* adj = &graph->adj_list[v]; + c_size_t size = (c_size_t)c_AdjList_GetSize(adj); + + for (c_size_t i = 0; i < size; ++i) { + c_uint_t target_value = 0; + /* Safely query edge list element matching custom pointer specifications */ + c_err_t err = c_AdjList_Get(adj, i, &target_value); + + if (err == C_SUCCESS) { + c_size_t w = (c_size_t)target_value; + + /* Short-circuit if a cycle has already been detected */ + if (c_DirectedCycle_HasCycle(self)) return; + + if (!self->marked[w]) { + self->edge_to[w] = v; + c_DirectedCycle_DFS(self, graph, w); + } + /* Cycle detected! Trace back path sequence */ + else if (self->on_stack[w]) { + /* Collect cycle steps onto a temporary reverse stack trace */ + c_size_t* reverse_stack = (c_size_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_size_t)); + if (!reverse_stack) return; + + c_size_t stack_size = 0; + for (c_size_t x = v; x != w; x = self->edge_to[x]) { + reverse_stack[stack_size++] = x; + } + reverse_stack[stack_size++] = w; + reverse_stack[stack_size++] = v; + + /* Push structured forward order into the self->cycle storage container using VertexIdList interface */ + while (stack_size > 0) { + c_VertexIdList_Append(&self->cycle, (c_uint_t)reverse_stack[--stack_size]); + } + + c_Allocator_Free(&self->allocator, reverse_stack); + return; + } + } + } + + self->on_stack[v] = C_FALSE; +} + +c_err_t c_DirectedCycle_Init(c_DirectedCycle_t* self, const c_Digraph_t* graph, c_Allocator_t* allocator) { + if (!self || !graph) return C_ERR_PARAM; + + self->allocator = allocator ? *allocator : c_DefaultAllocator; + self->marked = (c_bool_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_bool_t)); + self->edge_to = (c_size_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_size_t)); + self->on_stack = (c_bool_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_bool_t)); + + /* Using c_VertexIdList_Init mapper directly onto your internal cycle storage */ + c_VertexIdList_Init(&self->cycle,0, allocator); + + if (!self->marked || !self->edge_to || !self->on_stack) { + c_DirectedCycle_Destroy(self); + return C_ERR_NOMEM; + } + + /* Iterate through all vertices to handle disconnected graph structures */ + for (c_size_t v = 0; v < graph->V; ++v) { + if (!self->marked[v] && !c_DirectedCycle_HasCycle(self)) { + c_DirectedCycle_DFS(self, graph, v); + } + } + + return C_SUCCESS; +} + +void c_DirectedCycle_Destroy(c_DirectedCycle_t* self) { + if (!self) return; + if (self->marked) c_Allocator_Free(&self->allocator, self->marked); + if (self->edge_to) c_Allocator_Free(&self->allocator, self->edge_to); + if (self->on_stack) c_Allocator_Free(&self->allocator, self->on_stack); + + c_VertexIdList_Destroy(&self->cycle); + + self->marked = NULL; + self->edge_to = NULL; + self->on_stack = NULL; +} + +c_err_t c_DirectedCycle_GetCycle(c_DirectedCycle_t* self, c_VertexIdList_t* out_cycle) { + if (!self || !out_cycle) return C_ERR_PARAM; + if (!c_DirectedCycle_HasCycle(self)) return C_ERR_FAIL; + + c_size_t size = (c_size_t)c_VertexIdList_GetSize(&self->cycle); + for (c_size_t i = 0; i < size; ++i) { + c_uint_t val = 0; + /* Using the safe pointer signature format */ + c_err_t err = c_VertexIdList_Get((c_VertexIdList_t*)&self->cycle, i, &val); + if (err == C_SUCCESS) { + c_err_t app_err = c_VertexIdList_Append(out_cycle, val); + if (app_err != C_SUCCESS) return app_err; + } + } + return C_SUCCESS; +} diff --git a/Graph/c_DirectedCycle.h b/Graph/c_DirectedCycle.h new file mode 100644 index 0000000..4783a51 --- /dev/null +++ b/Graph/c_DirectedCycle.h @@ -0,0 +1,55 @@ +#ifndef INCLUDED_C_DIRECTEDCYCLE_H +#define INCLUDED_C_DIRECTEDCYCLE_H + +#ifndef INCLUDED_C_DIGRAPH_H +#include +#endif /*INCLUDED_C_DIGRAPH_H*/ + +#ifndef INCLUDED_C_VERTEXIDLIST_H +#include +#endif /*INCLUDED_C_VERTEXIDLIST_H*/ + + + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_bool_t* marked; /* Visited tracking array (size of graph->V) */ + c_size_t* edge_to; /* edge_to[w] = last edge on path to w */ + c_bool_t* on_stack; /* Keeps track of vertices currently on the recursive DFS stack */ + c_VertexIdList_t cycle; /* Stores the cycle sequence if found */ + c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ +} c_DirectedCycle_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Determines if the digraph has a directed cycle, and if so, finds one. + * @param allocator Explicit allocator context used to allocate internal routing arrays + */ +c_err_t c_DirectedCycle_Init(c_DirectedCycle_t* self, const c_Digraph_t* graph, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers + */ +void c_DirectedCycle_Destroy(c_DirectedCycle_t* self); + +/** + * @brief Does the digraph have a directed cycle? + */ +C_STATIC_FORCE_INLINE c_bool_t c_DirectedCycle_HasCycle(c_DirectedCycle_t* self) { + if (!self) return C_FALSE; + return !c_VertexIdList_IsEmpty(&self->cycle); +} + +/** + * @brief Gets the vertices on a directed cycle. + * @param out_cycle An initialized c_VertexIdList_t container to collect the sequence. + */ +c_err_t c_DirectedCycle_GetCycle(c_DirectedCycle_t* self, c_VertexIdList_t* out_cycle); + + +#endif /*INCLUDED_C_DIRECTEDCYCLE_H*/ diff --git a/Graph/c_DirectedCycle.t.c b/Graph/c_DirectedCycle.t.c new file mode 100644 index 0000000..8a3e4ab --- /dev/null +++ b/Graph/c_DirectedCycle.t.c @@ -0,0 +1,71 @@ +#include "c_Test.h" +#include "c_Digraph.h" +#include "c_DirectedCycle.h" +#include "c_VertexIdList.h" + +TEST_CASE(test_directed_cycle_detection_with_vertex_list) { + c_Digraph_t g; + + /* 1. Initialize a directed graph with 4 vertices using the default allocator */ + c_err_t err = c_Digraph_Init(&g, 4, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* + * 2. Construct an explicit cyclical loop topology: + * 0 -> 1 -> 2 -> 0 (Cyclic core group) + * 2 -> 3 (Dead-end branch link) + */ + c_Digraph_AddEdge(&g, 0, 1); + c_Digraph_AddEdge(&g, 1, 2); + c_Digraph_AddEdge(&g, 2, 0); /* Creates back edge link closing the loop */ + c_Digraph_AddEdge(&g, 2, 3); + + /* 3. Initialize the cycle processing engine (explicitly passing graph's allocator) */ + c_DirectedCycle_t detector; + err = c_DirectedCycle_Init(&detector, &g, &g.allocator); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* 4. Assert that a directed cycle was successfully intercepted */ + ASSERT_TRUE(c_DirectedCycle_HasCycle(&detector)); + + /* 5. Extract the tracked cycle loop vertices into an external c_VertexIdList_t container */ + c_VertexIdList_t extracted_cycle; + c_VertexIdList_Init(&extracted_cycle, 0, NULL); + + err = c_DirectedCycle_GetCycle(&detector, &extracted_cycle); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* The cyclic sequence path count should be 4 elements long (e.g., 0 -> 1 -> 2 -> 0) */ + c_size_t cycle_size = (c_size_t)c_VertexIdList_GetSize(&extracted_cycle); + ASSERT_LL_EQ(4, cycle_size); + + /* 6. Verify the sequential order of vertices inside the cycle using safe c_VertexIdList_Get specs */ + c_uint_t v0 = 0, v1 = 0, v2 = 0, v3 = 0; + + ASSERT_INT_EQ(C_SUCCESS, c_VertexIdList_Get(&extracted_cycle, 0, &v0)); + ASSERT_INT_EQ(C_SUCCESS, c_VertexIdList_Get(&extracted_cycle, 1, &v1)); + ASSERT_INT_EQ(C_SUCCESS, c_VertexIdList_Get(&extracted_cycle, 2, &v2)); + ASSERT_INT_EQ(C_SUCCESS, c_VertexIdList_Get(&extracted_cycle, 3, &v3)); + + /* A valid cycle validation check ensures the sequence loops cleanly back to its origin vertex */ + ASSERT_LL_EQ(v0, v3); + + /* 7. Clean up and reclaim all tracked buffer allocations safely */ + c_VertexIdList_Destroy(&extracted_cycle); + c_DirectedCycle_Destroy(&detector); + c_Digraph_Destroy(&g); +} + +int main(void) { + /* Bootstrapping the testing test suite environment */ + TEST_START(DirectedCycle_Verification_Suite); + + /* Run specified structural topology edge cases */ + RUN_TEST(test_directed_cycle_detection_with_vertex_list); + + /* Generate the total reporting tracking sheet metrics summary */ + TEST_REPORT(); + + /* Unwind back with correct failure signal flag checking patterns */ + RETURN_TEST_STATUS; +} diff --git a/Graph/c_DirectedDFS.c b/Graph/c_DirectedDFS.c new file mode 100644 index 0000000..56717bd --- /dev/null +++ b/Graph/c_DirectedDFS.c @@ -0,0 +1,59 @@ +#include + +/* Private recursive engine helper */ +static void c_DirectedDFS_Internal(c_DirectedDFS_t* self, const c_Digraph_t* graph, c_size_t v) { + self->marked[v] = C_TRUE; + self->count++; + + c_AdjList_t* adj = &graph->adj_list[v]; + c_size_t size = (c_size_t)c_AdjList_GetSize(adj); + + for (c_size_t i = 0; i < size; ++i) { + c_size_t w; + if (c_AdjList_Get(adj, i, &w)!=C_ERR_OK) { + continue; + } + if (!self->marked[w]) { + c_DirectedDFS_Internal(self, graph, w); + } + } +} + +c_err_t c_DirectedDFS_Init(c_DirectedDFS_t* self, const c_Digraph_t* graph, c_size_t s, c_Allocator_t* allocator) { + if (!self || !graph || s >= graph->V) return C_ERR_PARAM; + + self->allocator = allocator?*allocator:c_DefaultAllocator; + self->count = 0; + self->marked = (c_bool_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_bool_t)); + if (!self->marked) return C_ERR_NOMEM; + + c_DirectedDFS_Internal(self, graph, s); + return C_SUCCESS; +} + +c_err_t c_DirectedDFS_InitMulti(c_DirectedDFS_t* self, const c_Digraph_t* graph, const c_size_t* sources, c_size_t source_count, c_Allocator_t* allocator) { + if (!self || !graph || !sources || source_count == 0) return C_ERR_PARAM; + + self->allocator = allocator?*allocator:c_DefaultAllocator; + self->count = 0; + self->marked = (c_bool_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_bool_t)); + if (!self->marked) return C_ERR_NOMEM; + + for (c_size_t i = 0; i < source_count; ++i) { + c_size_t s = sources[i]; + if (s < graph->V && !self->marked[s]) { + c_DirectedDFS_Internal(self, graph, s); + } + } + return C_SUCCESS; +} + +void c_DirectedDFS_Destroy(c_DirectedDFS_t* self) { + if (!self) return; + if (self->marked) { + c_Allocator_Free(&self->allocator, self->marked); + self->marked = NULL; + } + self->count = 0; +} + diff --git a/Graph/c_DirectedDFS.h b/Graph/c_DirectedDFS.h new file mode 100644 index 0000000..c981a5e --- /dev/null +++ b/Graph/c_DirectedDFS.h @@ -0,0 +1,55 @@ +#ifndef INCLUDED_C_DIRECTEDDFS_H +#define INCLUDED_C_DIRECTEDDFS_H + +#ifndef INCLUDED_C_DIGRAPH_H +#include +#endif /*INCLUDED_C_DIGRAPH_H*/ + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_bool_t* marked; /* Visited tracking array (size of graph->V) */ + c_size_t count; /* Total number of vertices reachable from source(s) */ + c_Allocator_t allocator; +}c_DirectedDFS_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Computes vertices reachable from a single source vertex 's' + */ +c_err_t c_DirectedDFS_Init(c_DirectedDFS_t* self, const c_Digraph_t* graph, c_size_t s, c_Allocator_t* allocator); + +/** + * @brief Computes vertices reachable from a list of multiple source vertices + */ +c_err_t c_DirectedDFS_InitMulti(c_DirectedDFS_t* self, const c_Digraph_t* graph, const c_size_t* sources, c_size_t source_count, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers + */ +void c_DirectedDFS_Destroy(c_DirectedDFS_t* self); + +/* ================================================================================================================== */ +/* Inline Query Interfaces */ + +/** + * @brief Is there a directed path from the source(s) to vertex 'v'? + */ +C_STATIC_FORCE_INLINE +c_bool_t c_DirectedDFS_HasPathTo(const c_DirectedDFS_t* self, c_size_t v, c_size_t total_V) { + if (!self || !self->marked || v >= total_V) return C_FALSE; + return self->marked[v]; +} + +/** + * @brief Returns the total count of reachable vertices + */ +C_STATIC_FORCE_INLINE +c_size_t c_DirectedDFS_GetCount(const c_DirectedDFS_t* self) { + return self ? self->count : 0; +} + +#endif /*INCLUDED_C_DIRECTEDDFS_H*/ diff --git a/Graph/c_DirectedDFS.t.c b/Graph/c_DirectedDFS.t.c new file mode 100644 index 0000000..81bd46d --- /dev/null +++ b/Graph/c_DirectedDFS.t.c @@ -0,0 +1,43 @@ +#include "c_DirectedDFS.h" +#include "c_Test.h" +#include +#include + + +TEST_CASE(test_directed_dfs_reachability) { + c_Digraph_t g; + c_Digraph_Init(&g, 6, NULL); + + /* Construct a graph chain structure: 0 -> 1 -> 2 -> 3; and separate branch 4 -> 5 */ + c_Digraph_AddEdge(&g, 0, 1); + c_Digraph_AddEdge(&g, 1, 2); + c_Digraph_AddEdge(&g, 2, 3); + c_Digraph_AddEdge(&g, 4, 5); + + /* 1. Run DFS starting from source vertex 0 */ + c_DirectedDFS_t dfs; + c_err_t err = c_DirectedDFS_Init(&dfs, &g, 0, 0); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Verify Reachability mapping */ + ASSERT_TRUE(c_DirectedDFS_HasPathTo(&dfs, 0, g.V)); + ASSERT_TRUE(c_DirectedDFS_HasPathTo(&dfs, 3, g.V)); + ASSERT_FALSE(c_DirectedDFS_HasPathTo(&dfs, 4, g.V)); /* Isolated from 0 */ + ASSERT_INT_EQ(4, c_DirectedDFS_GetCount(&dfs)); /* 0, 1, 2, 3 are found */ + + c_DirectedDFS_Destroy(&dfs); + c_Digraph_Destroy(&g); +} + + +int main(int argc, char** argv){ + + TEST_START(Component Tests); + + // Execution list configurations + RUN_TEST(test_directed_dfs_reachability); + + TEST_REPORT(); + + RETURN_TEST_STATUS; +} diff --git a/Graph/c_DirectedEdge.c b/Graph/c_DirectedEdge.c new file mode 100644 index 0000000..1746572 --- /dev/null +++ b/Graph/c_DirectedEdge.c @@ -0,0 +1 @@ +#include diff --git a/Graph/c_DirectedEdge.h b/Graph/c_DirectedEdge.h new file mode 100644 index 0000000..1f6a405 --- /dev/null +++ b/Graph/c_DirectedEdge.h @@ -0,0 +1,36 @@ +#ifndef INCLUDED_C_DIRECTEDEDGE_H +#define INCLUDED_C_DIRECTEDEDGE_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_size_t from; /* The source vertex index */ + c_size_t to; /* The destination vertex index */ + double weight; /* The numerical edge weight */ +} c_DirectedEdge_t; + +#define C_DIRECTED_EDGE_SENTINEL ((c_size_t)(-1)) + +/* Inline helper methods for c_DirectedEdge_t */ +C_STATIC_FORCE_INLINE +c_size_t c_DirectedEdge_From(const c_DirectedEdge_t* edge) { + return edge ? edge->from : 0; +} + +C_STATIC_FORCE_INLINE +c_size_t c_DirectedEdge_To(const c_DirectedEdge_t* edge) { + return edge ? edge->to : 0; +} + +C_STATIC_FORCE_INLINE +double c_DirectedEdge_Weight(const c_DirectedEdge_t* edge) { + return edge ? edge->weight : 0.0; +} + +#endif /*INCLUDED_C_DIRECTEDEDGE_H*/ diff --git a/Graph/c_DirectedEulerianCycle.c b/Graph/c_DirectedEulerianCycle.c new file mode 100644 index 0000000..3b6e919 --- /dev/null +++ b/Graph/c_DirectedEulerianCycle.c @@ -0,0 +1,121 @@ +#include +#include "c_NonrecursiveDirectedDFS.h" + +c_err_t c_DirectedEulerianCycle_Init(c_DirectedEulerianCycle_t* self, c_Digraph_t* graph, c_Allocator_t* allocator) { + if (!self || !graph) return C_ERR_PARAM; + + self->allocator = allocator ? *allocator : c_DefaultAllocator; + c_VertexIdList_Init(&self->cycle, 0, allocator); + + /* Edge case: An empty graph with no edges trivially has an empty Eulerian cycle path */ + if (graph->E == 0) return C_SUCCESS; + + /* 1. Necessary condition verification: In-degree must equal Out-degree for all nodes */ + c_size_t non_isolated_vertex = graph->V; + for (c_size_t v = 0; v < graph->V; ++v) { + c_size_t out_deg = c_Digraph_GetOutDegree(graph, v); + c_size_t in_deg = c_Digraph_GetInDegree(graph, v); + + if (out_deg != in_deg) { + return C_SUCCESS; /* No Eulerian cycle exists, return gracefully with empty cycle path */ + } + if (out_deg > 0 && non_isolated_vertex == graph->V) { + non_isolated_vertex = v; /* Track a starting node candidates containing edges */ + } + } + + if (non_isolated_vertex == graph->V) return C_SUCCESS; // Graph has no edges + + /* 2. Necessary condition verification: Connectivity check via non-recursive DFS */ + c_NonrecursiveDirectedDFS_t dfs; + c_err_t err = c_NonrecursiveDirectedDFS_Init(&dfs, graph, non_isolated_vertex, allocator); + if (err != C_SUCCESS) return err; + + for (c_size_t v = 0; v < graph->V; ++v) { + if (c_Digraph_GetOutDegree(graph, v) > 0 && !c_NonrecursiveDirectedDFS_HasPathTo(&dfs, v, graph->V)) { + c_NonrecursiveDirectedDFS_Destroy(&dfs); + return C_SUCCESS; /* Graph edges are disconnected */ + } + } + c_NonrecursiveDirectedDFS_Destroy(&dfs); + + /* 3. Hierholzer's Algorithm: Construct the cycle path */ + /* Maintain local tracking copies of current edge indexing to safely 'consume' them */ + c_size_t* edge_index_iterator = (c_size_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_size_t)); + if (!edge_index_iterator) return C_ERR_NOMEM; + + /* Stack setup for path assembly tracking: capacity bounded by E + 1 */ + c_size_t max_stack_cap = graph->E + 1; + c_size_t* path_stack = (c_size_t*)c_Allocator_Calloc(&self->allocator, max_stack_cap, sizeof(c_size_t)); + if (!path_stack) { + c_Allocator_Free(&self->allocator, edge_index_iterator); + return C_ERR_NOMEM; + } + + /* Temporary collector stack tracking for final inversion layout */ + c_size_t* output_stack = (c_size_t*)c_Allocator_Calloc(&self->allocator, max_stack_cap, sizeof(c_size_t)); + if (!output_stack) { + c_Allocator_Free(&self->allocator, edge_index_iterator); + c_Allocator_Free(&self->allocator, path_stack); + return C_ERR_NOMEM; + } + + c_size_t path_stack_size = 0; + c_size_t output_stack_size = 0; + + /* Start the path construction from our candidate node */ + path_stack[path_stack_size++] = non_isolated_vertex; + + while (path_stack_size > 0) { + c_size_t curr_v = path_stack[path_stack_size - 1]; + c_AdjList_t* adj = &graph->adj_list[curr_v]; + c_size_t out_degree = (c_size_t)c_UIntArray_GetSize(adj); + + if (edge_index_iterator[curr_v] < out_degree) { + c_uint_t target_w = 0; + err = c_UIntArray_Get(adj, edge_index_iterator[curr_v], &target_w); + edge_index_iterator[curr_v]++; /* Consume the edge */ + + if (err == C_SUCCESS) { + path_stack[path_stack_size++] = (c_size_t)target_w; + } + } else { + /* Backtrack step: Node has no remaining active edges out, push to tour results */ + output_stack[output_stack_size++] = curr_v; + path_stack_size--; + } + } + + /* Push collected route into the formal c_VertexIdList storage layout matching target sequential format */ + while (output_stack_size > 0) { + c_VertexIdList_Append(&self->cycle, (c_uint_t)output_stack[--output_stack_size]); + } + + /* Clean up local working variables */ + c_Allocator_Free(&self->allocator, edge_index_iterator); + c_Allocator_Free(&self->allocator, path_stack); + c_Allocator_Free(&self->allocator, output_stack); + + return C_SUCCESS; +} + +void c_DirectedEulerianCycle_Destroy(c_DirectedEulerianCycle_t* self) { + if (!self) return; + c_VertexIdList_Destroy(&self->cycle); +} + +c_err_t c_DirectedEulerianCycle_GetCycle(c_DirectedEulerianCycle_t* self, c_VertexIdList_t* out_cycle) { + if (!self || !out_cycle) return C_ERR_PARAM; + if (!c_DirectedEulerianCycle_HasCycle(self)) return C_ERR_FAIL; + + c_size_t size = (c_size_t)c_VertexIdList_GetSize(&self->cycle); + for (c_size_t i = 0; i < size; ++i) { + c_uint_t val = 0; + c_err_t err = c_VertexIdList_Get((c_VertexIdList_t*)&self->cycle, i, &val); + if (err == C_SUCCESS) { + c_err_t app_err = c_VertexIdList_Append(out_cycle, val); + if (app_err != C_SUCCESS) return app_err; + } + } + return C_SUCCESS; +} diff --git a/Graph/c_DirectedEulerianCycle.h b/Graph/c_DirectedEulerianCycle.h new file mode 100644 index 0000000..e7a28f3 --- /dev/null +++ b/Graph/c_DirectedEulerianCycle.h @@ -0,0 +1,51 @@ +#ifndef INCLUDED_C_DIRECTEDEULERIANCYCLE_H +#define INCLUDED_C_DIRECTEDEULERIANCYCLE_H + +#ifndef INCLUDED_C_DIGRAPH_H +#include +#endif /*INCLUDED_C_DIGRAPH_H*/ + + +#ifndef INCLUDED_C_VERTEXIDLIST_H +#include +#endif /*INCLUDED_C_VERTEXIDLIST_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_VertexIdList_t cycle; /* Stores the Eulerian cycle path sequence if found */ + c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ +} c_DirectedEulerianCycle_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Computes a directed Eulerian cycle in a digraph, if one exists. + * @param allocator Explicit allocator context used to allocate internal routing arrays + */ +c_err_t c_DirectedEulerianCycle_Init(c_DirectedEulerianCycle_t* self, c_Digraph_t* graph, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers + */ +void c_DirectedEulerianCycle_Destroy(c_DirectedEulerianCycle_t* self); + +/** + * @brief Does the digraph have a directed Eulerian cycle? + */ +C_STATIC_FORCE_INLINE c_bool_t c_DirectedEulerianCycle_HasCycle(c_DirectedEulerianCycle_t* self) { + if (!self) return C_FALSE; + return !c_VertexIdList_IsEmpty(&self->cycle); +} + +/** + * @brief Gets the vertices on the directed Eulerian cycle. + * @param out_cycle An initialized c_VertexIdList_t container to collect the sequence. + */ +c_err_t c_DirectedEulerianCycle_GetCycle(c_DirectedEulerianCycle_t* self, c_VertexIdList_t* out_cycle); + + +#endif /*INCLUDED_C_DIRECTEDEULERIANCYCLE_H*/ diff --git a/Graph/c_DirectedEulerianCycle.t.c b/Graph/c_DirectedEulerianCycle.t.c new file mode 100644 index 0000000..b2de5c3 --- /dev/null +++ b/Graph/c_DirectedEulerianCycle.t.c @@ -0,0 +1,59 @@ +#include "c_Test.h" +#include "c_Digraph.h" +#include "c_DirectedEulerianCycle.h" +#include "c_VertexIdList.h" + +TEST_CASE(test_directed_eulerian_cycle) { + c_Digraph_t g; + c_Digraph_Init(&g, 4, NULL); + + /* Construct a simple complete Eulerian directed graph: + * 0 -> 1 -> 2 -> 0 + * 2 -> 3 -> 2 (nested cycle hook) + */ + c_Digraph_AddEdge(&g, 0, 1); + c_Digraph_AddEdge(&g, 1, 2); + c_Digraph_AddEdge(&g, 2, 0); + c_Digraph_AddEdge(&g, 2, 3); + c_Digraph_AddEdge(&g, 3, 2); + + c_DirectedEulerianCycle_t eulerian; + c_err_t err = c_DirectedEulerianCycle_Init(&eulerian, &g, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Assert an Eulerian path cycle tour is verified */ + ASSERT_TRUE(c_DirectedEulerianCycle_HasCycle(&eulerian)); + + c_VertexIdList_t tour_output; + c_VertexIdList_Init(&tour_output, 0, NULL); + + err = c_DirectedEulerianCycle_GetCycle(&eulerian, &tour_output); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Total path sequence elements count must equal exact E + 1 (5 edges + 1 = 6 steps) */ + c_size_t cycle_steps = (c_size_t)c_VertexIdList_GetSize(&tour_output); + ASSERT_LL_EQ(6, cycle_steps); + + /* First and last step points must align to form a valid complete tour loop boundary */ + c_uint_t start_v = 0; + c_uint_t end_v = 0; + c_VertexIdList_Get(&tour_output, 0, &start_v); + c_VertexIdList_Get(&tour_output, cycle_steps - 1, &end_v); + ASSERT_LL_EQ(start_v, end_v); + + c_VertexIdList_Destroy(&tour_output); + c_DirectedEulerianCycle_Destroy(&eulerian); + c_Digraph_Destroy(&g); +} + +int main(int argc, char** argv){ + + TEST_START(Component Tests); + + // Execution list configurations + RUN_TEST(test_directed_eulerian_cycle); + + TEST_REPORT(); + + RETURN_TEST_STATUS; +} \ No newline at end of file diff --git a/Graph/c_DirectedEulerianPath.c b/Graph/c_DirectedEulerianPath.c new file mode 100644 index 0000000..bc5f03f --- /dev/null +++ b/Graph/c_DirectedEulerianPath.c @@ -0,0 +1,138 @@ +#include +#include "c_NonrecursiveDirectedDFS.h" + +c_err_t c_DirectedEulerianPath_Init(c_DirectedEulerianPath_t* self, c_Digraph_t* graph, c_Allocator_t* allocator) { + if (!self || !graph) return C_ERR_PARAM; + + self->allocator = allocator ? *allocator : c_DefaultAllocator; + c_VertexIdList_Init(&self->path, 0, allocator); + + /* Edge case: An empty graph with no edges trivially has an empty path */ + if (graph->E == 0) return C_SUCCESS; + + /* 1. Degree tracking analysis to identify the unique start node candidate */ + c_size_t start_vertex = graph->V; + c_size_t end_vertex = graph->V; + c_size_t start_nodes_count = 0; + c_size_t end_nodes_count = 0; + c_size_t fallback_start = graph->V; + + for (c_size_t v = 0; v < graph->V; ++v) { + c_size_t out_deg = c_Digraph_GetOutDegree(graph, v); + c_size_t in_deg = c_Digraph_GetInDegree(graph, v); + + if (out_deg > 0 && fallback_start == graph->V) { + fallback_start = v; /* Used if the graph is a full cycle loop */ + } + + if (out_deg > in_deg) { + if (out_deg - in_deg > 1) return C_SUCCESS; /* Fails condition: open paths allow a max delta of 1 */ + start_vertex = v; + start_nodes_count++; + } else if (in_deg > out_deg) { + if (in_deg - out_deg > 1) return C_SUCCESS; + end_vertex = v; + end_nodes_count++; + } + } + + /* Validate structural structural configuration cases */ + if (start_nodes_count == 0 && end_nodes_count == 0) { + /* Case A: It's an Eulerian Cycle, choose the first non-isolated node as start */ + start_vertex = fallback_start; + } else if (start_nodes_count == 1 && end_nodes_count == 1) { + /* Case B: It's an open Eulerian Path from start_vertex to end_vertex */ + // start_vertex is already correctly captured here + } else { + return C_SUCCESS; /* Fails degree criteria layout, return with empty path */ + } + + if (start_vertex == graph->V) return C_SUCCESS; + + /* 2. Strong connectivity check over edge elements using NonrecursiveDFS */ + c_NonrecursiveDirectedDFS_t dfs; + c_err_t err = c_NonrecursiveDirectedDFS_Init(&dfs, graph, start_vertex, allocator); + if (err != C_SUCCESS) return err; + + for (c_size_t v = 0; v < graph->V; ++v) { + if (c_Digraph_GetOutDegree(graph, v) > 0 && !c_NonrecursiveDirectedDFS_HasPathTo(&dfs, v, graph->V)) { + c_NonrecursiveDirectedDFS_Destroy(&dfs); + return C_SUCCESS; /* Edges are fragmented in isolated structures */ + } + } + c_NonrecursiveDirectedDFS_Destroy(&dfs); + + /* 3. Hierholzer's Iterative Engine Routine */ + c_size_t* edge_index_iterator = (c_size_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_size_t)); + if (!edge_index_iterator) return C_ERR_NOMEM; + + c_size_t max_stack_cap = graph->E + 1; + c_size_t* path_stack = (c_size_t*)c_Allocator_Calloc(&self->allocator, max_stack_cap, sizeof(c_size_t)); + if (!path_stack) { + c_Allocator_Free(&self->allocator, edge_index_iterator); + return C_ERR_NOMEM; + } + + c_size_t* output_stack = (c_size_t*)c_Allocator_Calloc(&self->allocator, max_stack_cap, sizeof(c_size_t)); + if (!output_stack) { + c_Allocator_Free(&self->allocator, edge_index_iterator); + c_Allocator_Free(&self->allocator, path_stack); + return C_ERR_NOMEM; + } + + c_size_t path_stack_size = 0; + c_size_t output_stack_size = 0; + + path_stack[path_stack_size++] = start_vertex; + + while (path_stack_size > 0) { + c_size_t curr_v = path_stack[path_stack_size - 1]; + c_AdjList_t* adj = &graph->adj_list[curr_v]; + c_size_t out_degree = (c_size_t)c_UIntArray_GetSize(adj); + + if (edge_index_iterator[curr_v] < out_degree) { + c_uint_t target_w = 0; + err = c_UIntArray_Get(adj, edge_index_iterator[curr_v], &target_w); + edge_index_iterator[curr_v]++; /* Consume edge track */ + + if (err == C_SUCCESS) { + path_stack[path_stack_size++] = (c_size_t)target_w; + } + } else { + output_stack[output_stack_size++] = curr_v; + path_stack_size--; + } + } + + /* Unwind tracking queue directly to the VertexIdList matching forward travel steps */ + while (output_stack_size > 0) { + c_VertexIdList_Append(&self->path, (c_uint_t)output_stack[--output_stack_size]); + } + + c_Allocator_Free(&self->allocator, edge_index_iterator); + c_Allocator_Free(&self->allocator, path_stack); + c_Allocator_Free(&self->allocator, output_stack); + + return C_SUCCESS; +} + +void c_DirectedEulerianPath_Destroy(c_DirectedEulerianPath_t* self) { + if (!self) return; + c_VertexIdList_Destroy(&self->path); +} + +c_err_t c_DirectedEulerianPath_GetPath(c_DirectedEulerianPath_t* self, c_VertexIdList_t* out_path) { + if (!self || !out_path) return C_ERR_PARAM; + if (!c_DirectedEulerianPath_HasPath(self)) return C_ERR_FAIL; + + c_size_t size = (c_size_t)c_VertexIdList_GetSize(&self->path); + for (c_size_t i = 0; i < size; ++i) { + c_uint_t val = 0; + c_err_t err = c_VertexIdList_Get((c_VertexIdList_t*)&self->path, i, &val); + if (err == C_SUCCESS) { + c_err_t app_err = c_VertexIdList_Append(out_path, val); + if (app_err != C_SUCCESS) return app_err; + } + } + return C_SUCCESS; +} diff --git a/Graph/c_DirectedEulerianPath.h b/Graph/c_DirectedEulerianPath.h new file mode 100644 index 0000000..d485300 --- /dev/null +++ b/Graph/c_DirectedEulerianPath.h @@ -0,0 +1,52 @@ +#ifndef INCLUDED_C_DIRECTEDEULERIANPATH_H +#define INCLUDED_C_DIRECTEDEULERIANPATH_H + +#ifndef INCLUDED_C_DIGRAPH_H +#include +#endif /*INCLUDED_C_DIGRAPH_H*/ + + +#ifndef INCLUDED_C_VERTEXIDLIST_H +#include +#endif /*INCLUDED_C_VERTEXIDLIST_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_VertexIdList_t path; /* Stores the Eulerian path sequence if found */ + c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ +} c_DirectedEulerianPath_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Computes a directed Eulerian path in a digraph, if one exists. + * @param allocator Explicit allocator context used to allocate internal routing arrays + */ +c_err_t c_DirectedEulerianPath_Init(c_DirectedEulerianPath_t* self, c_Digraph_t* graph, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers + */ +void c_DirectedEulerianPath_Destroy(c_DirectedEulerianPath_t* self); + +/** + * @brief Does the digraph have a directed Eulerian path? + */ +C_STATIC_FORCE_INLINE +c_bool_t c_DirectedEulerianPath_HasPath(c_DirectedEulerianPath_t* self) { + if (!self) return C_FALSE; + return !c_VertexIdList_IsEmpty(&self->path); +} + +/** + * @brief Gets the vertices on the directed Eulerian path. + * @param out_path An initialized c_VertexIdList_t container to collect the sequence. + */ +c_err_t c_DirectedEulerianPath_GetPath(c_DirectedEulerianPath_t* self, c_VertexIdList_t* out_path); + + +#endif /*INCLUDED_C_DIRECTEDEULERIANPATH_H*/ diff --git a/Graph/c_DirectedEulerianPath.t.c b/Graph/c_DirectedEulerianPath.t.c new file mode 100644 index 0000000..0c9a2e5 --- /dev/null +++ b/Graph/c_DirectedEulerianPath.t.c @@ -0,0 +1,71 @@ +#include "c_Test.h" +#include "c_Digraph.h" +#include "c_DirectedEulerianPath.h" +#include "c_VertexIdList.h" + +TEST_CASE(test_directed_eulerian_path_open) { + c_Digraph_t g; + c_Digraph_Init(&g, 4, NULL); + + /* Construct an open Eulerian path: 0 -> 1 -> 2 -> 3 -> 1 + * Start vertex: 0 (Out=1, In=0) + * End vertex: 1 (Out=1, In=2) -- wait, let's fix the loop to be valid: + * 0 -> 1 + * 1 -> 2 + * 2 -> 3 + * 3 -> 1 + * Degrees calculation: + * v=0: Out=1, In=0 (Start node!) + * v=1: Out=1, In=2 + * v=2: Out=1, In=1 + * v=3: Out=1, In=1 + * Wait, v=1 has Out=1, In=2, which means delta is 1 (In > Out), so v=1 is the unique end node. + * Let's trace edges: (0,1), (1,2), (2,3), (3,1). Total edges = 4. + * Every single edge visited exactly once. Valid path! + */ + c_Digraph_AddEdge(&g, 0, 1); + c_Digraph_AddEdge(&g, 1, 2); + c_Digraph_AddEdge(&g, 2, 3); + c_Digraph_AddEdge(&g, 3, 1); + + c_DirectedEulerianPath_t eulerian; + c_err_t err = c_DirectedEulerianPath_Init(&eulerian, &g, 0); + ASSERT_INT_EQ(C_SUCCESS, err); + + ASSERT_TRUE(c_DirectedEulerianPath_HasPath(&eulerian)); + + c_VertexIdList_t path_output; + c_VertexIdList_Init(&path_output, 0, NULL); + + err = c_DirectedEulerianPath_GetPath(&eulerian, &path_output); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Total path sequence elements count must equal exact E + 1 (4 edges + 1 = 5 steps) */ + c_size_t path_steps = (c_size_t)c_VertexIdList_GetSize(&path_output); + ASSERT_LL_EQ(5, path_steps); + + /* Confirm correct endpoints sequence values */ + c_uint_t start_v = 0; + c_uint_t end_v = 0; + c_VertexIdList_Get(&path_output, 0, &start_v); + c_VertexIdList_Get(&path_output, path_steps - 1, &end_v); + + ASSERT_LL_EQ(0, start_v); /* Should start at 0 */ + ASSERT_LL_EQ(1, end_v); /* Should terminate at 1 */ + + c_VertexIdList_Destroy(&path_output); + c_DirectedEulerianPath_Destroy(&eulerian); + c_Digraph_Destroy(&g); +} + +int main(int argc, char** argv){ + + TEST_START(Component Tests); + + // Execution list configurations + RUN_TEST(test_directed_eulerian_path_open); + + TEST_REPORT(); + + RETURN_TEST_STATUS; +} \ No newline at end of file diff --git a/Graph/c_Edge.c b/Graph/c_Edge.c new file mode 100644 index 0000000..d7d9bc4 --- /dev/null +++ b/Graph/c_Edge.c @@ -0,0 +1 @@ +#include diff --git a/Graph/c_Edge.h b/Graph/c_Edge.h new file mode 100644 index 0000000..7bb8706 --- /dev/null +++ b/Graph/c_Edge.h @@ -0,0 +1,36 @@ +#ifndef INCLUDED_C_EDGE_H +#define INCLUDED_C_EDGE_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_size_t v; /* One endpoint vertex */ + c_size_t w; /* The other endpoint vertex */ + double weight; /* Edge weight value */ +} c_Edge_t; + +#define C_EDGE_SENTINEL ((c_size_t)-1) + +/* Inline helper methods for c_Edge_t */ +C_STATIC_FORCE_INLINE +c_size_t c_Edge_Either(const c_Edge_t* edge) { + return edge ? edge->v : 0; +} + +C_STATIC_FORCE_INLINE +c_size_t c_Edge_Other(const c_Edge_t* edge, c_size_t v) { + if (!edge) return 0; + return (edge->v == v) ? edge->w : edge->v; +} + +C_STATIC_FORCE_INLINE double c_Edge_Weight(const c_Edge_t* edge) { + return edge ? edge->weight : 0.0; +} + +#endif /*INCLUDED_C_EDGE_H*/ diff --git a/Graph/c_EdgeIdList.c b/Graph/c_EdgeIdList.c new file mode 100644 index 0000000..62ce324 --- /dev/null +++ b/Graph/c_EdgeIdList.c @@ -0,0 +1 @@ +#include diff --git a/Graph/c_EdgeIdList.h b/Graph/c_EdgeIdList.h new file mode 100644 index 0000000..3d5592e --- /dev/null +++ b/Graph/c_EdgeIdList.h @@ -0,0 +1,32 @@ +#ifndef INCLUDED_C_EDGEIDLIST_H +#define INCLUDED_C_EDGEIDLIST_H + +#ifndef INCLUDED_C_UINTARRAY_H +#include +#endif /*INCLUDED_C_UINTARRAY_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + + +typedef c_UIntArray_t c_EdgeIdList_t; + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +#define c_EdgeIdList_Init c_UIntArray_Init +#define c_EdgeIdList_Destroy c_UIntArray_Destroy +#define c_EdgeIdList_Resize c_UIntArray_Resize +#define c_EdgeIdList_Append c_UIntArray_Append +#define c_EdgeIdList_Set c_UIntArray_Set +#define c_EdgeIdList_Get c_UIntArray_Get +#define c_EdgeIdList_Remove c_UIntArray_Remove +#define c_EdgeIdList_IsEmpty c_UIntArray_IsEmpty +#define c_EdgeIdList_GetSize c_UIntArray_GetSize +#define c_EdgeIdList_Copy c_UIntArray_Copy + + +#endif /*INCLUDED_C_EDGEIDLIST_H*/ diff --git a/Graph/c_EdgeWeightedDigraph.c b/Graph/c_EdgeWeightedDigraph.c new file mode 100644 index 0000000..2abe62c --- /dev/null +++ b/Graph/c_EdgeWeightedDigraph.c @@ -0,0 +1,161 @@ +#include + + +c_err_t c_EdgeWeightedDigraph_Init(c_EdgeWeightedDigraph_t* self, c_size_t V, c_Allocator_t* alloc) { + if (!self) return C_ERR_PARAM; + + self->allocator = alloc ? *alloc : c_DefaultAllocator; + self->V = V; + self->E = 0; + self->adj_list = NULL; + self->edges_pool = NULL; + self->edges_cap = 0; + self->indegree = NULL; + + if (V > 0) { + /* Allocate dynamic adjacency list managers matching V size */ + self->adj_list = c_Allocator_Calloc(&self->allocator, V, sizeof(c_AdjList_t)); + if (!self->adj_list) return C_ERR_NOMEM; + + for (c_size_t i = 0; i < V; ++i) { + c_AdjList_Init(&self->adj_list[i],0, alloc); + } + + /* Allocate the specialized O(1) in-degree cache array */ + self->indegree = (c_size_t*)c_Allocator_Calloc(&self->allocator, V, sizeof(c_size_t)); + if (!self->indegree) { + c_Allocator_Free(&self->allocator, self->adj_list); + self->adj_list = NULL; + return C_ERR_NOMEM; + } + } + + return C_SUCCESS; +} + + +void c_EdgeWeightedDigraph_Destroy(c_EdgeWeightedDigraph_t* self) { + if (!self) return; + + if (self->adj_list) { + for (c_size_t i = 0; i < self->V; ++i) { + c_UIntArray_Destroy(&self->adj_list[i]); + } + c_Allocator_Free(&self->allocator, self->adj_list); + self->adj_list = NULL; + } + + if (self->edges_pool) { + c_Allocator_Free(&self->allocator, self->edges_pool); + self->edges_pool = NULL; + } + + if (self->indegree) { + c_Allocator_Free(&self->allocator, self->indegree); + self->indegree = NULL; + } + + self->V = 0; + self->E = 0; + self->edges_cap = 0; + c_Allocator_Destroy(&self->allocator); +} + +c_err_t c_EdgeWeightedDigraph_AddEdge(c_EdgeWeightedDigraph_t* self, c_size_t from, c_size_t to, double weight) { + if (!self || from >= self->V || to >= self->V) return C_ERR_PARAM; + + /* 1. Ensure the global directed edge pool has sufficient capacity */ + if (self->E >= self->edges_cap) { + c_size_t old_cap = self->edges_cap; + c_size_t new_cap = (old_cap == 0) ? 4 : (old_cap * 2); + + c_DirectedEdge_t* new_pool = (c_DirectedEdge_t*)c_Allocator_Realloc( + &self->allocator, + self->edges_pool, + old_cap * sizeof(c_DirectedEdge_t), + new_cap * sizeof(c_DirectedEdge_t) + ); + if (!new_pool) return C_ERR_NOMEM; + + self->edges_pool = new_pool; + self->edges_cap = new_cap; + } + + /* 2. Populate the edge metadata inside the pool slot */ + c_size_t edge_id = self->E; + self->edges_pool[edge_id] = (c_DirectedEdge_t){ .from = from, .to = to, .weight = weight }; + + /* 3. Append the edge_id reference to the source vertex's adjacency out-list */ + c_err_t err = c_AdjList_Append(&self->adj_list[from], (c_uint_t)edge_id); + if (err != C_SUCCESS) return err; + + /* 4. Update the cached in-degree tracking array counter at the destination vertex */ + self->indegree[to]++; + self->E++; + + return C_SUCCESS; +} + +c_err_t c_EdgeWeightedDigraph_GetEdge(c_EdgeWeightedDigraph_t* self, c_size_t edge_idx, c_DirectedEdge_t* edge) { + if (!self ) return C_ERR_PARAM; + + /* Strict firewall boundary validation against the total allocated edge count */ + if (edge_idx >= self->E) return C_ERR_OUTOFBOUND; + + /* Execute a fast bitwise block-copy of the edge object contents */ + if (edge) { + *edge = self->edges_pool[edge_idx]; + } + + return C_SUCCESS; +} + + +c_err_t c_EdgeWeightedDigraph_RemoveEdge(c_EdgeWeightedDigraph_t* self, c_size_t edge_id) { + if (!self) return C_ERR_PARAM; + + /* 1. Bounds check firewall validation */ + if (edge_id >= self->E) return C_ERR_OUTOFBOUND; + + c_DirectedEdge_t* edge = &self->edges_pool[edge_id]; + + /* If the edge is already soft-deleted, skip to avoid double decrement corruptions */ + if (edge->from == C_DIRECTED_EDGE_SENTINEL) return C_SUCCESS; + + c_size_t from_vertex = edge->from; + c_size_t to_vertex = edge->to; + + /* 2. Locate and purge the edge_id reference out from the source's adj_list */ + c_UIntArray_t* adj = &self->adj_list[from_vertex]; + c_size_t adj_size = (c_size_t)c_UIntArray_GetSize(adj); + c_bool_t found = C_FALSE; + + for (c_size_t i = 0; i < adj_size; ++i) { + c_uint_t generic_id = 0; + if (c_UIntArray_Get(adj, i, &generic_id) == C_SUCCESS) { + if ((c_size_t)generic_id == edge_id) { + /* Assuming your modular c_UIntArray layout supports safe positional removals */ + c_UIntArray_Remove(adj, i); + found = C_TRUE; + break; + } + } + } + + /* 3. Finalize accounting updates if the target link was successfully detached */ + if (found) { + /* Soft-delete the pool metadata content attributes */ + edge->from = C_DIRECTED_EDGE_SENTINEL; + edge->to = C_DIRECTED_EDGE_SENTINEL; + edge->weight = 0.0; + + /* Decrement your O(1) optimized in-degree tracker */ + if (self->indegree[to_vertex] > 0) { + self->indegree[to_vertex]--; + } + } else { + return C_ERR_NOTFOUND; + } + + return C_SUCCESS; +} diff --git a/Graph/c_EdgeWeightedDigraph.h b/Graph/c_EdgeWeightedDigraph.h new file mode 100644 index 0000000..ded9aa3 --- /dev/null +++ b/Graph/c_EdgeWeightedDigraph.h @@ -0,0 +1,78 @@ +#ifndef INCLUDED_C_EDGEWEIGHTEDDIGRAPH_H +#define INCLUDED_C_EDGEWEIGHTEDDIGRAPH_H + +#ifndef INCLUDED_C_DIRECTEDEDGE_H +#include +#endif /*INCLUDED_C_DIRECTEDEDGE_H*/ + + +#ifndef INCLUDED_C_ADJLIST_H +#include +#endif /*INCLUDED_C_ADJLIST_H*/ + +#ifndef INCLUDED_C_ALLOCATOR_H +#include +#endif /*INCLUDED_C_ALLOCATOR_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_size_t V; /* Total number of vertices */ + c_size_t E; /* Total number of edges */ + c_AdjList_t* adj_list; /* Array of dynamic lists storing global edge_ids exiting each vertex */ + c_DirectedEdge_t* edges_pool; /* Global contiguous array pool housing raw directed edge objects */ + c_size_t edges_cap; /* Allocated edge pool tracking capacity boundary */ + c_size_t* indegree; /* O(1) in-degree array cache for runtime optimization */ + c_Allocator_t allocator; /* Embedded custom allocator structure instance */ +} c_EdgeWeightedDigraph_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +/** + * @brief Initializes an empty edge-weighted digraph with V vertices. + * @param allocator Explicit allocator context used to configure memory regions + */ +c_err_t c_EdgeWeightedDigraph_Init(c_EdgeWeightedDigraph_t* self, c_size_t V, c_Allocator_t* alloc); + +/** + * @brief Destroys the digraph and releases all inner heap allocations safely. + */ +void c_EdgeWeightedDigraph_Destroy(c_EdgeWeightedDigraph_t* self); + +/** + * @brief Adds a directed weighted edge from vertex 'from' to vertex 'to'. + */ +c_err_t c_EdgeWeightedDigraph_AddEdge(c_EdgeWeightedDigraph_t* self, c_size_t from, c_size_t to, double weight); + + +c_err_t c_EdgeWeightedDigraph_GetEdge(c_EdgeWeightedDigraph_t* self, c_size_t edge_idx, c_DirectedEdge_t* edge); + +c_err_t c_EdgeWeightedDigraph_RemoveEdge(c_EdgeWeightedDigraph_t* self, c_size_t edge_id); + +/* ================================================================================================================== */ +/* Inline Inspection Hooks */ + +C_STATIC_FORCE_INLINE c_size_t c_EdgeWeightedDigraph_GetV(c_EdgeWeightedDigraph_t* self) { + return self ? self->V : 0; +} + +C_STATIC_FORCE_INLINE c_size_t c_EdgeWeightedDigraph_GetE(c_EdgeWeightedDigraph_t* self) { + return self ? self->E : 0; +} + +C_STATIC_FORCE_INLINE c_size_t c_EdgeWeightedDigraph_GetOutDegree(c_EdgeWeightedDigraph_t* self, c_size_t v) { + if (!self || v >= self->V) return 0; + return (c_size_t)c_AdjList_GetSize(&self->adj_list[v]); +} + +C_STATIC_FORCE_INLINE c_size_t c_EdgeWeightedDigraph_GetInDegree(c_EdgeWeightedDigraph_t* self, c_size_t v) { + if (!self || v >= self->V) return 0; + return self->indegree[v]; +} + + +#endif /*INCLUDED_C_EDGEWEIGHTEDDIGRAPH_H*/ diff --git a/Graph/c_EdgeWeightedDigraph.t.c b/Graph/c_EdgeWeightedDigraph.t.c new file mode 100644 index 0000000..b5e69ed --- /dev/null +++ b/Graph/c_EdgeWeightedDigraph.t.c @@ -0,0 +1,77 @@ +#include "c_Test.h" +#include "c_EdgeWeightedDigraph.h" + +TEST_CASE(test_edge_weighted_digraph_plumbing) { + c_EdgeWeightedDigraph_t graph; + c_err_t err = c_EdgeWeightedDigraph_Init(&graph, 3, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + ASSERT_LL_EQ(3, c_EdgeWeightedDigraph_GetV(&graph)); + ASSERT_LL_EQ(0, c_EdgeWeightedDigraph_GetE(&graph)); + + /* Inject directed edges: 0 -> 1 (W: 1.5) and 0 -> 2 (W: 4.2) */ + err = c_EdgeWeightedDigraph_AddEdge(&graph, 0, 1, 1.5); ASSERT_INT_EQ(C_SUCCESS, err); + err = c_EdgeWeightedDigraph_AddEdge(&graph, 0, 2, 4.2); ASSERT_INT_EQ(C_SUCCESS, err); + + ASSERT_LL_EQ(2, c_EdgeWeightedDigraph_GetE(&graph)); + + /* Validate O(1) degree cache outputs */ + ASSERT_LL_EQ(2, c_EdgeWeightedDigraph_GetOutDegree(&graph, 0)); + ASSERT_LL_EQ(0, c_EdgeWeightedDigraph_GetInDegree(&graph, 0)); + ASSERT_LL_EQ(1, c_EdgeWeightedDigraph_GetInDegree(&graph, 1)); + ASSERT_LL_EQ(1, c_EdgeWeightedDigraph_GetInDegree(&graph, 2)); + + /* Inspect outbound connections from vertex index 0 */ + c_AdjList_t* zero_adj = &graph.adj_list[0]; + ASSERT_INT_EQ(2, c_AdjList_GetSize(zero_adj)); + + c_uint_t first_edge_id = 0; + err = c_AdjList_Get(zero_adj, 0, &first_edge_id); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Map back to the edge pool item row to verify its integrity */ + c_DirectedEdge_t* edge1 = &graph.edges_pool[(c_size_t)first_edge_id]; + ASSERT_LL_EQ(0, c_DirectedEdge_From(edge1)); + ASSERT_LL_EQ(1, c_DirectedEdge_To(edge1)); + ASSERT_DOUBLE_EQ_MSG(1.5, c_DirectedEdge_Weight(edge1), "Directed weight match failed"); + + c_EdgeWeightedDigraph_Destroy(&graph); +} + +TEST_CASE(test_edge_weighted_digraph_soft_remove) { + c_EdgeWeightedDigraph_t graph; + c_err_t err = c_EdgeWeightedDigraph_Init(&graph, 3, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Inject directed edges: 0 -> 1 [Edge ID: 0] and 0 -> 2 [Edge ID: 1] */ + c_EdgeWeightedDigraph_AddEdge(&graph, 0, 1, 1.5); + c_EdgeWeightedDigraph_AddEdge(&graph, 0, 2, 4.2); + + ASSERT_LL_EQ(2, c_EdgeWeightedDigraph_GetE(&graph)); + ASSERT_LL_EQ(1, c_EdgeWeightedDigraph_GetInDegree(&graph, 1)); + ASSERT_LL_EQ(2, c_EdgeWeightedDigraph_GetOutDegree(&graph, 0)); + + /* Remove edge 0 (0 -> 1) */ + err = c_EdgeWeightedDigraph_RemoveEdge(&graph, 0); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Verify that the adjacency list and degree caches accurately reflected the change */ + ASSERT_LL_EQ(1, c_EdgeWeightedDigraph_GetOutDegree(&graph, 0)); + ASSERT_LL_EQ(0, c_EdgeWeightedDigraph_GetInDegree(&graph, 1)); /* Should drop down to 0 */ + ASSERT_LL_EQ(1, c_EdgeWeightedDigraph_GetInDegree(&graph, 2)); /* Edge 1 remains intact */ + + /* Ensure bounds boundary checks prevent out-of-bounds corruption */ + err = c_EdgeWeightedDigraph_RemoveEdge(&graph, 99); + ASSERT_INT_EQ(C_ERR_OUTOFBOUND, err); + + c_EdgeWeightedDigraph_Destroy(&graph); +} + + +int main(void) { + TEST_START(EdgeWeightedDigraph_Plumbing_Suite); + RUN_TEST(test_edge_weighted_digraph_plumbing); + RUN_TEST(test_edge_weighted_digraph_soft_remove); + TEST_REPORT(); + RETURN_TEST_STATUS; +} diff --git a/Graph/c_EdgeWeightedDirectedCycle.c b/Graph/c_EdgeWeightedDirectedCycle.c new file mode 100644 index 0000000..8147c7d --- /dev/null +++ b/Graph/c_EdgeWeightedDirectedCycle.c @@ -0,0 +1,145 @@ +#include + + +#define CYCLE_SENTINEL ((c_size_t)-1) + +typedef struct { + c_size_t v; /* Current vertex index */ + c_size_t edge_idx; /* Next neighbor index to pull from adjacency out-list */ +} c_DigraphCycleFrame_t; + +/* Private non-recursive DFS machine loop processor */ +static void c_EdgeWeightedDirectedCycle_Process(c_EdgeWeightedDirectedCycle_t* self, c_EdgeWeightedDigraph_t* graph, c_size_t root, c_DigraphCycleFrame_t* frame_stack) { + c_size_t stack_size = 0; + + /* Push initial root activation block onto structural runtime stack */ + self->marked[root] = C_TRUE; + self->on_stack[root] = C_TRUE; + frame_stack[stack_size++] = (c_DigraphCycleFrame_t){ .v = root, .edge_idx = 0 }; + + while (stack_size > 0) { + c_DigraphCycleFrame_t* current_frame = &frame_stack[stack_size - 1]; + c_size_t u = current_frame->v; + + c_UIntArray_t* adj = &graph->adj_list[u]; + c_size_t neighbor_count = (c_size_t)c_UIntArray_GetSize(adj); + + c_bool_t advanced = C_FALSE; + while (current_frame->edge_idx < neighbor_count) { + c_uint_t generic_edge_id = 0; + c_err_t err = c_UIntArray_Get(adj, current_frame->edge_idx, &generic_edge_id); + current_frame->edge_idx++; /* Advance out-list pointer position */ + + if (err == C_SUCCESS) { + c_size_t edge_id = (c_size_t)generic_edge_id; + c_size_t w = graph->edges_pool[edge_id].to; + + /* Case A: Encountered an unvisited node, simulate recursive call descent */ + if (!self->marked[w]) { + self->marked[w] = C_TRUE; + self->on_stack[w] = C_TRUE; + self->edge_to[w] = edge_id; + + frame_stack[stack_size++] = (c_DigraphCycleFrame_t){ .v = w, .edge_idx = 0 }; + advanced = C_TRUE; + break; + } + /* Case B: Backedge detected (target node is currently alive on stack) -> Cycle Trapped! */ + else if (self->on_stack[w]) { + c_size_t* reverse_stack = (c_size_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_size_t)); + if (!reverse_stack) return; + + c_size_t trace_size = 0; + reverse_stack[trace_size++] = edge_id; /* Seal final back edge closing the loop */ + + /* Trace backward using edge origin nodes until reaching intersection w */ + c_size_t curr_v = u; + while (curr_v != w) { + c_size_t prev_edge_id = self->edge_to[curr_v]; + if (prev_edge_id == CYCLE_SENTINEL) break; + + reverse_stack[trace_size++] = prev_edge_id; + curr_v = graph->edges_pool[prev_edge_id].from; + } + + /* Append elements forward into persistent container matching execution path */ + while (trace_size > 0) { + c_VertexIdList_Append(&self->cycle, (c_uint_t)reverse_stack[--trace_size]); + } + + c_Allocator_Free(&self->allocator, reverse_stack); + return; /* Return instantly to stop unnecessary processing */ + } + } + } + + /* Short-circuit bubbling up if an evaluation path already verified a cycle */ + if (c_EdgeWeightedDirectedCycle_HasCycle(self)) return; + + /* If all outbound avenues from node u are fully parsed, pop it from execution tracking */ + if (!advanced) { + self->on_stack[u] = C_FALSE; + stack_size--; + } + } +} + +c_err_t c_EdgeWeightedDirectedCycle_Init(c_EdgeWeightedDirectedCycle_t* self, c_EdgeWeightedDigraph_t* graph, c_Allocator_t* allocator) { + if (!self || !graph) return C_ERR_PARAM; + + self->allocator = allocator ? *allocator : c_DefaultAllocator; + c_VertexIdList_Init(&self->cycle, 0, allocator); + + if (graph->V == 0) return C_SUCCESS; + + self->marked = (c_bool_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_bool_t)); + self->edge_to = (c_size_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_size_t)); + self->on_stack = (c_bool_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_bool_t)); + + if (!self->marked || !self->edge_to || !self->on_stack) { + c_EdgeWeightedDirectedCycle_Destroy(self); + return C_ERR_NOMEM; + } + + for (c_size_t i = 0; i < graph->V; ++i) { + self->edge_to[i] = CYCLE_SENTINEL; + } + + /* Allocate continuous explicit compiler-emulated frame scratch buffer stack O(V) */ + c_DigraphCycleFrame_t* frame_stack = (c_DigraphCycleFrame_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_DigraphCycleFrame_t)); + if (!frame_stack) { + c_EdgeWeightedDirectedCycle_Destroy(self); + return C_ERR_NOMEM; + } + + /* Sweep disconnected structural pockets across graph nodes boundary safely */ + for (c_size_t v = 0; v < graph->V; ++v) { + if (!self->marked[v] && !c_EdgeWeightedDirectedCycle_HasCycle(self)) { + c_EdgeWeightedDirectedCycle_Process(self, graph, v, frame_stack); + } + } + + c_Allocator_Free(&self->allocator, frame_stack); + return C_SUCCESS; +} + +void c_EdgeWeightedDirectedCycle_Destroy(c_EdgeWeightedDirectedCycle_t* self) { + if (!self) return; + if (self->marked) c_Allocator_Free(&self->allocator, self->marked); + if (self->edge_to) c_Allocator_Free(&self->allocator, self->edge_to); + if (self->on_stack) c_Allocator_Free(&self->allocator, self->on_stack); + + c_VertexIdList_Destroy(&self->cycle); + + self->marked = NULL; + self->edge_to = NULL; + self->on_stack = NULL; +} + +c_err_t c_EdgeWeightedDirectedCycle_GetCycle(c_EdgeWeightedDirectedCycle_t* self, c_VertexIdList_t* out_cycle) { + if (!self || !out_cycle) return C_ERR_PARAM; + if (!c_EdgeWeightedDirectedCycle_HasCycle(self)) return C_ERR_FAIL; + + /* Copy pre-computed internal cycle sequence using high-speed block layout rules */ + return c_UIntArray_Copy(out_cycle, (c_UIntArray_t*)&self->cycle); +} diff --git a/Graph/c_EdgeWeightedDirectedCycle.h b/Graph/c_EdgeWeightedDirectedCycle.h new file mode 100644 index 0000000..98113c2 --- /dev/null +++ b/Graph/c_EdgeWeightedDirectedCycle.h @@ -0,0 +1,59 @@ +#ifndef INCLUDED_C_EDGEWEIGHTEDDIRECTEDCYCLE_H +#define INCLUDED_C_EDGEWEIGHTEDDIRECTEDCYCLE_H + + +#ifndef INCLUDED_C_EDGEWEIGHTEDDIGRAPH_H +#include +#endif /*INCLUDED_C_EDGEWEIGHTEDDIGRAPH_H*/ + + +#ifndef INCLUDED_C_VERTEXIDLIST_H +#include +#endif /*INCLUDED_C_VERTEXIDLIST_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_bool_t* marked; /* Visited tracking array (size of graph->V) */ + c_size_t* edge_to; /* edge_to[w] = global entering directed edge_id on path to w */ + c_bool_t* on_stack; /* Keeps track of vertices currently active on the emulated DFS stack */ + c_VertexIdList_t cycle; /* Stores the cycle edge_id sequence if a cycle is discovered */ + c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ +} c_EdgeWeightedDirectedCycle_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Non-recursively determines if an edge-weighted digraph contains a directed cycle. + * @param allocator Explicit allocator context used to configure internal tracking state memory + */ +c_err_t c_EdgeWeightedDirectedCycle_Init(c_EdgeWeightedDirectedCycle_t* self, c_EdgeWeightedDigraph_t* graph, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers safely. + */ +void c_EdgeWeightedDirectedCycle_Destroy(c_EdgeWeightedDirectedCycle_t* self); + +/** + * @brief Does the edge-weighted digraph contain a directed cycle? + */ +C_STATIC_FORCE_INLINE +c_bool_t c_EdgeWeightedDirectedCycle_HasCycle(c_EdgeWeightedDirectedCycle_t* self) { + if (!self) return C_FALSE; + return !c_VertexIdList_IsEmpty(&self->cycle); +} + +/** + * @brief Gets the collection of global directed edge IDs that form the detected cycle loop. + * @param out_cycle An initialized c_VertexIdList_t container to collect the edge sequence. + */ +c_err_t c_EdgeWeightedDirectedCycle_GetCycle(c_EdgeWeightedDirectedCycle_t* self, c_VertexIdList_t* out_cycle); + + + + + +#endif /*INCLUDED_C_EDGEWEIGHTEDDIRECTEDCYCLE_H*/ diff --git a/Graph/c_EdgeWeightedDirectedCycle.t.c b/Graph/c_EdgeWeightedDirectedCycle.t.c new file mode 100644 index 0000000..0f6c4d7 --- /dev/null +++ b/Graph/c_EdgeWeightedDirectedCycle.t.c @@ -0,0 +1,53 @@ +#include "c_Test.h" +#include "c_EdgeWeightedDigraph.h" +#include "c_EdgeWeightedDirectedCycle.h" +#include "c_VertexIdList.h" + +TEST_CASE(test_edge_weighted_directed_cycle_detection) { + c_EdgeWeightedDigraph_t g; + c_err_t err = c_EdgeWeightedDigraph_Init(&g, 3, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Construct a simple cyclic network loop topology: + * 0 -> 1 (Weight: 1.5) [Edge ID: 0] + * 1 -> 2 (Weight: 2.3) [Edge ID: 1] + * 2 -> 0 (Weight: 0.8) [Edge ID: 2] -> Closes the cycle + */ + c_EdgeWeightedDigraph_AddEdge(&g, 0, 1, 1.5); + c_EdgeWeightedDigraph_AddEdge(&g, 1, 2, 2.3); + c_EdgeWeightedDigraph_AddEdge(&g, 2, 0, 0.8); + + c_EdgeWeightedDirectedCycle_t detector; + err = c_EdgeWeightedDirectedCycle_Init(&detector, &g, &g.allocator); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Assert that a cycle is captured */ + ASSERT_TRUE(c_EdgeWeightedDirectedCycle_HasCycle(&detector)); + + c_VertexIdList_t extracted; + c_VertexIdList_Init(&extracted, 0, 0); + + err = c_EdgeWeightedDirectedCycle_GetCycle(&detector, &extracted); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* The loop cycle size must match exactly 3 edge elements */ + c_size_t cycle_size = (c_size_t)c_VertexIdList_GetSize(&extracted); + ASSERT_LL_EQ(3, cycle_size); + + /* Verify the extracted sequential edge tokens order matches forward travel steps */ + c_uint_t e0 = 0, e1 = 0, e2 = 0; + c_VertexIdList_Get(&extracted, 0, &e0); ASSERT_LL_EQ(0, e0); /* Edge 0 (0->1) */ + c_VertexIdList_Get(&extracted, 1, &e1); ASSERT_LL_EQ(1, e1); /* Edge 1 (1->2) */ + c_VertexIdList_Get(&extracted, 2, &e2); ASSERT_LL_EQ(2, e2); /* Edge 2 (2->0) */ + + c_VertexIdList_Destroy(&extracted); + c_EdgeWeightedDirectedCycle_Destroy(&detector); + c_EdgeWeightedDigraph_Destroy(&g); +} + +int main(void) { + TEST_START(EdgeWeightedDirectedCycle_Suite); + RUN_TEST(test_edge_weighted_directed_cycle_detection); + TEST_REPORT(); + RETURN_TEST_STATUS; +} diff --git a/Graph/c_EdgeWeightedGraph.c b/Graph/c_EdgeWeightedGraph.c new file mode 100644 index 0000000..9cd6cfe --- /dev/null +++ b/Graph/c_EdgeWeightedGraph.c @@ -0,0 +1,155 @@ +#include + +c_err_t c_EdgeWeightedGraph_Init(c_EdgeWeightedGraph_t* self, c_size_t V, c_Allocator_t* alloc) { + if (!self) return C_ERR_PARAM; + + self->allocator = alloc ? *alloc : c_DefaultAllocator; + self->V = V; + self->E = 0; + self->adj_list = NULL; + self->edges_pool = NULL; + self->edges_cap = 0; + + if (V > 0) { + /* Allocate dynamic adjacency list managers matching V size */ + self->adj_list = c_Allocator_Calloc(&self->allocator, V, sizeof(c_UIntArray_t)); + if (!self->adj_list) return C_ERR_NOMEM; + + for (c_size_t i = 0; i < V; ++i) { + c_AdjList_Init(&self->adj_list[i], 0, alloc); + } + } + + return C_SUCCESS; +} + +void c_EdgeWeightedGraph_Destroy(c_EdgeWeightedGraph_t* self) { + if (!self) return; + + if (self->adj_list) { + for (c_size_t i = 0; i < self->V; ++i) { + c_AdjList_Destroy(&self->adj_list[i]); + } + c_Allocator_Free(&self->allocator, self->adj_list); + self->adj_list = NULL; + } + + if (self->edges_pool) { + c_Allocator_Free(&self->allocator, self->edges_pool); + self->edges_pool = NULL; + } + + self->V = 0; + self->E = 0; + self->edges_cap = 0; +} + +c_err_t c_EdgeWeightedGraph_AddEdge(c_EdgeWeightedGraph_t* self, c_size_t v, c_size_t w, double weight) { + if (!self || v >= self->V || w >= self->V) return C_ERR_PARAM; + + /* 1. Ensure the global edge pool buffer has enough memory capacity */ + if (self->E >= self->edges_cap) { + c_size_t old_cap = self->edges_cap; + c_size_t new_cap = old_cap == 0 ? 4 : old_cap * 2; + + c_Edge_t* new_pool = (c_Edge_t*)c_Allocator_Realloc( + &self->allocator, + self->edges_pool, + old_cap * sizeof(c_Edge_t), + new_cap * sizeof(c_Edge_t) + ); + if (!new_pool) return C_ERR_NOMEM; + + self->edges_pool = new_pool; + self->edges_cap = new_cap; + } + + /* 2. Assign configuration details into the pool item slot */ + c_size_t edge_id = self->E; + self->edges_pool[edge_id] = (c_Edge_t){ .v = v, .w = w, .weight = weight }; + + /* 3. Append the structural reference pointer id into BOTH adjacent endpoint tracking arrays */ + c_err_t err = c_AdjList_Append(&self->adj_list[v], (c_uint_t)edge_id); + if (err != C_SUCCESS) return err; + + err = c_AdjList_Append(&self->adj_list[w], (c_uint_t)edge_id); + if (err != C_SUCCESS) return err; + + self->E++; + return C_SUCCESS; +} + +c_err_t c_EdgeWeightedGraph_GetEdge(c_EdgeWeightedGraph_t* self, c_size_t edge_idx, c_Edge_t* edge) { + if (!self ) return C_ERR_PARAM; + + /* Strict firewall boundary validation against the total allocated edge count */ + if (edge_idx >= self->E) return C_ERR_OUTOFBOUND; + + /* Execute a fast bitwise block-copy of the edge object contents */ + if (edge) { + *edge = self->edges_pool[edge_idx]; + } + + return C_SUCCESS; +} + + +c_err_t c_EdgeWeightedGraph_RemoveEdge(c_EdgeWeightedGraph_t* self, c_size_t edge_id) { + if (!self) return C_ERR_PARAM; + + /* 1. Bounds check firewall validation */ + if (edge_id >= self->E) return C_ERR_OUTOFBOUND; + + c_Edge_t* edge = &self->edges_pool[edge_id]; + + /* If the edge is already soft-deleted, skip to prevent repetitive lookups */ + if (edge->v == C_EDGE_SENTINEL) return C_SUCCESS; + + c_size_t vertex_v = edge->v; + c_size_t vertex_w = edge->w; + + /* 2. Purge the edge_id reference from vertex v's adjacency list */ + c_UIntArray_t* adj_v = &self->adj_list[vertex_v]; + c_size_t size_v = (c_size_t)c_UIntArray_GetSize(adj_v); + c_bool_t found_v = C_FALSE; + + for (c_size_t i = 0; i < size_v; ++i) { + c_uint_t generic_id = 0; + if (c_UIntArray_Get(adj_v, i, &generic_id) == C_SUCCESS) { + if ((c_size_t)generic_id == edge_id) { + c_UIntArray_Remove(adj_v, i); + found_v = C_TRUE; + break; + } + } + } + + /* 3. Purge the edge_id reference from vertex w's adjacency list */ + c_UIntArray_t* adj_w = &self->adj_list[vertex_w]; + c_size_t size_w = (c_size_t)c_UIntArray_GetSize(adj_w); + c_bool_t found_w = C_FALSE; + + for (c_size_t i = 0; i < size_w; ++i) { + c_uint_t generic_id = 0; + if (c_UIntArray_Get(adj_w, i, &generic_id) == C_SUCCESS) { + if ((c_size_t)generic_id == edge_id) { + c_UIntArray_Remove(adj_w, i); + found_w = C_TRUE; + break; + } + } + } + + /* 4. Finalize soft-deletion updates inside the pool slot */ + if (found_v || found_w) { + edge->v = C_EDGE_SENTINEL; + edge->w = C_EDGE_SENTINEL; + edge->weight = 0.0; + } else { + return C_ERR_NOTFOUND; + } + + return C_SUCCESS; +} + + diff --git a/Graph/c_EdgeWeightedGraph.h b/Graph/c_EdgeWeightedGraph.h new file mode 100644 index 0000000..cfcc832 --- /dev/null +++ b/Graph/c_EdgeWeightedGraph.h @@ -0,0 +1,60 @@ +#ifndef INCLUDED_C_EDGEWEIGHTEDGRAPH_H +#define INCLUDED_C_EDGEWEIGHTEDGRAPH_H + +#ifndef INCLUDED_C_EDGE_H +#include +#endif /*INCLUDED_C_EDGE_H*/ + +#ifndef INCLUDED_C_ADJLIST_H +#include +#endif /*INCLUDED_C_ADJLIST_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_size_t V; /* Total number of vertices */ + c_size_t E; /* Total number of edges */ + c_AdjList_t* adj_list; /* Array of dynamic lists storing indices to a global edge pool */ + c_Edge_t* edges_pool; /* Global continuous array pool housing raw edge objects */ + c_size_t edges_cap; /* Allocated edge pool tracking capacity boundary */ + c_Allocator_t allocator; /* Embedded custom allocator structure instance */ +} c_EdgeWeightedGraph_t; + + +/** + * @brief Initializes an empty edge-weighted graph with V vertices. + * @param allocator Explicit allocator context used to configure memory regions + */ +c_err_t c_EdgeWeightedGraph_Init(c_EdgeWeightedGraph_t* self, c_size_t V, c_Allocator_t* alloc); + +/** + * @brief Destroys the graph and releases all inner heap allocations safely. + */ +void c_EdgeWeightedGraph_Destroy(c_EdgeWeightedGraph_t* self); + +/** + * @brief Adds an undirected weighted edge between vertex v and w. + */ +c_err_t c_EdgeWeightedGraph_AddEdge(c_EdgeWeightedGraph_t* self, c_size_t v, c_size_t w, double weight); + +c_err_t c_EdgeWeightedGraph_GetEdge(c_EdgeWeightedGraph_t* self, c_size_t edge_idx, c_Edge_t* edge); + +c_err_t c_EdgeWeightedGraph_RemoveEdge(c_EdgeWeightedGraph_t* self, c_size_t edge_idx); + +/* ================================================================================================================== */ +/* Inline Inspection Hooks */ + +C_STATIC_FORCE_INLINE +c_size_t c_EdgeWeightedGraph_GetV(const c_EdgeWeightedGraph_t* self) { + return self ? self->V : 0; +} + +C_STATIC_FORCE_INLINE +c_size_t c_EdgeWeightedGraph_GetE(const c_EdgeWeightedGraph_t* self) { + return self ? self->E : 0; +} + + +#endif /*INCLUDED_C_EDGEWEIGHTEDGRAPH_H*/ diff --git a/Graph/c_EdgeWeightedGraph.t.c b/Graph/c_EdgeWeightedGraph.t.c new file mode 100644 index 0000000..f9bbb87 --- /dev/null +++ b/Graph/c_EdgeWeightedGraph.t.c @@ -0,0 +1,42 @@ +#include "c_Test.h" +#include "c_EdgeWeightedGraph.h" + +TEST_CASE(test_edge_weighted_graph_plumbing) { + c_EdgeWeightedGraph_t graph; + c_err_t err = c_EdgeWeightedGraph_Init(&graph, 3, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + ASSERT_LL_EQ(3, c_EdgeWeightedGraph_GetV(&graph)); + ASSERT_LL_EQ(0, c_EdgeWeightedGraph_GetE(&graph)); + + /* Inject a single undirected connection step: 0 - 1 (Weight: 3.5) */ + err = c_EdgeWeightedGraph_AddEdge(&graph, 0, 1, 3.5); + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_LL_EQ(1, c_EdgeWeightedGraph_GetE(&graph)); + + /* Inspect connectivity lists for index point 0 */ + c_AdjList_t* zero_adj = &graph.adj_list[0]; + ASSERT_INT_EQ(1, c_AdjList_GetSize(zero_adj)); + + c_uint_t generic_id = 0; + err = c_AdjList_Get(zero_adj, 0, &generic_id); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Fetch edge references out from pool */ + c_Edge_t* resolved_edge = &graph.edges_pool[(c_size_t)generic_id]; + ASSERT_DOUBLE_EQ_MSG(3.5, c_Edge_Weight(resolved_edge), "Weight match failed"); + + /* Validate endpoints parsing */ + c_size_t start_node = c_Edge_Either(resolved_edge); + ASSERT_LL_EQ(0, start_node); + ASSERT_LL_EQ(1, c_Edge_Other(resolved_edge, start_node)); + + c_EdgeWeightedGraph_Destroy(&graph); +} + +int main(void) { + TEST_START(EdgeWeightedGraph_Plumbing_Suite); + RUN_TEST(test_edge_weighted_graph_plumbing); + TEST_REPORT(); + RETURN_TEST_STATUS; +} diff --git a/Graph/c_EulerianCycle.c b/Graph/c_EulerianCycle.c new file mode 100644 index 0000000..9a65383 --- /dev/null +++ b/Graph/c_EulerianCycle.c @@ -0,0 +1,176 @@ +#include + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +// Helper struct to uniquely represent and filter out undirected edge pairings during the trail walk +typedef struct { + c_VertexId_t v; + c_VertexId_t w; + c_bool_t is_used; +} c_EdgeRef_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_EulerianCycle_Init(c_EulerianCycle_t* self, const c_Graph_t* G, c_Allocator_t* allocator) { + if (!self || !G ) return C_ERR_PARAM; + + self->allocator = allocator?*allocator:c_DefaultAllocator; + self->has_cycle = C_FALSE; + + // Initialize our results storage list container component + if (c_VertexIdList_Init(&self->cycle, 0, &self->allocator) != C_SUCCESS) { + return C_ERR_NOMEM; + } + + if (G->V == 0) return C_SUCCESS; + + // Condition 1: Check parity of vertex degrees (All active vertices must be even) + c_VertexId_t start_vertex = G->V; // Use G->V as an unassigned sentinel flag + for (c_VertexId_t v = 0; v < G->V; v++) { + c_AdjList_t* list = c_Graph_GetAdjList((c_Graph_t*)G, v); + c_size_t degree = list ? list->size : 0; + + if (degree % 2 != 0) { + return C_SUCCESS; // Odd degree breaks Eulerian cycle condition immediately + } + if (degree > 0 && start_vertex == G->V) { + start_vertex = v; // Locate the first non-isolated node to start traversal + } + } + + // Handle trivial baseline case where a graph has vertices but zero edges + if (G->E == 0) { + self->has_cycle = C_TRUE; + if (c_VertexIdList_Append(&self->cycle, 0) != C_SUCCESS) { + c_VertexIdList_Destroy(&self->cycle); + return C_ERR_NOMEM; + } + return C_SUCCESS; + } + + /* ---------------------------------------------------------------------- */ + /* Hierholzer's Algorithm Optimized Preparation */ + + // Tracks our processing cursor index position inside each vertex's adjacency array + c_size_t* adj_cursor = (c_size_t*)c_Allocator_Alloc(&self->allocator, G->V * sizeof(c_size_t)); + + // Since each undirected edge is stored twice (v->w and w->v), we can allocate a tracking bitmask + // array of size G->V. Each vertex tracks which neighbors it has already visited using a local bit field or boolean flag array. + // To keep it simple, clean, and cache-friendly, we allocate a flat array tracking visited states for all edges globally. + // Total directional edge slots in the graph = 2 * G->E. We can assign an overall visited flag to each unique undirected pair. + // To find the twin reverse edge instantly without deep loops, we can track a mirrored boolean array parallel to each adj_list entry. + + c_bool_t** edge_visited = (c_bool_t**)c_Allocator_Alloc(&self->allocator, G->V * sizeof(c_bool_t*)); + + if (!adj_cursor || !edge_visited) { + goto free_temp_memory; + } + + memset(adj_cursor, 0, G->V * sizeof(c_size_t)); + memset(edge_visited, 0, G->V * sizeof(c_bool_t*)); + + // Allocate sub-arrays matching the exact size layout of each adjacency list track + for (c_VertexId_t i = 0; i < G->V; i++) { + c_AdjList_t* list = c_Graph_GetAdjList((c_Graph_t*)G, i); + c_size_t sz = list ? list->size : 0; + if (sz > 0) { + edge_visited[i] = (c_bool_t*)c_Allocator_Alloc(&self->allocator, sz * sizeof(c_bool_t)); + if (!edge_visited[i]) goto free_temp_memory; + memset(edge_visited[i], 0, sz * sizeof(c_bool_t)); + } + } + + // Setup Hierholzer's explicit LIFO stack and temporary path components + c_VertexIdList_t stack; + c_VertexIdList_t reversed_route; + + if (c_VertexIdList_Init(&stack, 16, &self->allocator) != C_SUCCESS) goto free_temp_memory; + if (c_VertexIdList_Init(&reversed_route, 16, &self->allocator) != C_SUCCESS) { + c_VertexIdList_Destroy(&stack); + goto free_temp_memory; + } + + // Push the first valid non-isolated starting vertex onto the processing stack + c_VertexIdList_Append(&stack, (c_uint_t)start_vertex); + + while (stack.size > 0) { + c_VertexId_t v = (c_VertexId_t)stack.array[stack.size - 1]; + c_AdjList_t* list = c_Graph_GetAdjList((c_Graph_t*)G, v); + + // Check if the current vertex has any unvisited outgoing edges left + if (list && adj_cursor[v] < list->size) { + c_size_t idx = adj_cursor[v]++; // Advance cursor to consume edge slot + + if (!edge_visited[v][idx]) { + c_VertexId_t w = (c_VertexId_t)list->array[idx]; + + // Mark edge v -> w as burned + edge_visited[v][idx] = C_TRUE; + + // UNDIRECTED INVARIANT MATCH: Burn the twin matching reverse edge w -> v immediately. + // We do a small targeted scan on w's clean local array to find the match, maximizing cache line usage. + c_AdjList_t* twin_list = c_Graph_GetAdjList((c_Graph_t*)G, w); + if (twin_list) { + for (c_size_t j = 0; j < twin_list->size; j++) { + if (twin_list->array[j] == v && !edge_visited[w][j]) { + edge_visited[w][j] = C_TRUE; + break; + } + } + } + + // Push neighbor target node onto stack to step forward into the cycle loop path + c_VertexIdList_Append(&stack, (c_uint_t)w); + } + } else { + // Vertex has no unvisited edges left: pop from stack and append to our route timeline + c_VertexIdList_Append(&reversed_route, (c_uint_t)v); + stack.size--; + } + } + + // Condition 2: Validate graph connectedness. + // An Eulerian cycle must consume every single edge in the entire graph exactly once. + // Therefore, the total vertices recorded along the path itinerary trail must equal G->E + 1. + if (reversed_route.size == G->E + 1) { + self->has_cycle = C_TRUE; + + // Reverse elements from the stack output to rebuild a proper forward chronological itinerary path (source -> target) + for (c_size_t i = reversed_route.size; i > 0; i--) { + c_uint_t val; + c_VertexIdList_Get(&reversed_route, i - 1, &val); + c_VertexIdList_Append(&self->cycle, val); + } + } + + c_VertexIdList_Destroy(&stack); + c_VertexIdList_Destroy(&reversed_route); + +free_temp_memory: + if (adj_cursor) c_Allocator_Free(&self->allocator, adj_cursor); + if (edge_visited) { + for (c_VertexId_t i = 0; i < G->V; i++) { + if (edge_visited[i]) c_Allocator_Free(&self->allocator, edge_visited[i]); + } + c_Allocator_Free(&self->allocator, edge_visited); + } + return C_SUCCESS; +} + +void c_EulerianCycle_Destroy(c_EulerianCycle_t* self) { + if (!self) return; + c_VertexIdList_Destroy(&self->cycle); + self->has_cycle = C_FALSE; +} + +c_bool_t c_EulerianCycle_HasCycle(const c_EulerianCycle_t* self) { + return self ? self->has_cycle : C_FALSE; +} + +const c_VertexIdList_t* c_EulerianCycle_Path(const c_EulerianCycle_t* self) { + return self ? &self->cycle : NULL; +} + diff --git a/Graph/c_EulerianCycle.h b/Graph/c_EulerianCycle.h new file mode 100644 index 0000000..4889665 --- /dev/null +++ b/Graph/c_EulerianCycle.h @@ -0,0 +1,50 @@ +#ifndef INCLUDED_C_EULERIANCYCLE_H +#define INCLUDED_C_EULERIANCYCLE_H + +#ifndef INCLUDED_C_GRAPH_H +#include +#endif /*INCLUDED_C_GRAPH_H*/ + + +#ifndef INCLUDED_C_VERTEXIDLIST_H +#include +#endif /*INCLUDED_C_VERTEXIDLIST_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_VertexIdList_t cycle; // Stores the structural sequence of vertices forming the cycle + c_bool_t has_cycle; // True if an Eulerian cycle exists in the graph + c_Allocator_t allocator; // Memory allocator reference instance copy +} c_EulerianCycle_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Computes an Eulerian cycle in an undirected graph if one exists. + * @param self Pointer to the uninitialized Eulerian cycle tracking structure. + * @param G Pointer to the constant target graph object to analyze. + * @param allocator Memory allocator instance pointer to deploy. + * @return C_SUCCESS on success, or an error status code on allocation failure. + */ +c_err_t c_EulerianCycle_Init(c_EulerianCycle_t* self, const c_Graph_t* G, c_Allocator_t* allocator); + +/** + * @brief Drops all internal allocation states within the Eulerian cycle instance safely. + */ +void c_EulerianCycle_Destroy(c_EulerianCycle_t* self); + +/** + * @brief Does the graph contain an Eulerian cycle? + */ +c_bool_t c_EulerianCycle_HasCycle(const c_EulerianCycle_t* self); + +/** + * @brief Returns the vertex path sequence forming the Eulerian cycle, or empty if none exists. + */ +const c_VertexIdList_t* c_EulerianCycle_Path(const c_EulerianCycle_t* self); + +#endif /*INCLUDED_C_EULERIANCYCLE_H*/ diff --git a/Graph/c_EulerianCycle.t.c b/Graph/c_EulerianCycle.t.c new file mode 100644 index 0000000..93ce4b6 --- /dev/null +++ b/Graph/c_EulerianCycle.t.c @@ -0,0 +1,59 @@ +#include "c_EulerianCycle.h" +#include "c_Test.h" +#include +#include + +TEST_CASE(test_c_eulerian_cycle_detection) { + // Test Scenario A: Valid Eulerian Bowtie Figure-Eight Shape (5 vertices, all degrees are even) + c_Graph_t g_eulerian; + c_Graph_Init(&g_eulerian, 5, 0); + c_Graph_AddEdge(&g_eulerian, 0, 1); + c_Graph_AddEdge(&g_eulerian, 1, 2); + c_Graph_AddEdge(&g_eulerian, 2, 0); // Left Triangle loop closed + c_Graph_AddEdge(&g_eulerian, 2, 3); + c_Graph_AddEdge(&g_eulerian, 3, 4); + c_Graph_AddEdge(&g_eulerian, 4, 2); // Right Triangle loop closed + + c_EulerianCycle_t ec1; + c_err_t err = c_EulerianCycle_Init(&ec1, &g_eulerian, 0); + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_TRUE(c_EulerianCycle_HasCycle(&ec1)); + + const c_VertexIdList_t* path = c_EulerianCycle_Path(&ec1); + ASSERT_PTR_NOT_NULL(path); + ASSERT_LL_EQ(7, path->size); // 6 edges requires a 7-vertex path timeline loop + + // Verify it correctly maps back into a closed trail format + c_uint_t start, end; + c_VertexIdList_Get((c_VertexIdList_t*)path, 0, &start); + c_VertexIdList_Get((c_VertexIdList_t*)path, path->size - 1, &end); + ASSERT_LL_EQ(start, end); + + // Test Scenario B: Non-Eulerian Shape (A simple 4-node line path 0-1-2-3: odd endpoints) + c_Graph_t g_invalid; + c_Graph_Init(&g_invalid, 4, 0); + c_Graph_AddEdge(&g_invalid, 0, 1); + c_Graph_AddEdge(&g_invalid, 1, 2); + c_Graph_AddEdge(&g_invalid, 2, 3); + + c_EulerianCycle_t ec2; + err = c_EulerianCycle_Init(&ec2, &g_invalid, 0); + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_FALSE(c_EulerianCycle_HasCycle(&ec2)); // Must fail boundary check + + c_EulerianCycle_Destroy(&ec1); + c_EulerianCycle_Destroy(&ec2); + c_Graph_Destroy(&g_eulerian); + c_Graph_Destroy(&g_invalid); +} + +int main(int argc, char** argv){ + TEST_START(Component Tests); + + // Execution list configurations + RUN_TEST(test_c_eulerian_cycle_detection); + + TEST_REPORT(); + + RETURN_TEST_STATUS; +} diff --git a/Graph/c_FloydWarshall.c b/Graph/c_FloydWarshall.c new file mode 100644 index 0000000..5214ba8 --- /dev/null +++ b/Graph/c_FloydWarshall.c @@ -0,0 +1,139 @@ +#include "c_FloydWarshall.h" + + +#define FW_SENTINEL ((c_size_t)-1) + +static void c_FloydWarshall_FreeMatrices(c_FloydWarshall_t* self) { + if (self->dist_to) { + for (c_size_t i = 0; i < self->V; ++i) { + if (self->dist_to[i]) c_Allocator_Free(&self->allocator, self->dist_to[i]); + } + c_Allocator_Free(&self->allocator, self->dist_to); + self->dist_to = NULL; + } + if (self->next_vertex) { + for (c_size_t i = 0; i < self->V; ++i) { + if (self->next_vertex[i]) c_Allocator_Free(&self->allocator, self->next_vertex[i]); + } + c_Allocator_Free(&self->allocator, self->next_vertex); + self->next_vertex = NULL; + } + if (self->edge_to) { + for (c_size_t i = 0; i < self->V; ++i) { + if (self->edge_to[i]) c_Allocator_Free(&self->allocator, self->edge_to[i]); + } + c_Allocator_Free(&self->allocator, self->edge_to); + self->edge_to = NULL; + } +} + +c_err_t c_FloydWarshall_Init(c_FloydWarshall_t* self, c_EdgeWeightedDigraph_t* graph, c_Allocator_t* allocator) { + if (!self || !graph) return C_ERR_PARAM; + + self->allocator = allocator ? *allocator : c_DefaultAllocator; + self->V = graph->V; + self->has_neg_cycle = C_FALSE; + + self->dist_to = (double**)c_Allocator_Calloc(&self->allocator, self->V, sizeof(double*)); + self->next_vertex = (c_size_t**)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t*)); + self->edge_to = (c_size_t**)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t*)); + + if (!self->dist_to || !self->next_vertex || !self->edge_to) { + c_FloydWarshall_FreeMatrices(self); + return C_ERR_NOMEM; + } + + for (c_size_t i = 0; i < self->V; ++i) { + self->dist_to[i] = (double*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(double)); + self->next_vertex[i] = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + self->edge_to[i] = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + + if (!self->dist_to[i] || !self->next_vertex[i] || !self->edge_to[i]) { + c_FloydWarshall_FreeMatrices(self); + return C_ERR_NOMEM; + } + + for (c_size_t j = 0; j < self->V; ++j) { + self->dist_to[i][j] = (i == j) ? 0.0 : DBL_MAX; + self->next_vertex[i][j] = FW_SENTINEL; + self->edge_to[i][j] = FW_SENTINEL; + } + } + + /* Step 1: Base adjacency mapping from graph edges pool */ + for (c_size_t e = 0; e < graph->E; ++e) { + c_DirectedEdge_t* edge = &graph->edges_pool[e]; + c_size_t u = edge->from; + c_size_t v = edge->to; + + if (edge->weight < self->dist_to[u][v]) { + self->dist_to[u][v] = edge->weight; + self->next_vertex[u][v] = v; + self->edge_to[u][v] = e; + } + } + + /* Step 2: Floyd-Warshall dynamic programming loops pass */ + for (c_size_t k = 0; k < self->V; ++k) { + for (c_size_t i = 0; i < self->V; ++i) { + if (self->dist_to[i][k] == DBL_MAX) continue; + + for (c_size_t j = 0; j < self->V; ++j) { + if (self->dist_to[k][j] == DBL_MAX) continue; + + if (self->dist_to[i][j] > self->dist_to[i][k] + self->dist_to[k][j]) { + self->dist_to[i][j] = self->dist_to[i][k] + self->dist_to[k][j]; + self->next_vertex[i][j] = self->next_vertex[i][k]; + self->edge_to[i][j] = self->edge_to[i][k]; + } + } + + /* Step 3: Negative cycle interception */ + if (self->dist_to[i][i] < 0.0) { + self->has_neg_cycle = C_TRUE; + return C_SUCCESS; + } + } + } + + return C_SUCCESS; +} + +void c_FloydWarshall_Destroy(c_FloydWarshall_t* self) { + if (!self) return; + c_FloydWarshall_FreeMatrices(self); + self->V = 0; + self->has_neg_cycle = C_FALSE; +} + +/* ================================================================================================================== */ +/* Exact Signature Re-implementation Match */ + +c_err_t c_FloydWarshall_Path(c_FloydWarshall_t* self, c_size_t u, c_size_t v, c_EdgeIdList_t* out_path) { + if (!self || !out_path || u >= self->V || v >= self->V) return C_ERR_PARAM; + if (self->has_neg_cycle || !c_FloydWarshall_HasPath(self, u, v)) return C_ERR_FAIL; + + /* If origin equals target, the shortest path requires 0 edge steps */ + if (u == v) return C_SUCCESS; + + c_size_t curr_u = u; + c_err_t err = C_SUCCESS; + + /* Step forward from origin node 'u' using the pre-computed next_vertex mappings */ + while (curr_u != v) { + c_size_t next = self->next_vertex[curr_u][v]; + if (next == FW_SENTINEL) return C_ERR_FAIL; /* Structural anomaly anchor guard */ + + c_size_t edge_id = self->edge_to[curr_u][v]; + if (edge_id == FW_SENTINEL) return C_ERR_FAIL; + + /* Append the resolved edge token into your sequential vertex list wrapper */ + err = c_EdgeIdList_Append(out_path, (c_uint_t)edge_id); + if (err != C_SUCCESS) return err; + + /* Move the sliding window origin forward to transition the trace */ + curr_u = next; + } + + return C_SUCCESS; +} diff --git a/Graph/c_FloydWarshall.h b/Graph/c_FloydWarshall.h new file mode 100644 index 0000000..1811c58 --- /dev/null +++ b/Graph/c_FloydWarshall.h @@ -0,0 +1,73 @@ +#ifndef INCLUDED_C_FLOYDWARSHALL_H +#define INCLUDED_C_FLOYDWARSHALL_H + +#ifndef INCLUDED_C_EDGEWEIGHTEDDIGRAPH_H +#include +#endif /*INCLUDED_C_EDGEWEIGHTEDDIGRAPH_H*/ + +#ifndef INCLUDED_C_EDGEIDLIST_H +#include +#endif /*INCLUDED_C_EDGEIDLIST_H*/ + + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + double** dist_to; /* dist_to[u][v] = distance of shortest path from u to v */ + c_size_t** next_vertex; /* next_vertex[u][v] = immediate next vertex on path from u to v */ + c_size_t** edge_to; /* edge_to[u][v] = global directed edge_id for choice transition u -> next */ + c_size_t V; /* Total number of vertices in the digraph */ + c_bool_t has_neg_cycle; /* Flag indicating if a negative cycle was intercepted */ + c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ +} c_FloydWarshall_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Computes all-pairs shortest paths in a general edge-weighted digraph. + * @param allocator Explicit allocator context used to configure internal tracking state memory + */ +c_err_t c_FloydWarshall_Init(c_FloydWarshall_t* self, c_EdgeWeightedDigraph_t* graph, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers safely. + */ +void c_FloydWarshall_Destroy(c_FloydWarshall_t* self); + +/** + * @brief Is there a directed path from vertex 'u' to vertex 'v'? + */ +C_STATIC_FORCE_INLINE c_bool_t c_FloydWarshall_HasPath(c_FloydWarshall_t* self, c_size_t u, c_size_t v) { + if (!self || self->has_neg_cycle || u >= self->V || v >= self->V || !self->dist_to) return C_FALSE; + return self->dist_to[u][v] < DBL_MAX; +} + +/** + * @brief Returns the distance of the shortest path from vertex 'u' to vertex 'v'. + * @return Distance value, or DBL_MAX if unreachable / parameter error + */ +C_STATIC_FORCE_INLINE double c_FloydWarshall_Dist(c_FloydWarshall_t* self, c_size_t u, c_size_t v) { + if (!self || u >= self->V || v >= self->V || !self->dist_to) return DBL_MAX; + if (self->has_neg_cycle) return -DBL_MAX; + return self->dist_to[u][v]; +} + +/** + * @brief Does the edge-weighted digraph contain any negative cycles? + */ +C_STATIC_FORCE_INLINE +c_bool_t c_FloydWarshall_HasNegativeCycle(c_FloydWarshall_t* self) { + return self ? self->has_neg_cycle : C_FALSE; +} + +/** + * @brief Reconstructs the exact shortest path from vertex 'u' to vertex 'v' and appends it to out_path. + * @param out_path An initialized c_EdgeIdList_t container to collect the sequence of global directed edge_ids. + */ +c_err_t c_FloydWarshall_Path(c_FloydWarshall_t* self, c_size_t u, c_size_t v, c_EdgeIdList_t* out_path); + + +#endif /*INCLUDED_C_FLOYDWARSHALL_H*/ diff --git a/Graph/c_FloydWarshall.t.c b/Graph/c_FloydWarshall.t.c new file mode 100644 index 0000000..2db0ac1 --- /dev/null +++ b/Graph/c_FloydWarshall.t.c @@ -0,0 +1,124 @@ +#include "c_FloydWarshall.h" +#include "c_Test.h" +#include "c_EdgeWeightedDigraph.h" +#include "c_FloydWarshall.h" + +TEST_CASE(test_floyd_warshall_all_pairs) { + c_EdgeWeightedDigraph_t g; + c_EdgeWeightedDigraph_Init(&g, 3, NULL); + + /* Construct an evaluation network: + * 0 -> 1 (Weight: 3.0) + * 1 -> 2 (Weight: 1.0) + * 0 -> 2 (Weight: 5.0) -> Shortest path is 0->1->2 (Total = 4.0) + */ + c_EdgeWeightedDigraph_AddEdge(&g, 0, 1, 3.0); + c_EdgeWeightedDigraph_AddEdge(&g, 1, 2, 1.0); + c_EdgeWeightedDigraph_AddEdge(&g, 0, 2, 5.0); + + c_FloydWarshall_t fw; + c_err_t err = c_FloydWarshall_Init(&fw, &g, &g.allocator); + ASSERT_INT_EQ(C_SUCCESS, err); + + ASSERT_FALSE(c_FloydWarshall_HasNegativeCycle(&fw)); + + /* Verify all-pairs queries */ + ASSERT_TRUE(c_FloydWarshall_HasPath(&fw, 0, 2)); + ASSERT_DOUBLE_EQ_MSG(4.0, c_FloydWarshall_Dist(&fw, 0, 2), "All-pairs path minimization failed"); + ASSERT_DOUBLE_EQ_MSG(1.0, c_FloydWarshall_Dist(&fw, 1, 2), "Direct neighbor query mismatch"); + + /* Backward parsing check */ + ASSERT_FALSE(c_FloydWarshall_HasPath(&fw, 2, 0)); + + c_FloydWarshall_Destroy(&fw); + c_EdgeWeightedDigraph_Destroy(&g); +} + +TEST_CASE(test_floyd_warshall_negative_cycle) { + c_EdgeWeightedDigraph_t g; + c_EdgeWeightedDigraph_Init(&g, 2, NULL); + + /* Construct a negative loop */ + c_EdgeWeightedDigraph_AddEdge(&g, 0, 1, 1.0); + c_EdgeWeightedDigraph_AddEdge(&g, 1, 0, -3.0); /* Cycle total is -2.0 */ + + c_FloydWarshall_t fw; + c_err_t err = c_FloydWarshall_Init(&fw, &g, &g.allocator); + ASSERT_INT_EQ(C_SUCCESS, err); + + ASSERT_TRUE(c_FloydWarshall_HasNegativeCycle(&fw)); + + c_FloydWarshall_Destroy(&fw); + c_EdgeWeightedDigraph_Destroy(&g); +} + +TEST_CASE(test_floyd_warshall_exact_path_extraction) { + c_EdgeWeightedDigraph_t g; + c_err_t err = c_EdgeWeightedDigraph_Init(&g, 4, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* + * Construct an evaluation network topology branch: + * 0 -> 1 (Weight: 3.0) [Edge ID: 0] + * 1 -> 2 (Weight: 1.0) [Edge ID: 1] + * 0 -> 2 (Weight: 6.0) [Edge ID: 2] -> Heavy choice, skipped + * 2 -> 3 (Weight: 2.0) [Edge ID: 3] + * True Shortest path from 0 to 3 is: 0 -> 1 -> 2 -> 3 (Total Weight = 3.0 + 1.0 + 2.0 = 6.0) + */ + c_EdgeWeightedDigraph_AddEdge(&g, 0, 1, 3.0); + c_EdgeWeightedDigraph_AddEdge(&g, 1, 2, 1.0); + c_EdgeWeightedDigraph_AddEdge(&g, 0, 2, 6.0); + c_EdgeWeightedDigraph_AddEdge(&g, 2, 3, 2.0); + + c_FloydWarshall_t fw; + err = c_FloydWarshall_Init(&fw, &g, &g.allocator); + ASSERT_INT_EQ(C_SUCCESS, err); + + ASSERT_FALSE(c_FloydWarshall_HasNegativeCycle(&fw)); + ASSERT_TRUE(c_FloydWarshall_HasPath(&fw, 0, 3)); + ASSERT_DOUBLE_EQ_MSG(6.0, c_FloydWarshall_Dist(&fw, 0, 3), "Shortest matrix evaluation total math wrong"); + + /* Create target list to capture path edge tokens */ + c_EdgeIdList_t edge_path; + c_EdgeIdList_Init(&edge_path,0,0); + + /* Run the matching signature format */ + err = c_FloydWarshall_Path(&fw, 0, 3, &edge_path); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Expected sequence size should be exactly 3 edge steps long */ + c_size_t path_len = (c_size_t)c_EdgeIdList_GetSize(&edge_path); + ASSERT_LL_EQ(3, path_len); + + /* Extract and verify raw edge pool tokens matching forward traversal direction */ + c_uint_t e_id = 0; + c_EdgeIdList_Get(&edge_path, 0, &e_id); ASSERT_LL_EQ(0, e_id); /* Edge 0 (0->1) */ + c_DirectedEdge_t edge; + c_EdgeWeightedDigraph_GetEdge(&g, e_id, &edge); + ASSERT_LL_EQ(0, edge.from); + ASSERT_LL_EQ(1, edge.to); + + c_EdgeIdList_Get(&edge_path, 1, &e_id); ASSERT_LL_EQ(1, e_id); /* Edge 1 (1->2) */ + + c_EdgeWeightedDigraph_GetEdge(&g, e_id, &edge); + ASSERT_LL_EQ(1, edge.from); + ASSERT_LL_EQ(2, edge.to); + + c_EdgeIdList_Get(&edge_path, 2, &e_id); ASSERT_LL_EQ(3, e_id); /* Edge 3 (2->3) */ + c_EdgeWeightedDigraph_GetEdge(&g, e_id, &edge); + ASSERT_LL_EQ(2, edge.from); + ASSERT_LL_EQ(3, edge.to); + + c_EdgeIdList_Destroy(&edge_path); + c_FloydWarshall_Destroy(&fw); + c_EdgeWeightedDigraph_Destroy(&g); +} + +int main(void) { + TEST_START(FloydWarshall_Matrix_Suite); + RUN_TEST(test_floyd_warshall_all_pairs); + RUN_TEST(test_floyd_warshall_negative_cycle); + RUN_TEST(test_floyd_warshall_exact_path_extraction); + TEST_REPORT(); + RETURN_TEST_STATUS; +} diff --git a/Graph/c_GabowSCC.c b/Graph/c_GabowSCC.c new file mode 100644 index 0000000..e21cb62 --- /dev/null +++ b/Graph/c_GabowSCC.c @@ -0,0 +1,105 @@ +#include + + +static void c_GabowSCC_DFS(c_GabowSCC_t* self, c_Digraph_t* graph, c_size_t v) { + self->marked[v] = C_TRUE; + self->pre[v] = self->pre_counter++; + + /* Push onto both structural tracking stacks */ + self->scc_stack[self->scc_stack_sz++] = v; + self->path_stack[self->path_stack_sz++] = v; + + c_AdjList_t* adj = &graph->adj_list[v]; + c_size_t size = (c_size_t)c_UIntArray_GetSize(adj); + + for (c_size_t i = 0; i < size; ++i) { + c_uint_t target_value = 0; + c_err_t err = c_UIntArray_Get(adj, i, &target_value); + + if (err == C_SUCCESS) { + c_size_t w = (c_size_t)target_value; + + if (!self->marked[w]) { + c_GabowSCC_DFS(self, graph, w); + } + /* If already visited but not yet assigned to an SCC component */ + else if (self->id[w] == C_SIZE_MAX) { + /* Pop vertices from path_stack whose pre-order numbers are greater than pre[w] */ + while (self->path_stack_sz > 0 && self->pre[self->path_stack[self->path_stack_sz - 1]] > self->pre[w]) { + self->path_stack_sz--; + } + } + } + } + + /* If v is the root of an SCC component block, pop the complete component cluster */ + if (self->path_stack_sz > 0 && self->path_stack[self->path_stack_sz - 1] == v) { + self->path_stack_sz--; /* Remove v from the root boundary stack */ + + c_size_t w = 0; + do { + w = self->scc_stack[--self->scc_stack_sz]; + self->id[w] = self->count; + } while (w != v); + + self->count++; /* Increment structural component counter group */ + } +} + +c_err_t c_GabowSCC_Init(c_GabowSCC_t* self, c_Digraph_t* graph, c_Allocator_t* allocator) { + if (!self || !graph) return C_ERR_PARAM; + + self->allocator = allocator ? *allocator : c_DefaultAllocator; + self->count = 0; + self->pre_counter = 0; + self->scc_stack_sz = 0; + self->path_stack_sz = 0; + self->V = graph->V; + + if (self->V == 0) return C_SUCCESS; + + /* 1. Allocate tracked execution matrices arrays */ + self->marked = (c_bool_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_bool_t)); + self->id = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + self->pre = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + self->scc_stack = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + self->path_stack = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + + if (!self->marked || !self->id || !self->pre || !self->scc_stack || !self->path_stack) { + c_GabowSCC_Destroy(self); + return C_ERR_NOMEM; + } + + /* Initialize all ID components to unassigned boundary sentinel state */ + for (c_size_t i = 0; i < self->V; ++i) { + self->id[i] = C_SIZE_MAX; + } + + /* 2. Process all components loops securely */ + for (c_size_t v = 0; v < self->V; ++v) { + if (!self->marked[v]) { + c_GabowSCC_DFS(self, graph, v); + } + } + + return C_SUCCESS; +} + +void c_GabowSCC_Destroy(c_GabowSCC_t* self) { + if (!self) return; + if (self->marked) c_Allocator_Free(&self->allocator, self->marked); + if (self->id) c_Allocator_Free(&self->allocator, self->id); + if (self->pre) c_Allocator_Free(&self->allocator, self->pre); + if (self->scc_stack) c_Allocator_Free(&self->allocator, self->scc_stack); + if (self->path_stack) c_Allocator_Free(&self->allocator, self->path_stack); + + self->marked = NULL; + self->id = NULL; + self->pre = NULL; + self->scc_stack = NULL; + self->path_stack = NULL; + self->count = 0; + self->scc_stack_sz = 0; + self->path_stack_sz = 0; + self->V = 0; +} diff --git a/Graph/c_GabowSCC.h b/Graph/c_GabowSCC.h new file mode 100644 index 0000000..d080fd8 --- /dev/null +++ b/Graph/c_GabowSCC.h @@ -0,0 +1,68 @@ +#ifndef INCLUDED_C_GABOWSCC_H +#define INCLUDED_C_GABOWSCC_H + +#ifndef INCLUDED_C_DIGRAPH_H +#include +#endif /*INCLUDED_C_DIGRAPH_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_bool_t* marked; /* Visited tracking array (size of graph->V) */ + c_size_t* id; /* id[v] = component identifier containing v (0 to count-1) */ + c_size_t* pre; /* pre[v] = preorder index of vertex v */ + c_size_t* scc_stack; /* Stack to keep track of vertices in the current SCC candidate */ + c_size_t scc_stack_sz; /* Size of scc_stack */ + c_size_t* path_stack; /* Stack to determine root of strongly connected components */ + c_size_t path_stack_sz;/* Size of path_stack */ + c_size_t pre_counter; /* Preorder index sequencing counter */ + c_size_t count; /* Total number of strongly connected components discovered */ + c_size_t V; /* Stored vertex count for safe boundary check lookups */ + c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ +} c_GabowSCC_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Computes strongly connected components for the specified digraph using Gabow's algorithm. + * @param allocator Explicit allocator context used to allocate internal routing arrays + */ +c_err_t c_GabowSCC_Init(c_GabowSCC_t* self, c_Digraph_t* graph, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers + */ +void c_GabowSCC_Destroy(c_GabowSCC_t* self); + +/** + * @brief Are vertices 'v' and 'w' strongly connected (belong to the same SCC)? + */ +C_STATIC_FORCE_INLINE +c_bool_t c_GabowSCC_StronglyConnected(c_GabowSCC_t* self, c_size_t v, c_size_t w) { + if (!self || !self->id || v >= self->V || w >= self->V) return C_FALSE; + return self->id[v] == self->id[w]; +} + +/** + * @brief Returns the component id containing vertex 'v' + * @return Component integer ID (0 to count-1), or C_SIZE_MAX on boundary check failures + */ +C_STATIC_FORCE_INLINE +c_size_t c_GabowSCC_GetId(const c_GabowSCC_t* self, c_size_t v) { + if (!self || !self->id || v >= self->V) return C_SIZE_MAX; + return self->id[v]; +} + +/** + * @brief Returns the total number of strongly connected components + */ +C_STATIC_FORCE_INLINE +c_size_t c_GabowSCC_GetCount(const c_GabowSCC_t* self) { + return self ? self->count : 0; +} + + +#endif /*INCLUDED_C_GABOWSCC_H*/ diff --git a/Graph/c_GabowSCC.t.c b/Graph/c_GabowSCC.t.c new file mode 100644 index 0000000..39a8e0b --- /dev/null +++ b/Graph/c_GabowSCC.t.c @@ -0,0 +1,54 @@ +#include "c_Test.h" +#include "c_Digraph.h" +#include "c_GabowSCC.h" + +TEST_CASE(test_gabow_scc_clustering) { + c_Digraph_t g; + c_err_t err = c_Digraph_Init(&g, 5, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Graph layout matching standard component test tracks: + * Component 1: 0 -> 1 -> 2 -> 0 + * Bridge edge: 2 -> 3 + * Component 2: 3 -> 4 -> 3 + */ + c_Digraph_AddEdge(&g, 0, 1); + c_Digraph_AddEdge(&g, 1, 2); + c_Digraph_AddEdge(&g, 2, 0); + c_Digraph_AddEdge(&g, 2, 3); + c_Digraph_AddEdge(&g, 3, 4); + c_Digraph_AddEdge(&g, 4, 3); + + c_GabowSCC_t scc; + err = c_GabowSCC_Init(&scc, &g, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Verify count profile */ + ASSERT_LL_EQ(2, c_GabowSCC_GetCount(&scc)); + + /* Verify internal associations layout */ + ASSERT_TRUE(c_GabowSCC_StronglyConnected(&scc, 0, 1)); + ASSERT_TRUE(c_GabowSCC_StronglyConnected(&scc, 1, 2)); + ASSERT_TRUE(c_GabowSCC_StronglyConnected(&scc, 3, 4)); + ASSERT_FALSE(c_GabowSCC_StronglyConnected(&scc, 0, 3)); + + /* Check boundary protections fallback indices */ + ASSERT_LL_EQ(C_SIZE_MAX, c_GabowSCC_GetId(&scc, 99)); + + c_GabowSCC_Destroy(&scc); + c_Digraph_Destroy(&g); +} + +int main(void) { + /* Bootstrapping test environment wrapper */ + TEST_START(Test); + + /* Run specified edge cases */ + RUN_TEST(test_gabow_scc_clustering); + + /* Generate total logging matrix summaries sheet */ + TEST_REPORT(); + + /* Unwind tracking status frames code signals */ + RETURN_TEST_STATUS; +} diff --git a/Graph/c_Graph.c b/Graph/c_Graph.c index ba962ce..97cd032 100644 --- a/Graph/c_Graph.c +++ b/Graph/c_Graph.c @@ -30,7 +30,7 @@ void c_Graph_Destroy(c_Graph_t* self) { } -c_err_t c_Graph_AddEdge(c_Graph_t* self, c_size_t v, c_size_t w) { +c_err_t c_Graph_AddEdge(c_Graph_t* self, c_VertexId_t v, c_VertexId_t w) { if (!self || v >= self->V || w >= self->V) { return C_ERR_PARAM; } @@ -70,7 +70,7 @@ c_err_t c_Graph_AddEdge(c_Graph_t* self, c_size_t v, c_size_t w) { return C_SUCCESS; } -c_err_t c_Graph_RemoveEdge(c_Graph_t* self, c_size_t v, c_size_t w) { +c_err_t c_Graph_RemoveEdge(c_Graph_t* self, c_VertexId_t v, c_VertexId_t w) { if (!self || v >= self->V || w >= self->V) { return C_ERR_PARAM; } @@ -123,7 +123,7 @@ c_err_t c_Graph_RemoveEdge(c_Graph_t* self, c_size_t v, c_size_t w) { return C_SUCCESS; } -c_bool_t c_Graph_HasEdge(const c_Graph_t* self, c_size_t v, c_size_t w) { +c_bool_t c_Graph_HasEdge(const c_Graph_t* self, c_VertexId_t v, c_VertexId_t w) { // Return false immediately if the graph is NULL or if indices are out of bounds if (!self || v >= self->V || w >= self->V) { return C_FALSE; @@ -142,12 +142,12 @@ c_bool_t c_Graph_HasEdge(const c_Graph_t* self, c_size_t v, c_size_t w) { } -c_size_t c_Graph_Degree(c_Graph_t* self, c_size_t v) { +c_size_t c_Graph_Degree(c_Graph_t* self, c_VertexId_t v) { if (!self || v >=self->V) return 0; return self->adj_list[v].size; } -c_AdjList_t* c_Graph_GetAdjList(c_Graph_t* self, c_size_t v) { +c_AdjList_t* c_Graph_GetAdjList(c_Graph_t* self, c_VertexId_t v) { if (!self || v >= self->V) { return NULL; } diff --git a/Graph/c_Graph.h b/Graph/c_Graph.h index ff7b0d7..ea68582 100644 --- a/Graph/c_Graph.h +++ b/Graph/c_Graph.h @@ -10,6 +10,8 @@ /* ------------------------------------------------------------------------------------------------------------------ */ /* */ +typedef c_uint_t c_VertexId_t; + typedef struct { c_size_t V; c_size_t E; @@ -24,15 +26,15 @@ c_err_t c_Graph_Init(c_Graph_t* self, c_size_t V, c_Allocator_t* allocator); void c_Graph_Destroy(c_Graph_t* self); -c_err_t c_Graph_AddEdge(c_Graph_t* self, c_size_t v, c_size_t w); +c_err_t c_Graph_AddEdge(c_Graph_t* self, c_VertexId_t v, c_VertexId_t w); -c_err_t c_Graph_RemoveEdge(c_Graph_t* self, c_size_t v, c_size_t w); +c_err_t c_Graph_RemoveEdge(c_Graph_t* self, c_VertexId_t v, c_VertexId_t w); -c_bool_t c_Graph_HasEdge(const c_Graph_t* self, c_size_t v, c_size_t w); +c_bool_t c_Graph_HasEdge(const c_Graph_t* self, c_VertexId_t v, c_VertexId_t w); -c_size_t c_Graph_Degree(c_Graph_t* self, c_size_t v); +c_size_t c_Graph_Degree(c_Graph_t* self, c_VertexId_t v); -c_AdjList_t* c_Graph_GetAdjList(c_Graph_t* self, c_size_t v); +c_AdjList_t* c_Graph_GetAdjList(c_Graph_t* self, c_VertexId_t v); /** * @brief Creates a complete deep copy of a source graph. diff --git a/Graph/c_GraphDFS.c b/Graph/c_GraphDFS.c new file mode 100644 index 0000000..e241d3d --- /dev/null +++ b/Graph/c_GraphDFS.c @@ -0,0 +1,63 @@ +#include + +// Internal recursive helper function that tracks execution states down the stack frames +static c_bool_t c_Graph_DFS_Internal( + const c_Graph_t* self, + c_size_t current_vertex, + c_bool_t* visited, + c_Graph_DFS_Callback callback, + void* context) +{ + // Mark node as discovered + visited[current_vertex] = C_TRUE; + + // Execute user callback function. If it returns false, bubble up to trigger an early abort. + if (callback && !callback(current_vertex, context)) { + return C_FALSE; + } + + const c_AdjList_t* neighbors = &self->adj_list[current_vertex]; + + // Cache-friendly sequential scan over the contiguous array of primitive integer neighbors + for (c_size_t i = 0; i < neighbors->size; i++) { + c_uint_t neighbor = neighbors->array[i]; + + if (!visited[neighbor]) { + // Recurse into unvisited neighbors. Propagate abort commands immediately. + if (!c_Graph_DFS_Internal(self, neighbor, visited, callback, context)) { + return C_FALSE; + } + } + } + + return C_TRUE; // Continue searching normally +} + +c_err_t c_Graph_DFS(const c_Graph_t* self, c_size_t src_vertex, c_Graph_DFS_Callback callback, void* context, c_Allocator_t* allocator) { + if (!self || src_vertex >= self->V) { + return C_ERR_PARAM; + } + + if (self->V == 0) { + return C_SUCCESS; + } + + allocator = allocator?allocator:&c_DefaultAllocator; + + // Allocate tracking memory via the graph's configured allocator + c_bool_t* visited = (c_bool_t*)c_Allocator_Alloc(allocator, self->V * sizeof(c_bool_t)); + if (!visited) { + return C_ERR_NOMEM; + } + + // Initialize all tracking slots to false + memset(visited, 0, self->V * sizeof(c_bool_t)); + + // Launch the deep structural recursive traversal engine + c_Graph_DFS_Internal(self, src_vertex, visited, callback, context); + + // Safely free the temporary visited array tracking memory bounds + c_Allocator_Free(allocator, visited); + + return C_SUCCESS; +} diff --git a/Graph/c_GraphDFS.h b/Graph/c_GraphDFS.h new file mode 100644 index 0000000..7fccf19 --- /dev/null +++ b/Graph/c_GraphDFS.h @@ -0,0 +1,33 @@ +#ifndef INCLUDED_C_GRAPHDFS_H +#define INCLUDED_C_GRAPHDFS_H + +#ifndef INCLUDED_C_GRAPH_H +#include +#endif /*INCLUDED_C_GRAPH_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +/** + * @brief User-defined callback action function executed when a vertex is discovered. + * @param vertex_index The index of the vertex being visited. + * @param context User-supplied optional state pointer (e.g., custom accumulation structures). + * @return C_TRUE to continue traversing, C_FALSE to abort the entire DFS traversal early. + */ +typedef c_bool_t (*c_Graph_DFS_Callback)(c_size_t vertex_index, void* context); + +/** + * @brief Performs a Depth-First Search starting from a designated source vertex. + * @param self Pointer to the constant graph container instance. + * @param src_vertex The index of the root vertex to start the search from. + * @param callback Function handler to execute on every discovered node. + * @param context Optional custom state pointer forwarded directly to the callback handler. + * @param allocator allocator for DFS + * @return C_SUCCESS on successful traversal, or an error status code on failure. + */ +c_err_t c_Graph_DFS(const c_Graph_t* self, c_size_t src_vertex, c_Graph_DFS_Callback callback, void* context, c_Allocator_t* allocator); + + +#endif /*INCLUDED_C_GRAPHDFS_H*/ diff --git a/Graph/c_GraphDFS.t.c b/Graph/c_GraphDFS.t.c new file mode 100644 index 0000000..f80e14f --- /dev/null +++ b/Graph/c_GraphDFS.t.c @@ -0,0 +1,65 @@ +#include "c_GraphDFS.h" +#include "c_Test.h" +#include +#include + +// Struct context to keep track of discovered traversal orders inside the callback +typedef struct { + c_size_t order_buffer[10]; + c_size_t count; +} DFS_Tracker; + +// Dummy callback processor mapping discovery steps +static c_bool_t mock_dfs_visitor(c_size_t vertex, void* context) { + DFS_Tracker* tracker = (DFS_Tracker*)context; + if (tracker->count < 10) { + tracker->order_buffer[tracker->count++] = vertex; + } + return C_TRUE; // Keep traversing +} + +TEST_CASE(test_graph_dfs_traversal) { + c_Graph_t graph; + c_Graph_Init(&graph, 4, 0); + + // Build a simple line-graph with a branch: 0-1, 1-2, 1-3 + c_Graph_AddEdge(&graph, 0, 1); + c_Graph_AddEdge(&graph, 1, 2); + c_Graph_AddEdge(&graph, 1, 3); + + DFS_Tracker tracker = { .count = 0 }; + + // Execute Depth First Scan starting from Root node 0 + c_err_t err = c_Graph_DFS(&graph, 0, mock_dfs_visitor, &tracker, 0); + + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_LL_EQ(4, tracker.count); // All nodes should be found + + // The first node discovered must be the start node (0) + ASSERT_LL_EQ(0, tracker.order_buffer[0]); + // The second node discovered must be 1, since it's 0's only neighbor + ASSERT_LL_EQ(1, tracker.order_buffer[1]); + + // Nodes 2 and 3 are structural branches out of 1. + // DFS will traverse one completely before popping back to process the other. + // Hence, positions 2 and 3 must contain some permutation of nodes 2 and 3. + c_size_t pos2 = tracker.order_buffer[2]; + c_size_t pos3 = tracker.order_buffer[3]; + ASSERT_TRUE((pos2 == 2 && pos3 == 3) || (pos2 == 3 && pos3 == 2)); + + c_Graph_Destroy(&graph); +} + +int main(int argc, char** argv){ + + TEST_START(Unit Tests); + + // 运行普通无环境要求的用例 + RUN_TEST(test_graph_dfs_traversal); + + // 打印最终统计报告 + TEST_REPORT(); + + RETURN_TEST_STATUS; + return 0; +} diff --git a/Graph/c_KosarajuSharirSCC.c b/Graph/c_KosarajuSharirSCC.c new file mode 100644 index 0000000..4da05d7 --- /dev/null +++ b/Graph/c_KosarajuSharirSCC.c @@ -0,0 +1,101 @@ +#include +#include "c_DepthFirstOrder.h" + +/* Private recursive DFS engine helper subroutine for the second pass grouping */ +static void c_KosarajuSharirSCC_DFS(c_KosarajuSharirSCC_t* self, const c_Digraph_t* graph, c_size_t v) { + self->marked[v] = C_TRUE; + self->id[v] = self->count; + + c_AdjList_t* adj = &graph->adj_list[v]; + c_size_t size = (c_size_t)c_AdjList_GetSize(adj); + + for (c_size_t i = 0; i < size; ++i) { + c_uint_t target_value = 0; + c_err_t err = c_AdjList_Get(adj, i, &target_value); + + if (err == C_SUCCESS) { + c_size_t w = (c_size_t)target_value; + if (!self->marked[w]) { + c_KosarajuSharirSCC_DFS(self, graph, w); + } + } + } +} + +c_err_t c_KosarajuSharirSCC_Init(c_KosarajuSharirSCC_t* self, c_Digraph_t* graph, c_Allocator_t* allocator) { + if (!self || !graph || graph->V==0) return C_ERR_PARAM; + + self->allocator = allocator ? *allocator : c_DefaultAllocator; + self->count = 0; + self->V = graph->V; + + /* 1. Allocate primary tracking mapping buffers */ + self->marked = (c_bool_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_bool_t)); + self->id = (c_size_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_size_t)); + if (!self->marked || !self->id) { + c_KosarajuSharirSCC_Destroy(self); + return C_ERR_NOMEM; + } + + /* 2. First Pass: Compute reverse post-order stream on the transposed/reversed graph */ + c_Digraph_t transposed; + c_err_t err = c_Digraph_Reverse(graph, &transposed); + if (err != C_SUCCESS) { + c_KosarajuSharirSCC_Destroy(self); + return err; + } + + c_DepthFirstOrder_t dfs_order; + err = c_DepthFirstOrder_Init(&dfs_order, &transposed, allocator); + if (err != C_SUCCESS) { + c_Digraph_Destroy(&transposed); + c_KosarajuSharirSCC_Destroy(self); + return err; + } + + c_VertexIdList_t rev_post_order; + c_VertexIdList_Init(&rev_post_order,0, allocator); + err = c_DepthFirstOrder_GetReversePost(&dfs_order, &rev_post_order); + + /* Clean up the working transposed graph and its intermediate state variables */ + c_DepthFirstOrder_Destroy(&dfs_order); + c_Digraph_Destroy(&transposed); + + if (err != C_SUCCESS) { + c_VertexIdList_Destroy(&rev_post_order); + c_KosarajuSharirSCC_Destroy(self); + return err; + } + + /* 3. Second Pass: Run standard DFS tracking on original graph using the reverse post-order sequence */ + c_size_t sequence_len = (c_size_t)c_VertexIdList_GetSize(&rev_post_order); + for (c_size_t i = 0; i < sequence_len; ++i) { + c_uint_t vertex_val = 0; + err = c_VertexIdList_Get(&rev_post_order, i, &vertex_val); + + if (err == C_SUCCESS) { + c_size_t v = (c_size_t)vertex_val; + if (!self->marked[v]) { + c_KosarajuSharirSCC_DFS(self, graph, v); + self->count++; /* Advance component ID group grouping */ + } + } + } + + c_VertexIdList_Destroy(&rev_post_order); + return C_SUCCESS; +} + +void c_KosarajuSharirSCC_Destroy(c_KosarajuSharirSCC_t* self) { + if (!self) return; + if (self->marked) { + c_Allocator_Free(&self->allocator, self->marked); + self->marked = NULL; + } + if (self->id) { + c_Allocator_Free(&self->allocator, self->id); + self->id = NULL; + } + self->count = 0; + self->V = 0; +} diff --git a/Graph/c_KosarajuSharirSCC.h b/Graph/c_KosarajuSharirSCC.h new file mode 100644 index 0000000..6b3c4e1 --- /dev/null +++ b/Graph/c_KosarajuSharirSCC.h @@ -0,0 +1,65 @@ +#ifndef INCLUDED_C_KOSARAJUSHARIRSCC_H +#define INCLUDED_C_KOSARAJUSHARIRSCC_H + +#ifndef INCLUDED_C_DIGRAPH_H +#include +#endif /*INCLUDED_C_DIGRAPH_H*/ + +#ifndef INCLUDED_C_VERTEXIDLIST_H +#include +#endif /*INCLUDED_C_VERTEXIDLIST_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_bool_t* marked; /* Visited tracking array (size of graph->V) */ + c_size_t* id; /* id[v] = component identifier containing v (0 to count-1) */ + c_size_t count; /* Total number of strongly connected components discovered */ + c_size_t V; + c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ +} c_KosarajuSharirSCC_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Computes strongly connected components for the specified digraph. + * @param allocator Explicit allocator context used to allocate internal arrays + */ +c_err_t c_KosarajuSharirSCC_Init(c_KosarajuSharirSCC_t* self, c_Digraph_t* graph, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers + */ +void c_KosarajuSharirSCC_Destroy(c_KosarajuSharirSCC_t* self); + +/** + * @brief Are vertices 'v' and 'w' strongly connected (belong to the same SCC)? + */ +C_STATIC_FORCE_INLINE +c_bool_t c_KosarajuSharirSCC_StronglyConnected(c_KosarajuSharirSCC_t* self, c_size_t v, c_size_t w) { + if (!self || !self->id || v >= self->V || w >= self->V) return C_FALSE; + return self->id[v] == self->id[w]; +} + +/** + * @brief Returns the component id containing vertex 'v' + * @return Component integer ID (0 to count-1), or C_SIZE_MAX on boundary check failures + */ +C_STATIC_FORCE_INLINE +c_size_t c_KosarajuSharirSCC_GetId(c_KosarajuSharirSCC_t* self, c_size_t v) { + if (!self || !self->id || v >= self->V) return C_SIZE_MAX; + return self->id[v]; +} + +/** + * @brief Returns the total number of strongly connected components + */ +C_STATIC_FORCE_INLINE +c_size_t c_KosarajuSharirSCC_GetCount(c_KosarajuSharirSCC_t* self) { + return self ? self->count : 0; +} + +#endif /*INCLUDED_C_KOSARAJUSHARIRSCC_H*/ diff --git a/Graph/c_KosarajuSharirSCC.t.c b/Graph/c_KosarajuSharirSCC.t.c new file mode 100644 index 0000000..07c4b59 --- /dev/null +++ b/Graph/c_KosarajuSharirSCC.t.c @@ -0,0 +1,57 @@ +#include "c_Test.h" +#include "c_Digraph.h" +#include "c_KosarajuSharirSCC.h" + +TEST_CASE(test_kosaraju_sharir_scc_clustering) { + c_Digraph_t g; + c_err_t err = c_Digraph_Init(&g, 5, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Construct a graph topology with 2 separate SCC groups: + * Component 1 (Loop group): 0 -> 1 -> 2 -> 0 + * Bridge link exiting out: 2 -> 3 + * Component 2 (Loop group): 3 -> 4 -> 3 + */ + c_Digraph_AddEdge(&g, 0, 1); + c_Digraph_AddEdge(&g, 1, 2); + c_Digraph_AddEdge(&g, 2, 0); + + c_Digraph_AddEdge(&g, 2, 3); /* Bridge connection */ + + c_Digraph_AddEdge(&g, 3, 4); + c_Digraph_AddEdge(&g, 4, 3); + + c_KosarajuSharirSCC_t scc; + err = c_KosarajuSharirSCC_Init(&scc, &g, 0); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Total strong connected component clusters count must equal exactly 2 */ + ASSERT_LL_EQ(2, c_KosarajuSharirSCC_GetCount(&scc)); + + /* Verify cluster properties (0, 1, 2 must have the exact same group ID) */ + ASSERT_TRUE(c_KosarajuSharirSCC_StronglyConnected(&scc, 0, 1)); + ASSERT_TRUE(c_KosarajuSharirSCC_StronglyConnected(&scc, 1, 2)); + + /* 3 and 4 must have the exact same group ID */ + ASSERT_TRUE(c_KosarajuSharirSCC_StronglyConnected(&scc, 3, 4)); + + /* Cross verification check (0 and 3 are isolated in different clusters) */ + ASSERT_FALSE(c_KosarajuSharirSCC_StronglyConnected(&scc, 0, 3)); + + c_KosarajuSharirSCC_Destroy(&scc); + c_Digraph_Destroy(&g); +} + + +int main(int argc, char** argv){ + TEST_START(c_NonrecursiveDFS Component Tests); + + // Execution list configurations + RUN_TEST(test_kosaraju_sharir_scc_clustering); + + TEST_REPORT(); + + RETURN_TEST_STATUS; + + return 0; +} \ No newline at end of file diff --git a/Graph/c_KruskalMST.c b/Graph/c_KruskalMST.c new file mode 100644 index 0000000..040aedb --- /dev/null +++ b/Graph/c_KruskalMST.c @@ -0,0 +1,89 @@ +#include "c_KruskalMST.h" +#include "c_MinPQ.h" +#include "c_QuickFindUF.h" /* Direct inline layout consumption */ + + +/* ================================================================================================================== */ +/* Private Sort Comparator for c_MinPQ_t matching edge priorities */ + +static int c_Kruskal_EdgeCompare(const void* a, const void* b, void* args) { + c_size_t id_a = *(const c_size_t*)a; + c_size_t id_b = *(const c_size_t*)b; + c_Edge_t* pool = (c_Edge_t*)args; + + double weight_a = pool[id_a].weight; + double weight_b = pool[id_b].weight; + + return (weight_a > weight_b) - (weight_a < weight_b); +} + +/* ================================================================================================================== */ +/* Core Kruskal MST Initializer Processing Routine */ + +c_err_t c_KruskalMST_Init(c_KruskalMST_t* self, c_EdgeWeightedGraph_t* graph, c_Allocator_t* allocator) { + if (!self || !graph) return C_ERR_PARAM; + + self->allocator = allocator ? *allocator : c_DefaultAllocator; + self->weight = 0.0; + c_VertexIdList_Init(&self->mst_edges, 0, allocator); + + if (graph->V == 0 || graph->E == 0) return C_SUCCESS; + + /* 1. Build a priority queue containing all edge indices to sort them automatically */ + c_MinPQ_t pq; + c_err_t err = c_MinPQ_Init( + &pq, + graph->E, + sizeof(c_size_t), + c_Kruskal_EdgeCompare, + graph->edges_pool, + allocator + ); + if (err != C_SUCCESS) return err; + + /* Seed the PQ with every single unique edge index */ + for (c_size_t i = 0; i < graph->E; ++i) { + c_MinPQ_Push(&pq, &i); + } + + /* 2. Configure your specific c_QuickFindUF data structure */ + c_QuickFindUF_t uf; + err = c_QuickFindUF_Init(&uf, graph->V, allocator); + if (err != C_SUCCESS) { + c_MinPQ_Destroy(&pq); + return err; + } + + /* 3. Main processing loop: extract edges in ascending order of weight */ + while (!c_MinPQ_IsEmpty(&pq) && c_VertexIdList_GetSize(&self->mst_edges) < graph->V - 1) { + c_size_t edge_id = 0; + c_MinPQ_Pop(&pq, &edge_id); + + c_Edge_t* edge = &graph->edges_pool[edge_id]; + c_size_t v = edge->v; + c_size_t w = edge->w; + + /* If v and w are not already connected, adding this edge is safe (no cycle created) */ + if (!c_QuickFindUF_IsConnected(&uf, v, w)) { + c_QuickFindUF_Union(&uf, v, w); + c_VertexIdList_Append(&self->mst_edges, (c_uint_t)edge_id); + self->weight += edge->weight; + } + } + + /* Clean up working structures */ + c_QuickFindUF_Destroy(&uf); + c_MinPQ_Destroy(&pq); + return C_SUCCESS; +} + +void c_KruskalMST_Destroy(c_KruskalMST_t* self) { + if (!self) return; + c_VertexIdList_Destroy(&self->mst_edges); + self->weight = 0.0; +} + +c_err_t c_KruskalMST_GetEdges(c_KruskalMST_t* self, c_VertexIdList_t* out_edges) { + if (!self || !out_edges) return C_ERR_PARAM; + return c_VertexIdList_Copy(out_edges, &self->mst_edges); +} diff --git a/Graph/c_KruskalMST.h b/Graph/c_KruskalMST.h new file mode 100644 index 0000000..9914195 --- /dev/null +++ b/Graph/c_KruskalMST.h @@ -0,0 +1,52 @@ +#ifndef INCLUDED_C_KRUSKALMST_H +#define INCLUDED_C_KRUSKALMST_H + +#ifndef INCLUDED_C_EDGEWEIGHTEDGRAPH_H +#include +#endif /*INCLUDED_C_EDGEWEIGHTEDGRAPH_H*/ + + +#ifndef INCLUDED_C_VERTEXIDLIST_H +#include +#endif /*INCLUDED_C_VERTEXIDLIST_H*/ + + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_VertexIdList_t mst_edges; /* Final optimized list of edge_ids inside the tree */ + double weight; /* Total minimum weight summation of the MST */ + c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ +} c_KruskalMST_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Computes a minimum spanning tree of an edge-weighted graph using Kruskal's algorithm. + * @param allocator Explicit allocator context used to configure internal tracking state memory + */ +c_err_t c_KruskalMST_Init(c_KruskalMST_t* self, c_EdgeWeightedGraph_t* graph, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers + */ +void c_KruskalMST_Destroy(c_KruskalMST_t* self); + +/** + * @brief Gets a copy of the edge ids included in the Minimum Spanning Tree. + */ +c_err_t c_KruskalMST_GetEdges(c_KruskalMST_t* self, c_VertexIdList_t* out_edges); + +/** + * @brief Returns the total weight summation of the MST. + */ +C_STATIC_FORCE_INLINE +double c_KruskalMST_Weight(c_KruskalMST_t* self) { + return self ? self->weight : 0.0; +} + + +#endif /*INCLUDED_C_KRUSKALMST_H*/ diff --git a/Graph/c_KruskalMST.t.c b/Graph/c_KruskalMST.t.c new file mode 100644 index 0000000..898e255 --- /dev/null +++ b/Graph/c_KruskalMST.t.c @@ -0,0 +1,48 @@ +#include "c_Test.h" +#include "c_EdgeWeightedGraph.h" +#include "c_KruskalMST.h" +#include "c_VertexIdList.h" + +TEST_CASE(test_kruskal_mst_with_quick_find_uf) { + c_EdgeWeightedGraph_t g; + c_err_t err = c_EdgeWeightedGraph_Init(&g, 4, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Construct a graph topology to test tree minimization: + * 0 - 1 (Weight: 1.0) + * 1 - 2 (Weight: 2.0) + * 2 - 3 (Weight: 3.0) + * 3 - 0 (Weight: 4.0) -> Redundant loop closer skipped + * 0 - 2 (Weight: 5.0) -> Heavier cross edge skipped + */ + c_EdgeWeightedGraph_AddEdge(&g, 0, 1, 1.0); + c_EdgeWeightedGraph_AddEdge(&g, 1, 2, 2.0); + c_EdgeWeightedGraph_AddEdge(&g, 2, 3, 3.0); + c_EdgeWeightedGraph_AddEdge(&g, 3, 0, 4.0); + c_EdgeWeightedGraph_AddEdge(&g, 0, 2, 5.0); + + c_KruskalMST_t kruskal; + err = c_KruskalMST_Init(&kruskal, &g, &g.allocator); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Total minimum weight summation must equal 1.0 + 2.0 + 3.0 = 6.0 */ + ASSERT_DOUBLE_EQ_MSG(6.0, c_KruskalMST_Weight(&kruskal), "Kruskal MST calculation failed"); + + c_VertexIdList_t result_list; + c_VertexIdList_Init(&result_list, 0, 0); + + err = c_KruskalMST_GetEdges(&kruskal, &result_list); + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_INT_EQ(3, c_UIntArray_GetSize(&result_list)); + + c_VertexIdList_Destroy(&result_list); + c_KruskalMST_Destroy(&kruskal); + c_EdgeWeightedGraph_Destroy(&g); +} + +int main(void) { + TEST_START(Kruskal_MST_QuickFind_UF_Suite); + RUN_TEST(test_kruskal_mst_with_quick_find_uf); + TEST_REPORT(); + RETURN_TEST_STATUS; +} diff --git a/Graph/c_LazyPrimMST.c b/Graph/c_LazyPrimMST.c new file mode 100644 index 0000000..219251a --- /dev/null +++ b/Graph/c_LazyPrimMST.c @@ -0,0 +1,115 @@ +#include "c_LazyPrimMST.h" +#include "c_MinPQ.h" + + +/* ================================================================================================================== */ +/* Private Comparison Callback for c_MinPQ_t */ + +static int c_LazyPrim_EdgeCompare(const void* a, const void* b, void* args) { + c_size_t id_a = *(const c_size_t*)a; + c_size_t id_b = *(const c_size_t*)b; + c_Edge_t* pool = (c_Edge_t*)args; + + double weight_a = pool[id_a].weight; + double weight_b = pool[id_b].weight; + + return (weight_a > weight_b) - (weight_a < weight_b); +} + +/* ================================================================================================================== */ +/* Lazy Prim Subroutine Processing Functions */ + +static void c_LazyPrim_Scan(c_LazyPrimMST_t* self, const c_EdgeWeightedGraph_t* graph, c_size_t v, c_MinPQ_t* pq) { + self->marked[v] = C_TRUE; + + c_UIntArray_t* adj = &graph->adj_list[v]; + c_size_t size = (c_size_t)c_UIntArray_GetSize(adj); + + for (c_size_t i = 0; i < size; ++i) { + c_uint_t generic_edge_id = 0; + c_err_t err = c_UIntArray_Get(adj, i, &generic_edge_id); + + if (err == C_SUCCESS) { + c_size_t edge_id = (c_size_t)generic_edge_id; + c_Edge_t* edge = &graph->edges_pool[edge_id]; + c_size_t w = (edge->v == v) ? edge->w : edge->v; + + if (!self->marked[w]) { + /* Push the edge_id payload into the min-priority queue */ + c_MinPQ_Push(pq, &edge_id); + } + } + } +} + +c_err_t c_LazyPrimMST_Init(c_LazyPrimMST_t* self, c_EdgeWeightedGraph_t* graph, c_Allocator_t* allocator) { + if (!self || !graph) return C_ERR_PARAM; + + self->allocator = allocator ? *allocator : c_DefaultAllocator; + self->weight = 0.0; + c_VertexIdList_Init(&self->mst_edges, 0, allocator); + + self->marked = (c_bool_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_bool_t)); + if (!self->marked) return C_ERR_NOMEM; + + /* 1. Initialize your formal generic Min-Priority Queue to hold c_size_t elements */ + c_MinPQ_t pq; + c_err_t err = c_MinPQ_Init( + &pq, + graph->E, + sizeof(c_size_t), + c_LazyPrim_EdgeCompare, + graph->edges_pool, /* Pass global edge pool as the comparison context */ + allocator + ); + + if (err != C_SUCCESS) { + c_Allocator_Free(&self->allocator, self->marked); + self->marked = NULL; + return err; + } + + /* 2. Run over all vertices to build component spanning forest loops if graph is disconnected */ + for (c_size_t v = 0; v < graph->V; ++v) { + if (!self->marked[v]) { + c_LazyPrim_Scan(self, graph, v, &pq); + + while (!c_MinPQ_IsEmpty(&pq)) { + c_size_t edge_id = 0; + c_MinPQ_Pop(&pq, &edge_id); /* Pops the edge with minimum weight */ + + c_Edge_t* edge = &graph->edges_pool[edge_id]; + c_size_t v_end = edge->v; + c_size_t w_end = edge->w; + + /* Invariant check: Skip if both vertices are already part of the MST tree */ + if (self->marked[v_end] && self->marked[w_end]) continue; + + c_VertexIdList_Append(&self->mst_edges, (c_uint_t)edge_id); + self->weight += edge->weight; + + if (!self->marked[v_end]) c_LazyPrim_Scan(self, graph, v_end, &pq); + if (!self->marked[w_end]) c_LazyPrim_Scan(self, graph, w_end, &pq); + } + } + } + + /* Clean up PQ tracking allocations */ + c_MinPQ_Destroy(&pq); + return C_SUCCESS; +} + +void c_LazyPrimMST_Destroy(c_LazyPrimMST_t* self) { + if (!self) return; + if (self->marked) { + c_Allocator_Free(&self->allocator, self->marked); + self->marked = NULL; + } + c_VertexIdList_Destroy(&self->mst_edges); + self->weight = 0.0; +} + +c_err_t c_LazyPrimMST_GetEdges(c_LazyPrimMST_t* self, c_VertexIdList_t* out_edges) { + if (!self || !out_edges) return C_ERR_PARAM; + return c_VertexIdList_Copy(out_edges, &self->mst_edges); +} diff --git a/Graph/c_LazyPrimMST.h b/Graph/c_LazyPrimMST.h new file mode 100644 index 0000000..e1b255c --- /dev/null +++ b/Graph/c_LazyPrimMST.h @@ -0,0 +1,52 @@ +#ifndef INCLUDED_C_LAZYPRIMMST_H +#define INCLUDED_C_LAZYPRIMMST_H + +#ifndef INCLUDED_C_EDGEWEIGHTEDGRAPH_H +#include +#endif /*INCLUDED_C_EDGEWEIGHTEDGRAPH_H*/ + + +#ifndef INCLUDED_C_VERTEXIDLIST_H +#include +#endif /*INCLUDED_C_VERTEXIDLIST_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_bool_t* marked; /* Visited tracking array (size of graph->V) */ + c_VertexIdList_t mst_edges; /* List of global edge_ids included in the MST */ + double weight; /* Total total minimum weight summation of the MST */ + c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ +} c_LazyPrimMST_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Computes a minimum spanning tree of an edge-weighted graph using Lazy Prim's algorithm. + * @param allocator Explicit allocator context used to allocate internal search tracking objects + */ +c_err_t c_LazyPrimMST_Init(c_LazyPrimMST_t* self, c_EdgeWeightedGraph_t* graph, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers + */ +void c_LazyPrimMST_Destroy(c_LazyPrimMST_t* self); + +/** + * @brief Gets a copy of the edge ids included in the Minimum Spanning Tree. + * @param out_edges An initialized c_VertexIdList_t container to collect the sequence. + */ +c_err_t c_LazyPrimMST_GetEdges(c_LazyPrimMST_t* self, c_VertexIdList_t* out_edges); + +/** + * @brief Returns the total total minimum weight summation of the MST + */ +C_STATIC_FORCE_INLINE +double c_LazyPrimMST_Weight(c_LazyPrimMST_t* self) { + return self ? self->weight : 0.0; +} + +#endif /*INCLUDED_C_LAZYPRIMMST_H*/ diff --git a/Graph/c_LazyPrimMST.t.c b/Graph/c_LazyPrimMST.t.c new file mode 100644 index 0000000..bf91108 --- /dev/null +++ b/Graph/c_LazyPrimMST.t.c @@ -0,0 +1,85 @@ +#include "c_Test.h" +#include "c_EdgeWeightedGraph.h" +#include "c_LazyPrimMST.h" +#include "c_VertexIdList.h" + +TEST_CASE(test_realigned_lazy_prim_mst) { + c_EdgeWeightedGraph_t g; + + /* 1. Initialize an undirected edge-weighted graph with 4 vertices */ + c_err_t err = c_EdgeWeightedGraph_Init(&g, 4, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_LL_EQ(4, c_EdgeWeightedGraph_GetV(&g)); + + /* + * 2. Build a classic cyclical cycle graph topology to verify tree minimization: + * 0 - 1 (Weight: 1.0) -> Expected in MST + * 1 - 2 (Weight: 2.0) -> Expected in MST + * 2 - 3 (Weight: 3.0) -> Expected in MST + * 3 - 0 (Weight: 4.0) -> Redundant (heavier cycle closer edge) + * 0 - 2 (Weight: 5.0) -> Heavy redundant cross-diagonal edge + */ + err = c_EdgeWeightedGraph_AddEdge(&g, 0, 1, 1.0); ASSERT_INT_EQ(C_SUCCESS, err); + err = c_EdgeWeightedGraph_AddEdge(&g, 1, 2, 2.0); ASSERT_INT_EQ(C_SUCCESS, err); + err = c_EdgeWeightedGraph_AddEdge(&g, 2, 3, 3.0); ASSERT_INT_EQ(C_SUCCESS, err); + err = c_EdgeWeightedGraph_AddEdge(&g, 3, 0, 4.0); ASSERT_INT_EQ(C_SUCCESS, err); + err = c_EdgeWeightedGraph_AddEdge(&g, 0, 2, 5.0); ASSERT_INT_EQ(C_SUCCESS, err); + + ASSERT_LL_EQ(5, c_EdgeWeightedGraph_GetE(&g)); + + /* 3. Compute the Minimum Spanning Tree using our normalized Lazy Prim component */ + c_LazyPrimMST_t prim; + err = c_LazyPrimMST_Init(&prim, &g, 0); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* 4. Total minimal spanning tree weight must sum exactly to 1.0 + 2.0 + 3.0 = 6.0 */ + ASSERT_DOUBLE_EQ_MSG(6.0, c_LazyPrimMST_Weight(&prim), "Lazy Prim failed to compute correct total MST weight"); + + /* 5. Extract tree edge selections and parse structural contents */ + c_VertexIdList_t selected_edges; + c_VertexIdList_Init(&selected_edges, 0, 0); + + err = c_LazyPrimMST_GetEdges(&prim, &selected_edges); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* A graph with V vertices must span exactly V - 1 edges in its minimal spanning layout */ + c_size_t mst_size = (c_size_t)c_VertexIdList_GetSize(&selected_edges); + ASSERT_LL_EQ(3, mst_size); + + /* 6. Verify that our heavier redundant edges (4.0 and 5.0) were skipped */ + double computed_weight_check = 0.0; + for (c_size_t i = 0; i < mst_size; ++i) { + c_uint_t generic_edge_id = 0; + err = c_VertexIdList_Get(&selected_edges, i, &generic_edge_id); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Resolve back via the graph's continuous edge pool layout mapping */ + c_Edge_t* edge = &g.edges_pool[(c_size_t)generic_edge_id]; + double w = c_Edge_Weight(edge); + + computed_weight_check += w; + + /* Ensure neither of the heavy loop closures was mistakenly collected */ + ASSERT_TRUE(w < 3.9); + } + ASSERT_DOUBLE_EQ_MSG(6.0, computed_weight_check, "Sum of extracted edges does not equal total logged weight"); + + /* 7. Reclaim and safely recycle structural resources */ + c_VertexIdList_Destroy(&selected_edges); + c_LazyPrimMST_Destroy(&prim); + c_EdgeWeightedGraph_Destroy(&g); +} + +int main(void) { + /* Fire up the core testing wrapper suite */ + TEST_START(LazyPrimMST_GenericPQ_Integration_Suite); + + /* Run specified structural plumbing edge cases */ + RUN_TEST(test_realigned_lazy_prim_mst); + + /* Output summary metrics logs to console */ + TEST_REPORT(); + + /* Unwind system back with proper testing suite status signals */ + RETURN_TEST_STATUS; +} diff --git a/Graph/c_NonrecursiveDFS.c b/Graph/c_NonrecursiveDFS.c new file mode 100644 index 0000000..2a6a4fd --- /dev/null +++ b/Graph/c_NonrecursiveDFS.c @@ -0,0 +1,149 @@ +#include +#include + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +c_err_t c_NonrecursiveDFS_Init(c_NonrecursiveDFS_t* self, const c_Graph_t* G, c_VertexId_t s, c_Allocator_t* allocator) { + if (!self || !G || s >= G->V) { + return C_ERR_PARAM; + } + + self->allocator = allocator?*allocator:c_DefaultAllocator; + self->source = s; + self->count = 0; + self->marked = NULL; + self->edge_to = NULL; + + if (G->V == 0) { + return C_SUCCESS; + } + + // Allocate structural state tracing map blocks + self->marked = (c_bool_t*)c_Allocator_Alloc(&self->allocator, G->V * sizeof(*self->marked)); + self->edge_to = (c_size_t*)c_Allocator_Alloc(&self->allocator, G->V * sizeof(*self->edge_to)); + + if (!self->marked || !self->edge_to) { + c_NonrecursiveDFS_Destroy(self); + return C_ERR_NOMEM; + } + + memset(self->marked, 0, G->V * sizeof(*self->marked)); + for (c_size_t i = 0; i < G->V; i++) { + self->edge_to[i] = G->V; // Out-of-bounds boundary sentinel definition + } + + // Initialize an explicit LIFO dynamic array stack using your core components + c_ArrayStack_t stack; + if (c_ArrayStack_Init(&stack, sizeof(c_VertexId_t), 8, &self->allocator) != C_SUCCESS) { + c_NonrecursiveDFS_Destroy(self); + return C_ERR_NOMEM; + } + + // Append starting root index trajectory frame + if (c_ArrayStack_Push(&stack, &s) != C_ERR_OK) { + c_ArrayStack_Destroy(&stack); + c_NonrecursiveDFS_Destroy(self); + return C_ERR_NOMEM; + } + + c_err_t err = C_ERR_OK; + // Main non-recursive traversal processing engine loop + while (!c_ArrayStack_IsEmpty(&stack)) { + // Pop operations map straight to reducing active sizing tallies + c_VertexId_t v = -1; + if((err = c_ArrayStack_Pop(&stack, &v))!=C_ERR_OK){ + c_ArrayStack_Destroy(&stack); + c_NonrecursiveDFS_Destroy(self); + return err; + } + + + if (!self->marked[v]) { + self->marked[v] = C_TRUE; + self->count++; + + c_AdjList_t* list = c_Graph_GetAdjList((c_Graph_t*)G, v); + if (list && !c_AdjList_IsEmpty(list)) { + // Iterating backward replicates recursive evaluation order identically + c_size_t i = list->size; + while (i>0) { + i--; + const c_VertexId_t w = list->array[i]; + + if (!self->marked[w]) { + self->edge_to[w] = v; // Track incoming path route link + + if (c_ArrayStack_Push(&stack, &w) != C_ERR_OK) { + c_ArrayStack_Destroy(&stack); + c_NonrecursiveDFS_Destroy(self); + return C_ERR_NOMEM; + } + } + } + } + } + } + + c_ArrayStack_Destroy(&stack); + return err; +} + +void c_NonrecursiveDFS_Destroy(c_NonrecursiveDFS_t* self) { + if (!self) return; + + if (self->marked) c_Allocator_Free(&self->allocator, self->marked); + if (self->edge_to) c_Allocator_Free(&self->allocator, self->edge_to); + + self->marked = NULL; + self->edge_to = NULL; + self->count = 0; + self->source = 0; +} + +c_bool_t c_NonrecursiveDFS_HasPathTo(const c_NonrecursiveDFS_t* self, c_VertexId_t v) { + if (!self || !self->marked) { + return C_FALSE; + } + return self->marked[v]; +} + +c_err_t c_NonrecursiveDFS_PathTo(const c_NonrecursiveDFS_t* self, c_size_t v, c_VertexIdList_t* path) { + if (!self || !path ) return C_ERR_PARAM; + if (!c_NonrecursiveDFS_HasPathTo(self, v)) return C_ERR_FAIL; + + path->size = 0; + c_size_t current = v; + while (current != self->source) { + if (c_VertexIdList_Append(path, (c_uint_t)current) != C_SUCCESS) { + path->size = 0; + return C_ERR_NOMEM; + } + current = self->edge_to[current]; + } + + if (c_VertexIdList_Append(path, (c_uint_t)self->source) != C_SUCCESS) { + path->size = 0; + return C_ERR_NOMEM; + } + + // Mirror swap to sort sequence output chronologically (source -> v) + c_size_t left = 0; + c_size_t right = path->size - 1; + while (left < right) { + c_uint_t temp = path->array[left]; + path->array[left] = path->array[right]; + path->array[right] = temp; + left++; + right--; + } + + return C_SUCCESS; +} + +c_size_t c_NonrecursiveDFS_Count(const c_NonrecursiveDFS_t* self) { + if (!self) return 0; + return self->count; +} + diff --git a/Graph/c_NonrecursiveDFS.h b/Graph/c_NonrecursiveDFS.h new file mode 100644 index 0000000..81cbe45 --- /dev/null +++ b/Graph/c_NonrecursiveDFS.h @@ -0,0 +1,57 @@ +#ifndef INCLUDED_C_NONRECURSIVEDFS_H +#define INCLUDED_C_NONRECURSIVEDFS_H + +#ifndef INCLUDED_C_GRAPH_H +#include +#endif /*INCLUDED_C_GRAPH_H*/ + +#ifndef INCLUDED_C_VERTEXIDLIST_H +#include +#endif /*INCLUDED_C_VERTEXIDLIST_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_bool_t* marked; // marked[v] = true if v is reachable from source + c_size_t* edge_to; // edge_to[v] = last vertex on path from source to v + c_size_t count; // Total number of vertices connected to source + c_size_t source; // Source root vertex index + c_Allocator_t allocator; // Memory allocator reference instance copy +} c_NonrecursiveDFS_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Computes the vertices connected to a source vertex iteratively. + * @param self Pointer to the uninitialized search state structure. + * @param G Pointer to the constant target graph object to analyze. + * @param s The source root vertex index. + * @param allocator Memory allocator instance pointer to deploy. + * @return C_SUCCESS on success, or an error status code on allocation failure. + */ +c_err_t c_NonrecursiveDFS_Init(c_NonrecursiveDFS_t* self, const c_Graph_t* G, c_VertexId_t s, c_Allocator_t* allocator); + +/** + * @brief Drops all internal allocation states within the search instance safely. + */ +void c_NonrecursiveDFS_Destroy(c_NonrecursiveDFS_t* self); + +/** + * @brief Is there a path between the source vertex and vertex v? + */ +c_bool_t c_NonrecursiveDFS_HasPathTo(const c_NonrecursiveDFS_t* self, c_VertexId_t v); + +/** + * @brief Recovers a path track from the source vertex to target v. + */ +c_err_t c_NonrecursiveDFS_PathTo(const c_NonrecursiveDFS_t* self, c_size_t v, c_VertexIdList_t* path); + +/** + * @brief Returns the total number of vertices structurally connected to the source vertex. + */ +c_size_t c_NonrecursiveDFS_Count(const c_NonrecursiveDFS_t* self); + +#endif /*INCLUDED_C_NONRECURSIVEDFS_H*/ diff --git a/Graph/c_NonrecursiveDFS.t.c b/Graph/c_NonrecursiveDFS.t.c new file mode 100644 index 0000000..6671819 --- /dev/null +++ b/Graph/c_NonrecursiveDFS.t.c @@ -0,0 +1,52 @@ +#include "c_NonrecursiveDFS.h" +#include "c_Test.h" +#include +#include + +TEST_CASE(test_c_nonrecursive_dfs_component) { + c_Graph_t graph; + c_Graph_Init(&graph, 4, 0); + + // Form line path network structure: 0-1, 1-2, 2-3 + c_Graph_AddEdge(&graph, 0, 1); + c_Graph_AddEdge(&graph, 1, 2); + c_Graph_AddEdge(&graph, 2, 3); + + c_NonrecursiveDFS_t search; + c_err_t err = c_NonrecursiveDFS_Init(&search, &graph, 0, 0); + + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_LL_EQ(4, c_NonrecursiveDFS_Count(&search)); // All nodes connected + ASSERT_TRUE(c_NonrecursiveDFS_HasPathTo(&search, 3)); + + c_VertexIdList_t route; + c_VertexIdList_Init(&route, 4, 0); + + c_err_t path_err = c_NonrecursiveDFS_PathTo(&search, 3, &route); + ASSERT_INT_EQ(C_SUCCESS, path_err); + ASSERT_LL_EQ(4, route.size); + + // Verify chronological order sequence matches properly: 0 -> 1 -> 2 -> 3 + c_uint_t node; + c_VertexIdList_Get(&route, 0, &node); ASSERT_LL_EQ(0, node); + c_VertexIdList_Get(&route, 3, &node); ASSERT_LL_EQ(3, node); + + c_VertexIdList_Destroy(&route); + c_NonrecursiveDFS_Destroy(&search); + c_Graph_Destroy(&graph); +} + + + +int main(int argc, char** argv){ + TEST_START(c_NonrecursiveDFS Component Tests); + + // Execution list configurations + RUN_TEST(test_c_nonrecursive_dfs_component); + + TEST_REPORT(); + + RETURN_TEST_STATUS; + + return 0; +} diff --git a/Graph/c_NonrecursiveDirectedCycle.c b/Graph/c_NonrecursiveDirectedCycle.c new file mode 100644 index 0000000..5859899 --- /dev/null +++ b/Graph/c_NonrecursiveDirectedCycle.c @@ -0,0 +1,144 @@ +#include + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +typedef struct { + c_size_t v; /* Current vertex */ + c_size_t edge_idx; /* Next neighbor index to examine in the adjacency list */ +} c_CycleFrame_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/* Private non-recursive DFS engine subroutine */ +static void c_NonrecursiveDirectedCycle_Process(c_NonrecursiveDirectedCycle_t* self, const c_Digraph_t* graph, c_size_t root, c_CycleFrame_t* frame_stack) { + c_size_t stack_size = 0; + + /* Push initial root activation frame entry */ + self->marked[root] = C_TRUE; + self->on_stack[root] = C_TRUE; + frame_stack[stack_size++] = (c_CycleFrame_t){ .v = root, .edge_idx = 0 }; + + while (stack_size > 0) { + c_CycleFrame_t* current_frame = &frame_stack[stack_size - 1]; + c_size_t u = current_frame->v; + + c_AdjList_t* adj = &graph->adj_list[u]; + c_size_t neighbor_count = (c_size_t)c_UIntArray_GetSize(adj); + + c_bool_t advanced = C_FALSE; + while (current_frame->edge_idx < neighbor_count) { + c_uint_t target_value = 0; + c_err_t err = c_UIntArray_Get(adj, current_frame->edge_idx, &target_value); + current_frame->edge_idx++; /* Advance neighbor loop pointer state */ + + if (err == C_SUCCESS) { + c_size_t w = (c_size_t)target_value; + + /* Case A: Found an unvisited branch, push execution block down */ + if (!self->marked[w]) { + self->marked[w] = C_TRUE; + self->on_stack[w] = C_TRUE; + self->edge_to[w] = u; + + frame_stack[stack_size++] = (c_CycleFrame_t){ .v = w, .edge_idx = 0 }; + advanced = C_TRUE; + break; + } + /* Case B: Backedge detected (vertex is still active on stack) -> Cycle Found! */ + else if (self->on_stack[w]) { + c_size_t* reverse_stack = (c_size_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_size_t)); + if (!reverse_stack) return; + + c_size_t trace_size = 0; + for (c_size_t x = u; x != w; x = self->edge_to[x]) { + reverse_stack[trace_size++] = x; + } + reverse_stack[trace_size++] = w; + reverse_stack[trace_size++] = u; + + /* Build proper forward loop trail order inside cycle sequence container */ + while (trace_size > 0) { + c_VertexIdList_Append(&self->cycle, (c_uint_t)reverse_stack[--trace_size]); + } + + c_Allocator_Free(&self->allocator, reverse_stack); + return; /* Instantly yield loop upon detection */ + } + } + } + + /* If short-circuit evaluation trapped a cycle at child nodes, cleanly escape up */ + if (c_NonrecursiveDirectedCycle_HasCycle(self)) return; + + /* If all edges out of vertex u have been fully cleared, pop it from stack tracking */ + if (!advanced) { + self->on_stack[u] = C_FALSE; + stack_size--; + } + } +} + +c_err_t c_NonrecursiveDirectedCycle_Init(c_NonrecursiveDirectedCycle_t* self, const c_Digraph_t* graph, c_Allocator_t* allocator) { + if (!self || !graph) return C_ERR_PARAM; + + self->allocator = allocator ? *allocator : c_DefaultAllocator; + self->marked = (c_bool_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_bool_t)); + self->edge_to = (c_size_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_size_t)); + self->on_stack = (c_bool_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_bool_t)); + + c_VertexIdList_Init(&self->cycle, 0, allocator); + + if (!self->marked || !self->edge_to || !self->on_stack) { + c_NonrecursiveDirectedCycle_Destroy(self); + return C_ERR_NOMEM; + } + + /* Allocate explicit local runtime runtime loop frame stack structure sized O(V) */ + c_CycleFrame_t* frame_stack = (c_CycleFrame_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_CycleFrame_t)); + if (!frame_stack) { + c_NonrecursiveDirectedCycle_Destroy(self); + return C_ERR_NOMEM; + } + + /* Sweep disconnected structural pockets across graph nodes boundary safely */ + for (c_size_t v = 0; v < graph->V; ++v) { + if (!self->marked[v] && !c_NonrecursiveDirectedCycle_HasCycle(self)) { + c_NonrecursiveDirectedCycle_Process(self, graph, v, frame_stack); + } + } + + c_Allocator_Free(&self->allocator, frame_stack); + return C_SUCCESS; +} + +void c_NonrecursiveDirectedCycle_Destroy(c_NonrecursiveDirectedCycle_t* self) { + if (!self) return; + if (self->marked) c_Allocator_Free(&self->allocator, self->marked); + if (self->edge_to) c_Allocator_Free(&self->allocator, self->edge_to); + if (self->on_stack) c_Allocator_Free(&self->allocator, self->on_stack); + + c_VertexIdList_Destroy(&self->cycle); + + self->marked = NULL; + self->edge_to = NULL; + self->on_stack = NULL; +} + +c_err_t c_NonrecursiveDirectedCycle_GetCycle(c_NonrecursiveDirectedCycle_t* self, c_VertexIdList_t* out_cycle) { + if (!self || !out_cycle) return C_ERR_PARAM; + if (!c_NonrecursiveDirectedCycle_HasCycle(self)) return C_ERR_FAIL; + + c_size_t size = (c_size_t)c_VertexIdList_GetSize(&self->cycle); + for (c_size_t i = 0; i < size; ++i) { + c_uint_t val = 0; + c_err_t err = c_VertexIdList_Get((c_VertexIdList_t*)&self->cycle, i, &val); + if (err == C_SUCCESS) { + c_err_t app_err = c_VertexIdList_Append(out_cycle, val); + if (app_err != C_SUCCESS) return app_err; + } + } + return C_SUCCESS; +} diff --git a/Graph/c_NonrecursiveDirectedCycle.h b/Graph/c_NonrecursiveDirectedCycle.h new file mode 100644 index 0000000..4d97e35 --- /dev/null +++ b/Graph/c_NonrecursiveDirectedCycle.h @@ -0,0 +1,55 @@ +#ifndef INCLUDED_C_NONRECURSIVEDIRECTEDCYCLE_H +#define INCLUDED_C_NONRECURSIVEDIRECTEDCYCLE_H + +#ifndef INCLUDED_C_DIGRAPH_H +#include +#endif /*INCLUDED_C_DIGRAPH_H*/ + + +#ifndef INCLUDED_C_VERTEXIDLIST_H +#include +#endif /*INCLUDED_C_VERTEXIDLIST_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_bool_t* marked; /* Visited tracking array (size of graph->V) */ + c_size_t* edge_to; /* edge_to[w] = last edge on path to w */ + c_bool_t* on_stack; /* Keeps track of vertices currently on the mocked DFS stack */ + c_VertexIdList_t cycle; /* Stores the cycle sequence if found */ + c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ +} c_NonrecursiveDirectedCycle_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Non-recursively determines if the digraph has a directed cycle, and if so, finds one. + * @param allocator Explicit allocator context used to allocate internal routing arrays + */ +c_err_t c_NonrecursiveDirectedCycle_Init(c_NonrecursiveDirectedCycle_t* self, const c_Digraph_t* graph, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers + */ +void c_NonrecursiveDirectedCycle_Destroy(c_NonrecursiveDirectedCycle_t* self); + +/** + * @brief Does the digraph have a directed cycle? + */ +C_STATIC_FORCE_INLINE +c_bool_t c_NonrecursiveDirectedCycle_HasCycle(c_NonrecursiveDirectedCycle_t* self) { + if (!self) return C_FALSE; + return !c_VertexIdList_IsEmpty(&self->cycle); +} + +/** + * @brief Gets the vertices on a directed cycle. + * @param out_cycle An initialized c_VertexIdList_t container to collect the sequence. + */ +c_err_t c_NonrecursiveDirectedCycle_GetCycle(c_NonrecursiveDirectedCycle_t* self, c_VertexIdList_t* out_cycle); + + +#endif /*INCLUDED_C_NONRECURSIVEDIRECTEDCYCLE_H*/ diff --git a/Graph/c_NonrecursiveDirectedCycle.t.c b/Graph/c_NonrecursiveDirectedCycle.t.c new file mode 100644 index 0000000..af85bc5 --- /dev/null +++ b/Graph/c_NonrecursiveDirectedCycle.t.c @@ -0,0 +1,46 @@ +#include "c_Test.h" +#include "c_Digraph.h" +#include "c_NonrecursiveDirectedCycle.h" +#include "c_VertexIdList.h" + +TEST_CASE(test_nonrecursive_cycle_detection) { + c_Digraph_t g; + c_Digraph_Init(&g, 3, NULL); + + /* Construct an evaluation loop: 0 -> 1 -> 2 -> 0 */ + c_Digraph_AddEdge(&g, 0, 1); + c_Digraph_AddEdge(&g, 1, 2); + c_Digraph_AddEdge(&g, 2, 0); + + c_NonrecursiveDirectedCycle_t detector; + c_err_t err = c_NonrecursiveDirectedCycle_Init(&detector, &g, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + ASSERT_TRUE(c_NonrecursiveDirectedCycle_HasCycle(&detector)); + + c_VertexIdList_t extracted; + c_VertexIdList_Init(&extracted, 0, NULL); + + err = c_NonrecursiveDirectedCycle_GetCycle(&detector, &extracted); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Assert structural consistency (Loops through 4 steps back to index root origin) */ + ASSERT_INT_EQ(4, c_VertexIdList_GetSize(&extracted)); + + c_VertexIdList_Destroy(&extracted); + c_NonrecursiveDirectedCycle_Destroy(&detector); + c_Digraph_Destroy(&g); +} + + +int main(int argc, char** argv){ + + TEST_START(Component Tests); + + // Execution list configurations + RUN_TEST(test_nonrecursive_cycle_detection); + + TEST_REPORT(); + + RETURN_TEST_STATUS; +} diff --git a/Graph/c_NonrecursiveDirectedDFS.c b/Graph/c_NonrecursiveDirectedDFS.c new file mode 100644 index 0000000..f0ccab0 --- /dev/null +++ b/Graph/c_NonrecursiveDirectedDFS.c @@ -0,0 +1,88 @@ +#include +#include + +typedef struct { + c_size_t v; /* Current vertex */ + c_size_t edge_idx; /* Next neighbor index to examine in the adjacency list */ +} c_DFSFrame_t; + +c_err_t c_NonrecursiveDirectedDFS_Init(c_NonrecursiveDirectedDFS_t* self, c_Digraph_t* graph, c_size_t s, c_Allocator_t* allocator) { + if (!self || !graph || s >= graph->V) return C_ERR_PARAM; + + self->allocator = allocator?*allocator:c_DefaultAllocator; + self->count = 0; + + /* 1. Allocate the visited tracking array */ + self->marked = (c_bool_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_bool_t)); + if (!self->marked) return C_ERR_NOMEM; + + /* 2. Allocate an explicit execution stack bounded exactly by the number of vertices O(V) */ + c_DFSFrame_t frame={0}; + + c_ArrayStack_t stack; + c_err_t err = c_ArrayStack_Init(&stack, sizeof(c_DFSFrame_t), 0, allocator); + if (err!=C_ERR_OK) { + c_Allocator_Free(&self->allocator, self->marked); + self->marked = NULL; + return err; + } + + /* Push the initial source vertex frame onto our stack */ + self->marked[s] = C_TRUE; + self->count++; + + frame.v = s; + frame.edge_idx = 0; + c_ArrayStack_Push(&stack, &frame); + + /* 3. Non-recursive processing loop */ + while (!c_ArrayStack_IsEmpty(&stack)) { + /* Peek the top frame */ + c_DFSFrame_t* current_frame = c_ArrayStack_Peek(&stack); + + c_size_t u = current_frame->v; + + c_AdjList_t* adj = &graph->adj_list[u]; + c_size_t neighbor_count = (c_size_t)c_AdjList_GetSize(adj); + + /* Advance down the adjacency list until we find an unvisited neighbor or finish the list */ + c_bool_t advanced = C_FALSE; + while (current_frame->edge_idx < neighbor_count) { + c_size_t w; + c_AdjList_Get(adj, current_frame->edge_idx, &w); + current_frame->edge_idx++; /* Increment pointer for next pass */ + + if (!self->marked[w]) { + self->marked[w] = C_TRUE; + self->count++; + + /* Push new frame onto the stack (equivalent to recursive call invocation) */ + frame.v = w; + frame.edge_idx = 0; + c_ArrayStack_Push(&stack, &frame); + + advanced = C_TRUE; + break; + } + } + + /* If we explored all neighbors of vertex u, pop it from the stack */ + if (!advanced) { + c_ArrayStack_Pop(&stack, 0); + } + } + + /* Clean up the runtime frame stack buffer */ + c_ArrayStack_Destroy(&stack); + return C_SUCCESS; +} + +void c_NonrecursiveDirectedDFS_Destroy(c_NonrecursiveDirectedDFS_t* self) { + if (!self) return; + if (self->marked) { + c_Allocator_Free(&self->allocator, self->marked); + self->marked = NULL; + } + self->count = 0; +} + diff --git a/Graph/c_NonrecursiveDirectedDFS.h b/Graph/c_NonrecursiveDirectedDFS.h new file mode 100644 index 0000000..3b582fe --- /dev/null +++ b/Graph/c_NonrecursiveDirectedDFS.h @@ -0,0 +1,43 @@ +#ifndef INCLUDED_C_NONRECURSIVEDIRECTEDDFS_H +#define INCLUDED_C_NONRECURSIVEDIRECTEDDFS_H + +#ifndef INCLUDED_C_DIGRAPH_H +#include +#endif /*INCLUDED_C_DIGRAPH_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_bool_t* marked; /* Visited tracking array (size of graph->V) */ + c_size_t count; /* Total number of vertices reachable from source(s) */ + c_Allocator_t allocator; /* Copied allocator from the graph for isolation */ +} c_NonrecursiveDirectedDFS_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Computes vertices reachable from a single source vertex 's' using a stack + */ +c_err_t c_NonrecursiveDirectedDFS_Init(c_NonrecursiveDirectedDFS_t* self, c_Digraph_t* graph, c_size_t s, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers + */ +void c_NonrecursiveDirectedDFS_Destroy(c_NonrecursiveDirectedDFS_t* self); + +/* ================================================================================================================== */ +/* Inline Query Interfaces */ + +C_STATIC_FORCE_INLINE c_bool_t c_NonrecursiveDirectedDFS_HasPathTo(c_NonrecursiveDirectedDFS_t* self, c_size_t v, c_size_t total_V) { + if (!self || !self->marked || v >= total_V) return C_FALSE; + return self->marked[v]; +} + +C_STATIC_FORCE_INLINE c_size_t c_NonrecursiveDirectedDFS_GetCount(c_NonrecursiveDirectedDFS_t* self) { + return self ? self->count : 0; +} + +#endif /*INCLUDED_C_NONRECURSIVEDIRECTEDDFS_H*/ diff --git a/Graph/c_NonrecursiveDirectedDFS.t.c b/Graph/c_NonrecursiveDirectedDFS.t.c new file mode 100644 index 0000000..5eb3935 --- /dev/null +++ b/Graph/c_NonrecursiveDirectedDFS.t.c @@ -0,0 +1,46 @@ +#include "c_NonrecursiveDirectedDFS.h" +#include "c_Test.h" + +#include +#include + + +TEST_CASE(test_nonrecursive_dfs) { + c_Digraph_t g; + c_Digraph_Init(&g, 5, NULL); + + /* Construct a graph topology: + 0 -> 1 -> 2 + 0 -> 3 + 4 (Isolated node) + */ + c_Digraph_AddEdge(&g, 0, 1); + c_Digraph_AddEdge(&g, 1, 2); + c_Digraph_AddEdge(&g, 0, 3); + + c_NonrecursiveDirectedDFS_t dfs; + c_err_t err = c_NonrecursiveDirectedDFS_Init(&dfs, &g, 0, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Assert paths */ + ASSERT_TRUE(c_NonrecursiveDirectedDFS_HasPathTo(&dfs, 2, g.V)); + ASSERT_TRUE(c_NonrecursiveDirectedDFS_HasPathTo(&dfs, 3, g.V)); + ASSERT_FALSE(c_NonrecursiveDirectedDFS_HasPathTo(&dfs, 4, g.V)); + ASSERT_INT_EQ(4, c_NonrecursiveDirectedDFS_GetCount(&dfs)); + + c_NonrecursiveDirectedDFS_Destroy(&dfs); + c_Digraph_Destroy(&g); +} + + +int main(int argc, char** argv){ + + TEST_START(c_NonrecursiveDFS Component Tests); + + // Execution list configurations + RUN_TEST(test_nonrecursive_dfs); + + TEST_REPORT(); + + RETURN_TEST_STATUS; +} diff --git a/Graph/c_NonrecursiveGabowSCC.c b/Graph/c_NonrecursiveGabowSCC.c new file mode 100644 index 0000000..884a23b --- /dev/null +++ b/Graph/c_NonrecursiveGabowSCC.c @@ -0,0 +1,141 @@ +#include + + +typedef struct { + c_size_t v; /* Current vertex */ + c_size_t edge_idx; /* Next neighbor index to examine in the adjacency list */ +} c_GabowFrame_t; + +/* Private iterative DFS engine subroutine */ +static void c_NonrecursiveGabowSCC_Process(c_NonrecursiveGabowSCC_t* self, c_Digraph_t* graph, c_size_t root, c_GabowFrame_t* frame_stack) { + c_size_t frame_stack_size = 0; + + /* Push the initial root activation frame entry */ + self->marked[root] = C_TRUE; + self->pre[root] = self->pre_counter++; + self->scc_stack[self->scc_stack_sz++] = root; + self->path_stack[self->path_stack_sz++] = root; + + frame_stack[frame_stack_size++] = (c_GabowFrame_t){ .v = root, .edge_idx = 0 }; + + while (frame_stack_size > 0) { + c_GabowFrame_t* current_frame = &frame_stack[frame_stack_size - 1]; + c_size_t u = current_frame->v; + + c_AdjList_t* adj = &graph->adj_list[u]; + c_size_t neighbor_count = (c_size_t)c_UIntArray_GetSize(adj); + + c_bool_t advanced = C_FALSE; + while (current_frame->edge_idx < neighbor_count) { + c_uint_t target_value = 0; + c_err_t err = c_UIntArray_Get(adj, current_frame->edge_idx, &target_value); + current_frame->edge_idx++; /* Advance edge iterator pointer state */ + + if (err == C_SUCCESS) { + c_size_t w = (c_size_t)target_value; + + if (!self->marked[w]) { + self->marked[w] = C_TRUE; + self->pre[w] = self->pre_counter++; + self->scc_stack[self->scc_stack_sz++] = w; + self->path_stack[self->path_stack_sz++] = w; + + /* Push new execution frame onto the state stack (Simulating Recursive Call) */ + frame_stack[frame_stack_size++] = (c_GabowFrame_t){ .v = w, .edge_idx = 0 }; + advanced = C_TRUE; + break; + } + else if (self->id[w] == C_SIZE_MAX) { + /* Contract the path tracking stack for active cross-links or backedges */ + while (self->path_stack_sz > 0 && self->pre[self->path_stack[self->path_stack_sz - 1]] > self->pre[w]) { + self->path_stack_sz--; + } + } + } + } + + if (advanced) continue; /* Control passes down into child node execution branch */ + + /* Post-visit processing block (Unwinding) */ + frame_stack_size--; + + /* If u is detected as the root boundary of an active SCC cluster component */ + if (self->path_stack_sz > 0 && self->path_stack[self->path_stack_sz - 1] == u) { + self->path_stack_sz--; /* Pop from the active root tracking stack */ + + c_size_t component_node = 0; + do { + component_node = self->scc_stack[--self->scc_stack_sz]; + self->id[component_node] = self->count; + } while (component_node != u); + + self->count++; /* Advance final SCC group ID counter label */ + } + } +} + +c_err_t c_NonrecursiveGabowSCC_Init(c_NonrecursiveGabowSCC_t* self, c_Digraph_t* graph, c_Allocator_t* allocator) { + if (!self || !graph) return C_ERR_PARAM; + + self->allocator = allocator ? *allocator : c_DefaultAllocator; + self->count = 0; + self->pre_counter = 0; + self->scc_stack_sz = 0; + self->path_stack_sz = 0; + self->V = graph->V; + + if (self->V == 0) return C_SUCCESS; + + /* 1. Allocate all necessary analytical tracking structures */ + self->marked = (c_bool_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_bool_t)); + self->id = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + self->pre = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + self->scc_stack = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + self->path_stack = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + + if (!self->marked || !self->id || !self->pre || !self->scc_stack || !self->path_stack) { + c_NonrecursiveGabowSCC_Destroy(self); + return C_ERR_NOMEM; + } + + /* Assign unmapped initialization state labels across all indices */ + for (c_size_t i = 0; i < self->V; ++i) { + self->id[i] = C_SIZE_MAX; + } + + /* Allocate continuous explicit compiler-emulated frame scratch buffer stack O(V) */ + c_GabowFrame_t* frame_stack = (c_GabowFrame_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_GabowFrame_t)); + if (!frame_stack) { + c_NonrecursiveGabowSCC_Destroy(self); + return C_ERR_NOMEM; + } + + /* 2. Process all components securely */ + for (c_size_t v = 0; v < self->V; ++v) { + if (!self->marked[v]) { + c_NonrecursiveGabowSCC_Process(self, graph, v, frame_stack); + } + } + + c_Allocator_Free(&self->allocator, frame_stack); + return C_SUCCESS; +} + +void c_NonrecursiveGabowSCC_Destroy(c_NonrecursiveGabowSCC_t* self) { + if (!self) return; + if (self->marked) c_Allocator_Free(&self->allocator, self->marked); + if (self->id) c_Allocator_Free(&self->allocator, self->id); + if (self->pre) c_Allocator_Free(&self->allocator, self->pre); + if (self->scc_stack) c_Allocator_Free(&self->allocator, self->scc_stack); + if (self->path_stack) c_Allocator_Free(&self->allocator, self->path_stack); + + self->marked = NULL; + self->id = NULL; + self->pre = NULL; + self->scc_stack = NULL; + self->path_stack = NULL; + self->count = 0; + self->scc_stack_sz = 0; + self->path_stack_sz = 0; + self->V = 0; +} diff --git a/Graph/c_NonrecursiveGabowSCC.h b/Graph/c_NonrecursiveGabowSCC.h new file mode 100644 index 0000000..9a0c630 --- /dev/null +++ b/Graph/c_NonrecursiveGabowSCC.h @@ -0,0 +1,67 @@ +#ifndef INCLUDED_C_NONRECURSIVEGABOWSCC_H +#define INCLUDED_C_NONRECURSIVEGABOWSCC_H + +#ifndef INCLUDED_C_DIGRAPH_H +#include +#endif /*INCLUDED_C_DIGRAPH_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_bool_t* marked; /* Visited tracking array (size of graph->V) */ + c_size_t* id; /* id[v] = component identifier containing v (0 to count-1) */ + c_size_t* pre; /* pre[v] = preorder index of vertex v */ + c_size_t* scc_stack; /* Stack to keep track of vertices in the current SCC candidate */ + c_size_t scc_stack_sz; /* Size of scc_stack */ + c_size_t* path_stack; /* Stack to determine root of strongly connected components */ + c_size_t path_stack_sz;/* Size of path_stack */ + c_size_t pre_counter; /* Preorder index sequencing counter */ + c_size_t count; /* Total number of strongly connected components discovered */ + c_size_t V; /* Stored vertex count for safe boundary check lookups */ + c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ +} c_NonrecursiveGabowSCC_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Non-recursively computes strongly connected components for the specified digraph using Gabow's algorithm. + * @param allocator Explicit allocator context used to allocate internal routing arrays + */ +c_err_t c_NonrecursiveGabowSCC_Init(c_NonrecursiveGabowSCC_t* self, c_Digraph_t* graph, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers + */ +void c_NonrecursiveGabowSCC_Destroy(c_NonrecursiveGabowSCC_t* self); + +/** + * @brief Are vertices 'v' and 'w' strongly connected (belong to the same SCC)? + */ +C_STATIC_FORCE_INLINE +c_bool_t c_NonrecursiveGabowSCC_StronglyConnected(c_NonrecursiveGabowSCC_t* self, c_size_t v, c_size_t w) { + if (!self || !self->id || v >= self->V || w >= self->V) return C_FALSE; + return self->id[v] == self->id[w]; +} + +/** + * @brief Returns the component id containing vertex 'v' + * @return Component integer ID (0 to count-1), or C_SIZE_MAX on boundary check failures + */ +C_STATIC_FORCE_INLINE +c_size_t c_NonrecursiveGabowSCC_GetId(const c_NonrecursiveGabowSCC_t* self, c_size_t v) { + if (!self || !self->id || v >= self->V) return C_SIZE_MAX; + return self->id[v]; +} + +/** + * @brief Returns the total number of strongly connected components + */ +C_STATIC_FORCE_INLINE +c_size_t c_NonrecursiveGabowSCC_GetCount(const c_NonrecursiveGabowSCC_t* self) { + return self ? self->count : 0; +} + +#endif /*INCLUDED_C_NONRECURSIVEGABOWSCC_H*/ diff --git a/Graph/c_NonrecursiveGabowSCC.t.c b/Graph/c_NonrecursiveGabowSCC.t.c new file mode 100644 index 0000000..7b7eba2 --- /dev/null +++ b/Graph/c_NonrecursiveGabowSCC.t.c @@ -0,0 +1,51 @@ +#include "c_Test.h" +#include "c_Digraph.h" +#include "c_NonrecursiveGabowSCC.h" + +TEST_CASE(test_nonrecursive_gabow_scc) { + c_Digraph_t g; + c_err_t err = c_Digraph_Init(&g, 5, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Setup cyclic component tracking bounds: + * Component 1: 0 -> 1 -> 2 -> 0 + * Exit Bridge: 2 -> 3 + * Component 2: 3 -> 4 -> 3 + */ + c_Digraph_AddEdge(&g, 0, 1); + c_Digraph_AddEdge(&g, 1, 2); + c_Digraph_AddEdge(&g, 2, 0); + c_Digraph_AddEdge(&g, 2, 3); + c_Digraph_AddEdge(&g, 3, 4); + c_Digraph_AddEdge(&g, 4, 3); + + c_NonrecursiveGabowSCC_t scc; + err = c_NonrecursiveGabowSCC_Init(&scc, &g, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Verify proper isolation partitions count values */ + ASSERT_LL_EQ(2, c_NonrecursiveGabowSCC_GetCount(&scc)); + + /* Verify connected state mappings */ + ASSERT_TRUE(c_NonrecursiveGabowSCC_StronglyConnected(&scc, 0, 1)); + ASSERT_TRUE(c_NonrecursiveGabowSCC_StronglyConnected(&scc, 1, 2)); + ASSERT_TRUE(c_NonrecursiveGabowSCC_StronglyConnected(&scc, 3, 4)); + ASSERT_FALSE(c_NonrecursiveGabowSCC_StronglyConnected(&scc, 0, 3)); + + c_NonrecursiveGabowSCC_Destroy(&scc); + c_Digraph_Destroy(&g); +} + +int main(void) { + /* Bootstrapping test environment wrapper */ + TEST_START(Test); + + /* Run specified edge cases */ + RUN_TEST(test_nonrecursive_gabow_scc); + + /* Generate total logging matrix summaries sheet */ + TEST_REPORT(); + + /* Unwind tracking status frames code signals */ + RETURN_TEST_STATUS; +} \ No newline at end of file diff --git a/Graph/c_NonrecursiveTarjanSCC.c b/Graph/c_NonrecursiveTarjanSCC.c new file mode 100644 index 0000000..bce48d4 --- /dev/null +++ b/Graph/c_NonrecursiveTarjanSCC.c @@ -0,0 +1,138 @@ +#include +#include + +typedef struct { + c_size_t v; /* Current vertex */ + c_size_t edge_idx; /* Next neighbor index to examine in the adjacency list */ +} c_TarjanFrame_t; + +/* Private iterative DFS engine subroutine */ +static void c_NonrecursiveTarjanSCC_Process(c_NonrecursiveTarjanSCC_t* self, c_Digraph_t* graph, c_size_t root, c_TarjanFrame_t* frame_stack) { + c_size_t frame_stack_size = 0; + + /* Push the initial root activation frame entry */ + self->marked[root] = C_TRUE; + self->pre[root] = self->pre_counter++; + self->low[root] = self->pre[root]; + self->stack[self->stack_size++] = root; + self->on_stack[root] = C_TRUE; + + frame_stack[frame_stack_size++] = (c_TarjanFrame_t){ .v = root, .edge_idx = 0 }; + + while (frame_stack_size > 0) { + c_TarjanFrame_t* current_frame = &frame_stack[frame_stack_size - 1]; + c_size_t u = current_frame->v; + + c_AdjList_t* adj = &graph->adj_list[u]; + c_size_t neighbor_count = (c_size_t)c_AdjList_GetSize(adj); + + c_bool_t advanced = C_FALSE; + while (current_frame->edge_idx < neighbor_count) { + c_uint_t target_value = 0; + c_err_t err = c_AdjList_Get(adj, current_frame->edge_idx, &target_value); + current_frame->edge_idx++; /* Advance edge iterator pointer state */ + + if (err == C_SUCCESS) { + c_size_t w = (c_size_t)target_value; + + if (!self->marked[w]) { + self->marked[w] = C_TRUE; + self->pre[w] = self->pre_counter++; + self->low[w] = self->pre[w]; + self->stack[self->stack_size++] = w; + self->on_stack[w] = C_TRUE; + + /* Push new execution frame onto the state stack (Simulating Recursive Call) */ + frame_stack[frame_stack_size++] = (c_TarjanFrame_t){ .v = w, .edge_idx = 0 }; + advanced = C_TRUE; + break; + } + else if (self->on_stack[w]) { + self->low[u] = C_MIN(self->low[u], self->pre[w]); + } + } + } + + if (advanced) continue; /* Yield control forward down into the child node branch */ + + /* Post-visit Processing (Equivalent to tracking back up the recursive unwinding stack) */ + frame_stack_size--; /* Pop frame context */ + + if (frame_stack_size > 0) { + c_size_t parent = frame_stack[frame_stack_size - 1].v; + self->low[parent] = C_MIN(self->low[parent], self->low[u]); + } + + /* If u is a root node of an SCC, pop all component nodes off the membership tracker */ + if (self->low[u] == self->pre[u]) { + c_size_t component_node = 0; + do { + component_node = self->stack[--self->stack_size]; + self->id[component_node] = self->count; + self->on_stack[component_node] = C_FALSE; + } while (component_node != u); + + self->count++; /* Advance component group numbering identity mapping */ + } + } +} + +c_err_t c_NonrecursiveTarjanSCC_Init(c_NonrecursiveTarjanSCC_t* self, c_Digraph_t* graph, c_Allocator_t* allocator) { + if (!self || !graph) return C_ERR_PARAM; + + self->allocator = allocator ? *allocator : c_DefaultAllocator; + self->count = 0; + self->pre_counter = 0; + self->stack_size = 0; + self->V = graph->V; + + if (self->V == 0) return C_SUCCESS; + + self->marked = (c_bool_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_bool_t)); + self->id = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + self->pre = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + self->low = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + self->stack = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + self->on_stack = (c_bool_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_bool_t)); + + if (!self->marked || !self->id || !self->pre || !self->low || !self->stack || !self->on_stack) { + c_NonrecursiveTarjanSCC_Destroy(self); + return C_ERR_NOMEM; + } + + /* Allocate local frame execution engine tracking context sized O(V) */ + c_TarjanFrame_t* frame_stack = (c_TarjanFrame_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_TarjanFrame_t)); + if (!frame_stack) { + c_NonrecursiveTarjanSCC_Destroy(self); + return C_ERR_NOMEM; + } + + for (c_size_t v = 0; v < self->V; ++v) { + if (!self->marked[v]) { + c_NonrecursiveTarjanSCC_Process(self, graph, v, frame_stack); + } + } + + c_Allocator_Free(&self->allocator, frame_stack); + return C_SUCCESS; +} + +void c_NonrecursiveTarjanSCC_Destroy(c_NonrecursiveTarjanSCC_t* self) { + if (!self) return; + if (self->marked) c_Allocator_Free(&self->allocator, self->marked); + if (self->id) c_Allocator_Free(&self->allocator, self->id); + if (self->pre) c_Allocator_Free(&self->allocator, self->pre); + if (self->low) c_Allocator_Free(&self->allocator, self->low); + if (self->stack) c_Allocator_Free(&self->allocator, self->stack); + if (self->on_stack) c_Allocator_Free(&self->allocator, self->on_stack); + + self->marked = NULL; + self->id = NULL; + self->pre = NULL; + self->low = NULL; + self->stack = NULL; + self->on_stack = NULL; + self->count = 0; + self->stack_size = 0; + self->V = 0; +} diff --git a/Graph/c_NonrecursiveTarjanSCC.h b/Graph/c_NonrecursiveTarjanSCC.h new file mode 100644 index 0000000..2b37d3f --- /dev/null +++ b/Graph/c_NonrecursiveTarjanSCC.h @@ -0,0 +1,67 @@ +#ifndef INCLUDED_C_NONRECURSIVETARJANSCC_H +#define INCLUDED_C_NONRECURSIVETARJANSCC_H + +#ifndef INCLUDED_C_DIGRAPH_H +#include +#endif /*INCLUDED_C_DIGRAPH_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_bool_t* marked; /* Visited tracking array (size of graph->V) */ + c_size_t* id; /* id[v] = component identifier containing v (0 to count-1) */ + c_size_t* pre; /* pre[v] = preorder index of vertex v */ + c_size_t* low; /* low[v] = low link of vertex v */ + c_size_t* stack; /* Explicit vertex stack buffer for component mapping tracking */ + c_size_t stack_size; /* Current active elements size in stack tracker */ + c_bool_t* on_stack; /* Quick boolean lookup mapping for stack memberships */ + c_size_t pre_counter; /* Preorder index sequencing index variable counter */ + c_size_t count; /* Total number of strongly connected components discovered */ + c_size_t V; /* Stored vertex count for safe boundary check lookups */ + c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ +} c_NonrecursiveTarjanSCC_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Non-recursively computes strongly connected components for the specified digraph using Tarjan's algorithm. + * @param allocator Explicit allocator context used to allocate internal routing arrays + */ +c_err_t c_NonrecursiveTarjanSCC_Init(c_NonrecursiveTarjanSCC_t* self, c_Digraph_t* graph, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers + */ +void c_NonrecursiveTarjanSCC_Destroy(c_NonrecursiveTarjanSCC_t* self); + +/** + * @brief Are vertices 'v' and 'w' strongly connected (belong to the same SCC)? + */ +C_STATIC_FORCE_INLINE +c_bool_t c_NonrecursiveTarjanSCC_StronglyConnected(c_NonrecursiveTarjanSCC_t* self, c_size_t v, c_size_t w) { + if (!self || !self->id || v >= self->V || w >= self->V) return C_FALSE; + return self->id[v] == self->id[w]; +} + +/** + * @brief Returns the component id containing vertex 'v' + * @return Component integer ID (0 to count-1), or C_SIZE_MAX on boundary check failures + */ +C_STATIC_FORCE_INLINE +c_size_t c_NonrecursiveTarjanSCC_GetId(c_NonrecursiveTarjanSCC_t* self, c_size_t v) { + if (!self || !self->id || v >= self->V) return C_SIZE_MAX; + return self->id[v]; +} + +/** + * @brief Returns the total number of strongly connected components + */ +C_STATIC_FORCE_INLINE +c_size_t c_NonrecursiveTarjanSCC_GetCount(c_NonrecursiveTarjanSCC_t* self) { + return self ? self->count : 0; +} + +#endif /*INCLUDED_C_NONRECURSIVETARJANSCC_H*/ diff --git a/Graph/c_NonrecursiveTarjanSCC.t.c b/Graph/c_NonrecursiveTarjanSCC.t.c new file mode 100644 index 0000000..79d2f4d --- /dev/null +++ b/Graph/c_NonrecursiveTarjanSCC.t.c @@ -0,0 +1,77 @@ +#include "c_Test.h" +#include "c_Digraph.h" +#include "c_NonrecursiveTarjanSCC.h" + +TEST_CASE(test_tarjan_scc_with_internal_v_bounds) { + c_Digraph_t g; + + /* 1. Initialize a directed graph with 5 vertices using default allocator */ + c_err_t err = c_Digraph_Init(&g, 5, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* + * 2. Construct a topology containing 2 separate SCC groups: + * Component 1 (Cyclic Loop): 0 -> 1 -> 2 -> 0 + * Exit Bridge Connection: 2 -> 3 + * Component 2 (Cyclic Loop): 3 -> 4 -> 3 + */ + c_Digraph_AddEdge(&g, 0, 1); + c_Digraph_AddEdge(&g, 1, 2); + c_Digraph_AddEdge(&g, 2, 0); + + c_Digraph_AddEdge(&g, 2, 3); /* Bridge edge crossing components */ + + c_Digraph_AddEdge(&g, 3, 4); + c_Digraph_AddEdge(&g, 4, 3); + + /* 3. Initialize Tarjan's solver (passing graph's inner allocator wrapper) */ + c_NonrecursiveTarjanSCC_t scc; + err = c_NonrecursiveTarjanSCC_Init(&scc, &g, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* 4. Validate that exactly 2 distinct strong component groups are captured */ + c_size_t scc_count = c_NonrecursiveTarjanSCC_GetCount(&scc); + ASSERT_LL_EQ(2, scc_count); + + /* 5. Verify connectivity identities using your sleek inline signatures */ + /* Vertices 0, 1, and 2 should share identical cluster mapping configurations */ + ASSERT_TRUE(c_NonrecursiveTarjanSCC_StronglyConnected(&scc, 0, 1)); + ASSERT_TRUE(c_NonrecursiveTarjanSCC_StronglyConnected(&scc, 1, 2)); + ASSERT_TRUE(c_NonrecursiveTarjanSCC_StronglyConnected(&scc, 0, 2)); + + /* Vertices 3 and 4 should be mapped to the exact same cluster ID */ + ASSERT_TRUE(c_NonrecursiveTarjanSCC_StronglyConnected(&scc, 3, 4)); + + /* Vertices 0 and 3 should be structurally separate */ + ASSERT_FALSE(c_NonrecursiveTarjanSCC_StronglyConnected(&scc, 0, 3)); + + /* 6. Validate safe ID extraction limits via c_NonrecursiveTarjanSCC_GetId */ + c_size_t id_0 = c_NonrecursiveTarjanSCC_GetId(&scc, 0); + c_size_t id_3 = c_NonrecursiveTarjanSCC_GetId(&scc, 3); + + ASSERT_TRUE(id_0 != C_SIZE_MAX); + ASSERT_TRUE(id_3 != C_SIZE_MAX); + ASSERT_TRUE(id_0 != id_3); /* Component IDs must be uniquely distinct */ + + /* Verify that bounds protection prevents out-of-bounds index corruption */ + c_size_t invalid_id = c_NonrecursiveTarjanSCC_GetId(&scc, 99); + ASSERT_LL_EQ(C_SIZE_MAX, invalid_id); + + /* 7. Free and recycle array components tracking states safely */ + c_NonrecursiveTarjanSCC_Destroy(&scc); + c_Digraph_Destroy(&g); +} + +int main(void) { + /* Bootstrapping test environment wrapper */ + TEST_START(TarjanSCC_Refactored_Internal_V_Tests); + + /* Run specified edge cases */ + RUN_TEST(test_tarjan_scc_with_internal_v_bounds); + + /* Generate total logging matrix summaries sheet */ + TEST_REPORT(); + + /* Unwind tracking status frames code signals */ + RETURN_TEST_STATUS; +} diff --git a/Graph/c_NonrecursiveTopological.c b/Graph/c_NonrecursiveTopological.c new file mode 100644 index 0000000..9c37953 --- /dev/null +++ b/Graph/c_NonrecursiveTopological.c @@ -0,0 +1,92 @@ +#include +#include "c_NonrecursiveDirectedCycle.h" + +c_err_t c_NonrecursiveTopological_Init(c_NonrecursiveTopological_t* self, c_Digraph_t* graph, c_Allocator_t* allocator) { + if (!self || !graph) return C_ERR_PARAM; + + self->allocator = allocator ? *allocator : c_DefaultAllocator; + self->has_order = C_FALSE; + c_VertexIdList_Init(&self->order, 0, allocator); + + /* 1. Stack-safe Cycle Check: Ensure the graph is a DAG without relying on recursion stack */ + c_NonrecursiveDirectedCycle_t cycle_detector; + c_err_t err = c_NonrecursiveDirectedCycle_Init(&cycle_detector, graph, allocator); + if (err != C_SUCCESS) return err; + + c_bool_t has_cycle = c_NonrecursiveDirectedCycle_HasCycle(&cycle_detector); + c_NonrecursiveDirectedCycle_Destroy(&cycle_detector); + + if (has_cycle) { + return C_SUCCESS; /* Contains a cycle, return gracefully with has_order = C_FALSE */ + } + + /* 2. Kahn's Algorithm: Allocate a mutable working array copy of in-degrees */ + c_size_t* working_indegree = (c_size_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_size_t)); + if (!working_indegree) return C_ERR_NOMEM; + + /* A simple array-based queue bounded tightly by O(V) */ + c_size_t* zero_in_degree_queue = (c_size_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_size_t)); + if (!zero_in_degree_queue) { + c_Allocator_Free(&self->allocator, working_indegree); + return C_ERR_NOMEM; + } + + c_size_t head = 0; + c_size_t tail = 0; + + /* Initialize working in-degrees and seed the queue with source nodes (in-degree == 0) */ + for (c_size_t v = 0; v < graph->V; ++v) { + working_indegree[v] = c_Digraph_GetInDegree(graph, v); + if (working_indegree[v] == 0) { + zero_in_degree_queue[tail++] = v; + } + } + + /* 3. Non-recursive processing loop */ + while (head < tail) { + c_size_t u = zero_in_degree_queue[head++]; + + /* Append the processed node to the topological ordering stream */ + c_VertexIdList_Append(&self->order, (c_uint_t)u); + + c_AdjList_t* adj = &graph->adj_list[u]; + c_size_t size = (c_size_t)c_AdjList_GetSize(adj); + + /* Decrement in-degree for all downstream neighbors */ + for (c_size_t i = 0; i < size; ++i) { + c_uint_t target_value = 0; + err = c_AdjList_Get(adj, i, &target_value); + + if (err == C_SUCCESS) { + c_size_t w = (c_size_t)target_value; + working_indegree[w]--; + + /* If all parent dependencies are cleared, enqueue the neighbor */ + if (working_indegree[w] == 0) { + zero_in_degree_queue[tail++] = w; + } + } + } + } + + /* Clean up local working tracking allocations */ + c_Allocator_Free(&self->allocator, working_indegree); + c_Allocator_Free(&self->allocator, zero_in_degree_queue); + + self->has_order = C_TRUE; + return C_SUCCESS; +} + +void c_NonrecursiveTopological_Destroy(c_NonrecursiveTopological_t* self) { + if (!self) return; + c_VertexIdList_Destroy(&self->order); + self->has_order = C_FALSE; +} + +c_err_t c_NonrecursiveTopological_GetOrder(c_NonrecursiveTopological_t* self, c_VertexIdList_t* out_order) { + if (!self || !out_order) return C_ERR_PARAM; + if (!self->has_order) return C_ERR_FAIL; + + /* High-speed block copy optimization */ + return c_VertexIdList_Copy(out_order, &self->order); +} diff --git a/Graph/c_NonrecursiveTopological.h b/Graph/c_NonrecursiveTopological.h new file mode 100644 index 0000000..264c3ce --- /dev/null +++ b/Graph/c_NonrecursiveTopological.h @@ -0,0 +1,53 @@ +#ifndef INCLUDED_C_NONRECURSIVETOPOLOGICAL_H +#define INCLUDED_C_NONRECURSIVETOPOLOGICAL_H + +#ifndef INCLUDED_C_DIGRAPH_H +#include +#endif /*INCLUDED_C_DIGRAPH_H*/ + + +#ifndef INCLUDED_C_VERTEXIDLIST_H +#include +#endif /*INCLUDED_C_VERTEXIDLIST_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_VertexIdList_t order; /* Stores the topological order sequence if found */ + c_bool_t has_order; /* Is the graph a DAG (has a valid topological order)? */ + c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ +} c_NonrecursiveTopological_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Non-recursively computes a topological order for the specified digraph using Kahn's algorithm. + * @param allocator Explicit allocator context used to allocate internal routing objects + */ +c_err_t c_NonrecursiveTopological_Init(c_NonrecursiveTopological_t* self, c_Digraph_t* graph, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers + */ +void c_NonrecursiveTopological_Destroy(c_NonrecursiveTopological_t* self); + +/** + * @brief Does the digraph have a topological order (is it a DAG)? + */ +C_STATIC_FORCE_INLINE +c_bool_t c_NonrecursiveTopological_HasOrder(c_NonrecursiveTopological_t* self) { + return self ? self->has_order : C_FALSE; +} + +/** + * @brief Gets the topological order sequence of vertices. + * @param out_order An initialized c_VertexIdList_t container to collect the sequence. + * @return C_SUCCESS on success, C_ERR_FAIL if no order exists (not a DAG), or parameter error. + */ +c_err_t c_NonrecursiveTopological_GetOrder(c_NonrecursiveTopological_t* self, c_VertexIdList_t* out_order); + + +#endif /*INCLUDED_C_NONRECURSIVETOPOLOGICAL_H*/ diff --git a/Graph/c_NonrecursiveTopological.t.c b/Graph/c_NonrecursiveTopological.t.c new file mode 100644 index 0000000..ddb42d4 --- /dev/null +++ b/Graph/c_NonrecursiveTopological.t.c @@ -0,0 +1,57 @@ +#include "c_Test.h" +#include "c_Digraph.h" +#include "c_NonrecursiveTopological.h" +#include "c_VertexIdList.h" + +TEST_CASE(test_nonrecursive_topological_sort) { + c_Digraph_t g; + c_Digraph_Init(&g, 4, NULL); + + /* Construct a simple dependency layout DAG: + * 0 -> 1 -> 3 + * 0 -> 2 -> 3 + */ + c_Digraph_AddEdge(&g, 0, 1); + c_Digraph_AddEdge(&g, 1, 3); + c_Digraph_AddEdge(&g, 0, 2); + c_Digraph_AddEdge(&g, 2, 3); + + c_NonrecursiveTopological_t topo; + c_err_t err = c_NonrecursiveTopological_Init(&topo, &g, 0); + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_TRUE(c_NonrecursiveTopological_HasOrder(&topo)); + + c_VertexIdList_t result; + c_VertexIdList_Init(&result, 0, 0); + + err = c_NonrecursiveTopological_GetOrder(&topo, &result); + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_INT_EQ(4, c_VertexIdList_GetSize(&result)); + + /* Verify basic topological layout constraints: + * 1. 0 must always appear first (no incoming dependencies). + * 2. 3 must always appear last (depends on everyone else). + */ + c_uint_t first_v = 0, last_v = 0; + c_VertexIdList_Get(&result, 0, &first_v); + c_VertexIdList_Get(&result, 3, &last_v); + + ASSERT_LL_EQ(0, first_v); + ASSERT_LL_EQ(3, last_v); + + c_VertexIdList_Destroy(&result); + c_NonrecursiveTopological_Destroy(&topo); + c_Digraph_Destroy(&g); +} + +int main(int argc, char** argv){ + + TEST_START(Component Tests); + + // Execution list configurations + RUN_TEST(test_nonrecursive_topological_sort); + + TEST_REPORT(); + + RETURN_TEST_STATUS; +} diff --git a/Graph/c_PrimMST.c b/Graph/c_PrimMST.c new file mode 100644 index 0000000..8a025ad --- /dev/null +++ b/Graph/c_PrimMST.c @@ -0,0 +1,138 @@ +#include +#include "c_IndexMinPQ.h" + +#define PQ_SENTINEL ((c_size_t)-1) + +/* ================================================================================================================== */ +/* Private Comparison Callback for c_IndexMinPQ_t */ + +static int c_Prim_WeightCompare(const void* a, const void* b, void* args) { + double w_a = *(const double*)a; + double w_b = *(const double*)b; + (void)args; + + return (w_a > w_b) - (w_a < w_b); +} + +/* ================================================================================================================== */ +/* Eager Prim Scanning Subroutine Processing Functions */ + +static void c_Prim_Scan(c_PrimMST_t* self, const c_EdgeWeightedGraph_t* graph, c_size_t v, c_IndexMinPQ_t* pq) { + self->marked[v] = C_TRUE; + + c_AdjList_t* adj = &graph->adj_list[v]; + c_size_t size = (c_size_t)c_AdjList_GetSize(adj); + + for (c_size_t i = 0; i < size; ++i) { + c_uint_t generic_edge_id = 0; + c_err_t err = c_AdjList_Get(adj, i, &generic_edge_id); + + if (err == C_SUCCESS) { + c_size_t edge_id = (c_size_t)generic_edge_id; + c_Edge_t* edge = &graph->edges_pool[edge_id]; + c_size_t w = (edge->v == v) ? edge->w : edge->v; + + if (self->marked[w]) continue; + + if (edge->weight < self->dist_to[w]) { + self->dist_to[w] = edge->weight; + self->edge_to[w] = edge_id; + + /* Leverage clean generic membership checking and modification mutations */ + if (c_IndexMinPQ_Contains(pq, w)) { + c_IndexMinPQ_Change(pq, w, &self->dist_to[w]); + } else { + c_IndexMinPQ_Push(pq, w, &self->dist_to[w]); + } + } + } + } +} + +c_err_t c_PrimMST_Init(c_PrimMST_t* self, c_EdgeWeightedGraph_t* graph, c_Allocator_t* allocator) { + if (!self || !graph) return C_ERR_PARAM; + + self->allocator = allocator ? *allocator : c_DefaultAllocator; + self->weight = 0.0; + self->V = graph->V; + c_VertexIdList_Init(&self->mst_edges, 0, allocator); + + if (self->V == 0) return C_SUCCESS; + + /* 1. Allocate primary evaluation buffers */ + self->marked = (c_bool_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_bool_t)); + self->edge_to = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + self->dist_to = (double*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(double)); + + if (!self->marked || !self->edge_to || !self->dist_to) { + c_PrimMST_Destroy(self); + return C_ERR_NOMEM; + } + + /* Initialize metrics tracking layouts to positive infinity benchmarks */ + for (c_size_t v = 0; v < self->V; ++v) { + self->dist_to[v] = DBL_MAX; + self->edge_to[v] = PQ_SENTINEL; + } + + /* 2. Configure your formal generic Index Priority Queue sized exactly to V vertices */ + c_IndexMinPQ_t pq; + c_err_t err = c_IndexMinPQ_Init( + &pq, + self->V, + sizeof(double), + c_Prim_WeightCompare, + NULL, + allocator + ); + + if (err != C_SUCCESS) { + c_PrimMST_Destroy(self); + return err; + } + + /* 3. Run search loops across vertices to handle potential spanning forests */ + for (c_size_t v = 0; v < self->V; ++v) { + if (!self->marked[v]) { + self->dist_to[v] = 0.0; + c_IndexMinPQ_Push(&pq, v, &self->dist_to[v]); + + while (pq.size > 0) { + c_size_t curr_v = 0; + c_IndexMinPQ_Pop(&pq, &curr_v); + + /* Accumulate tree weights if it is not a structural forest root node */ + if (self->edge_to[curr_v] != PQ_SENTINEL) { + c_VertexIdList_Append(&self->mst_edges, (c_uint_t)self->edge_to[curr_v]); + self->weight += self->dist_to[curr_v]; + } + + c_Prim_Scan(self, graph, curr_v, &pq); + } + } + } + + /* Clean up index priority queue allocations */ + c_IndexMinPQ_Destroy(&pq); + return C_SUCCESS; +} + +void c_PrimMST_Destroy(c_PrimMST_t* self) { + if (!self) return; + if (self->marked) c_Allocator_Free(&self->allocator, self->marked); + if (self->edge_to) c_Allocator_Free(&self->allocator, self->edge_to); + if (self->dist_to) c_Allocator_Free(&self->allocator, self->dist_to); + + c_VertexIdList_Destroy(&self->mst_edges); + + self->marked = NULL; + self->edge_to = NULL; + self->dist_to = NULL; + self->weight = 0.0; + self->V = 0; +} + +c_err_t c_PrimMST_GetEdges(c_PrimMST_t* self, c_VertexIdList_t* out_edges) { + if (!self || !out_edges) return C_ERR_PARAM; + return c_VertexIdList_Copy(out_edges, &self->mst_edges); +} diff --git a/Graph/c_PrimMST.h b/Graph/c_PrimMST.h new file mode 100644 index 0000000..6033a6b --- /dev/null +++ b/Graph/c_PrimMST.h @@ -0,0 +1,54 @@ +#ifndef INCLUDED_C_PRIMMST_H +#define INCLUDED_C_PRIMMST_H + +#ifndef INCLUDED_C_EDGEWEIGHTEDGRAPH_H +#include +#endif /*INCLUDED_C_EDGEWEIGHTEDGRAPH_H*/ + +#ifndef INCLUDED_C_VERTEXIDLIST_H +#include +#endif /*INCLUDED_C_VERTEXIDLIST_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_size_t* edge_to; /* edge_to[v] = shortest edge connecting v to the MST */ + double* dist_to; /* dist_to[v] = weight of that shortest edge */ + c_bool_t* marked; /* marked[v] = C_TRUE if v is already in the MST */ + c_VertexIdList_t mst_edges; /* Final optimized list of edge_ids inside the tree */ + double weight; /* Total minimum weight summation of the MST */ + c_size_t V; /* Stored graph vertex limit count */ + c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ +} c_PrimMST_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Computes a minimum spanning tree of an edge-weighted graph using Eager Prim's algorithm. + * @param allocator Explicit allocator context used to configure internal tracking state memory + */ +c_err_t c_PrimMST_Init(c_PrimMST_t* self, c_EdgeWeightedGraph_t* graph, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers + */ +void c_PrimMST_Destroy(c_PrimMST_t* self); + +/** + * @brief Gets a copy of the edge ids included in the Minimum Spanning Tree. + */ +c_err_t c_PrimMST_GetEdges(c_PrimMST_t* self, c_VertexIdList_t* out_edges); + +/** + * @brief Returns the total weight summation of the MST. + */ +C_STATIC_FORCE_INLINE +double c_PrimMST_Weight(c_PrimMST_t* self) { + return self ? self->weight : 0.0; +} + + +#endif /*INCLUDED_C_PRIMMST_H*/ diff --git a/Graph/c_PrimMST.t.c b/Graph/c_PrimMST.t.c new file mode 100644 index 0000000..da08c82 --- /dev/null +++ b/Graph/c_PrimMST.t.c @@ -0,0 +1,48 @@ +#include "c_Test.h" +#include "c_EdgeWeightedGraph.h" +#include "c_PrimMST.h" +#include "c_VertexIdList.h" + +TEST_CASE(test_realigned_eager_prim_mst) { + c_EdgeWeightedGraph_t g; + c_err_t err = c_EdgeWeightedGraph_Init(&g, 4, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Construct a simple graph topology: + * 0 - 1 (Weight: 1.0) -> Picked + * 1 - 2 (Weight: 2.0) -> Picked + * 2 - 3 (Weight: 3.0) -> Picked + * 3 - 0 (Weight: 4.0) -> Skipped + * 0 - 2 (Weight: 5.0) -> Skipped + */ + c_EdgeWeightedGraph_AddEdge(&g, 0, 1, 1.0); + c_EdgeWeightedGraph_AddEdge(&g, 1, 2, 2.0); + c_EdgeWeightedGraph_AddEdge(&g, 2, 3, 3.0); + c_EdgeWeightedGraph_AddEdge(&g, 3, 0, 4.0); + c_EdgeWeightedGraph_AddEdge(&g, 0, 2, 5.0); + + c_PrimMST_t prim; + err = c_PrimMST_Init(&prim, &g, &g.allocator); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Total summation weight must equal 1.0 + 2.0 + 3.0 = 6.0 */ + ASSERT_DOUBLE_EQ_MSG(6.0, c_PrimMST_Weight(&prim), "Eager Prim failed to compute correct total MST weight"); + + c_VertexIdList_t result_list; + c_VertexIdList_Init(&result_list, 0, 0); + + err = c_PrimMST_GetEdges(&prim, &result_list); + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_INT_EQ(3, c_VertexIdList_GetSize(&result_list)); + + c_VertexIdList_Destroy(&result_list); + c_PrimMST_Destroy(&prim); + c_EdgeWeightedGraph_Destroy(&g); +} + +int main(void) { + TEST_START(EagerPrimMST_FormalIndexPQ_Integration_Suite); + RUN_TEST(test_realigned_eager_prim_mst); + TEST_REPORT(); + RETURN_TEST_STATUS; +} diff --git a/Graph/c_TarjanSCC.c b/Graph/c_TarjanSCC.c new file mode 100644 index 0000000..22489a6 --- /dev/null +++ b/Graph/c_TarjanSCC.c @@ -0,0 +1,100 @@ +#include +#include + +/* Private recursive DFS engine helper subroutine for single-pass grouping */ +static void c_TarjanSCC_DFS(c_TarjanSCC_t* self, c_Digraph_t* graph, c_size_t v) { + self->marked[v] = C_TRUE; + self->pre[v] = self->pre_counter++; + self->low[v] = self->pre[v]; + + /* Push vertex onto the component collector stack tracking context */ + self->stack[self->stack_size++] = v; + self->on_stack[v] = C_TRUE; + + c_AdjList_t* adj = &graph->adj_list[v]; + c_size_t size = (c_size_t)c_AdjList_GetSize(adj); + + for (c_size_t i = 0; i < size; ++i) { + c_uint_t target_value = 0; + c_err_t err = c_AdjList_Get(adj, i, &target_value); + + if (err == C_SUCCESS) { + c_size_t w = (c_size_t)target_value; + + if (!self->marked[w]) { + c_TarjanSCC_DFS(self, graph, w); + self->low[v] = C_MIN(self->low[v], self->low[w]); + } + else if (self->on_stack[w]) { + self->low[v] = C_MIN(self->low[v], self->pre[w]); + } + } + } + + /* If v is a root node of an SCC, pop all component nodes off the stack context */ + if (self->low[v] == self->pre[v]) { + c_size_t w = 0; + do { + w = self->stack[--self->stack_size]; + self->id[w] = self->count; + self->on_stack[w] = C_FALSE; + } while (w != v); + + self->count++; /* Advance component total tracking identity grouping */ + } +} + +c_err_t c_TarjanSCC_Init(c_TarjanSCC_t* self, c_Digraph_t* graph, c_Allocator_t* allocator) { + if (!self || !graph) return C_ERR_PARAM; + + self->allocator = allocator ? *allocator : c_DefaultAllocator; + self->count = 0; + self->pre_counter = 0; + self->stack_size = 0; + self->V = graph->V; /* Store vertex count locally for safe inline lookup assertions */ + + if (self->V == 0) return C_SUCCESS; + + /* 1. Allocate essential multi-array structural state mappings */ + self->marked = (c_bool_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_bool_t)); + self->id = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + self->pre = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + self->low = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + self->stack = (c_size_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_size_t)); + self->on_stack = (c_bool_t*)c_Allocator_Calloc(&self->allocator, self->V, sizeof(c_bool_t)); + + if (!self->marked || !self->id || !self->pre || !self->low || !self->stack || !self->on_stack) { + c_TarjanSCC_Destroy(self); + return C_ERR_NOMEM; + } + + /* 2. Run Single-Pass algorithm mapping over every unvisited node */ + for (c_size_t v = 0; v < self->V; ++v) { + if (!self->marked[v]) { + c_TarjanSCC_DFS(self, graph, v); + } + } + + return C_SUCCESS; +} + +void c_TarjanSCC_Destroy(c_TarjanSCC_t* self) { + if (!self) return; + if (self->marked) c_Allocator_Free(&self->allocator, self->marked); + if (self->id) c_Allocator_Free(&self->allocator, self->id); + if (self->pre) c_Allocator_Free(&self->allocator, self->pre); + if (self->low) c_Allocator_Free(&self->allocator, self->low); + if (self->stack) c_Allocator_Free(&self->allocator, self->stack); + if (self->on_stack) c_Allocator_Free(&self->allocator, self->on_stack); + + self->marked = NULL; + self->id = NULL; + self->pre = NULL; + self->low = NULL; + self->stack = NULL; + self->on_stack = NULL; + self->count = 0; + self->stack_size = 0; + self->V = 0; +} + diff --git a/Graph/c_TarjanSCC.h b/Graph/c_TarjanSCC.h new file mode 100644 index 0000000..19257b6 --- /dev/null +++ b/Graph/c_TarjanSCC.h @@ -0,0 +1,66 @@ +#ifndef INCLUDED_C_TARJANSCC_H +#define INCLUDED_C_TARJANSCC_H + +#ifndef INCLUDED_C_DIGRAPH_H +#include +#endif /*INCLUDED_C_DIGRAPH_H*/ + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_bool_t* marked; /* Visited tracking array (size of graph->V) */ + c_size_t* id; /* id[v] = component identifier containing v (0 to count-1) */ + c_size_t* pre; /* pre[v] = preorder index of vertex v */ + c_size_t* low; /* low[v] = low link of vertex v */ + c_size_t* stack; /* Explicit vertex stack buffer for component mapping tracking */ + c_size_t stack_size; /* Current active elements size in stack tracker */ + c_bool_t* on_stack; /* Quick boolean lookup mapping for stack memberships */ + c_size_t pre_counter; /* Preorder index sequencing index variable counter */ + c_size_t count; /* Total number of strongly connected components discovered */ + c_size_t V; + c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ +} c_TarjanSCC_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Computes strongly connected components for the specified digraph using Tarjan's algorithm. + * @param allocator Explicit allocator context used to allocate internal routing arrays + */ +c_err_t c_TarjanSCC_Init(c_TarjanSCC_t* self, c_Digraph_t* graph, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers + */ +void c_TarjanSCC_Destroy(c_TarjanSCC_t* self); + +/** + * @brief Are vertices 'v' and 'w' strongly connected (belong to the same SCC)? + */ +C_STATIC_FORCE_INLINE +c_bool_t c_TarjanSCC_StronglyConnected(c_TarjanSCC_t* self, c_size_t v, c_size_t w) { + if (!self || !self->id || v >= self->V || w >= self->V) return C_FALSE; + return self->id[v] == self->id[w]; +} + +/** + * @brief Returns the component id containing vertex 'v' + * @return Component integer ID (0 to count-1), or C_SIZE_MAX on boundary check failures + */ +C_STATIC_FORCE_INLINE +c_size_t c_TarjanSCC_GetId(const c_TarjanSCC_t* self, c_size_t v) { + if (!self || !self->id || v >= self->V) return C_SIZE_MAX; + return self->id[v]; +} + +/** + * @brief Returns the total number of strongly connected components + */ +C_STATIC_FORCE_INLINE +c_size_t c_TarjanSCC_GetCount(const c_TarjanSCC_t* self) { + return self ? self->count : 0; +} + +#endif /*INCLUDED_C_TARJANSCC_H*/ diff --git a/Graph/c_TarjanSCC.t.c b/Graph/c_TarjanSCC.t.c new file mode 100644 index 0000000..8980a9b --- /dev/null +++ b/Graph/c_TarjanSCC.t.c @@ -0,0 +1,77 @@ +#include "c_Test.h" +#include "c_Digraph.h" +#include "c_TarjanSCC.h" + +TEST_CASE(test_tarjan_scc_with_internal_v_bounds) { + c_Digraph_t g; + + /* 1. Initialize a directed graph with 5 vertices using default allocator */ + c_err_t err = c_Digraph_Init(&g, 5, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* + * 2. Construct a topology containing 2 separate SCC groups: + * Component 1 (Cyclic Loop): 0 -> 1 -> 2 -> 0 + * Exit Bridge Connection: 2 -> 3 + * Component 2 (Cyclic Loop): 3 -> 4 -> 3 + */ + c_Digraph_AddEdge(&g, 0, 1); + c_Digraph_AddEdge(&g, 1, 2); + c_Digraph_AddEdge(&g, 2, 0); + + c_Digraph_AddEdge(&g, 2, 3); /* Bridge edge crossing components */ + + c_Digraph_AddEdge(&g, 3, 4); + c_Digraph_AddEdge(&g, 4, 3); + + /* 3. Initialize Tarjan's solver (passing graph's inner allocator wrapper) */ + c_TarjanSCC_t scc; + err = c_TarjanSCC_Init(&scc, &g, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* 4. Validate that exactly 2 distinct strong component groups are captured */ + c_size_t scc_count = c_TarjanSCC_GetCount(&scc); + ASSERT_LL_EQ(2, scc_count); + + /* 5. Verify connectivity identities using your sleek inline signatures */ + /* Vertices 0, 1, and 2 should share identical cluster mapping configurations */ + ASSERT_TRUE(c_TarjanSCC_StronglyConnected(&scc, 0, 1)); + ASSERT_TRUE(c_TarjanSCC_StronglyConnected(&scc, 1, 2)); + ASSERT_TRUE(c_TarjanSCC_StronglyConnected(&scc, 0, 2)); + + /* Vertices 3 and 4 should be mapped to the exact same cluster ID */ + ASSERT_TRUE(c_TarjanSCC_StronglyConnected(&scc, 3, 4)); + + /* Vertices 0 and 3 should be structurally separate */ + ASSERT_FALSE(c_TarjanSCC_StronglyConnected(&scc, 0, 3)); + + /* 6. Validate safe ID extraction limits via c_TarjanSCC_GetId */ + c_size_t id_0 = c_TarjanSCC_GetId(&scc, 0); + c_size_t id_3 = c_TarjanSCC_GetId(&scc, 3); + + ASSERT_TRUE(id_0 != C_SIZE_MAX); + ASSERT_TRUE(id_3 != C_SIZE_MAX); + ASSERT_TRUE(id_0 != id_3); /* Component IDs must be uniquely distinct */ + + /* Verify that bounds protection prevents out-of-bounds index corruption */ + c_size_t invalid_id = c_TarjanSCC_GetId(&scc, 99); + ASSERT_LL_EQ(C_SIZE_MAX, invalid_id); + + /* 7. Free and recycle array components tracking states safely */ + c_TarjanSCC_Destroy(&scc); + c_Digraph_Destroy(&g); +} + +int main(void) { + /* Bootstrapping test environment wrapper */ + TEST_START(TarjanSCC_Refactored_Internal_V_Tests); + + /* Run specified edge cases */ + RUN_TEST(test_tarjan_scc_with_internal_v_bounds); + + /* Generate total logging matrix summaries sheet */ + TEST_REPORT(); + + /* Unwind tracking status frames code signals */ + RETURN_TEST_STATUS; +} diff --git a/Graph/c_Topological.c b/Graph/c_Topological.c new file mode 100644 index 0000000..da1ae68 --- /dev/null +++ b/Graph/c_Topological.c @@ -0,0 +1,52 @@ +#include +#include "c_DirectedCycle.h" +#include "c_DepthFirstOrder.h" + +c_err_t c_Topological_Init(c_Topological_t* self, c_Digraph_t* graph, c_Allocator_t* allocator) { + if (!self || !graph) return C_ERR_PARAM; + + self->allocator = allocator ? *allocator : c_DefaultAllocator; + self->has_order = C_FALSE; + c_VertexIdList_Init(&self->order, 0, allocator); + + /* 1. Cycle Check: Verify the graph is a Directed Acyclic Graph (DAG) */ + c_DirectedCycle_t cycle_detector; + c_err_t err = c_DirectedCycle_Init(&cycle_detector, graph, allocator); + if (err != C_SUCCESS) return err; + + c_bool_t has_cycle = c_DirectedCycle_HasCycle(&cycle_detector); + c_DirectedCycle_Destroy(&cycle_detector); + + /* If a cycle is present, it is not a DAG; skip order calculation and return success */ + if (has_cycle) { + return C_SUCCESS; + } + + /* 2. Compute Depth-First Order streams */ + c_DepthFirstOrder_t dfs_order; + err = c_DepthFirstOrder_Init(&dfs_order, graph, allocator); + if (err != C_SUCCESS) return err; + + /* 3. Extract the Reverse Post-Order stream directly using high-speed copy */ + err = c_DepthFirstOrder_GetReversePost(&dfs_order, &self->order); + c_DepthFirstOrder_Destroy(&dfs_order); + + if (err != C_SUCCESS) return err; + + self->has_order = C_TRUE; + return C_SUCCESS; +} + +void c_Topological_Destroy(c_Topological_t* self) { + if (!self) return; + c_VertexIdList_Destroy(&self->order); + self->has_order = C_FALSE; +} + +c_err_t c_Topological_GetOrder(c_Topological_t* self, c_VertexIdList_t* out_order) { + if (!self || !out_order) return C_ERR_PARAM; + if (!self->has_order) return C_ERR_FAIL; + + /* High-speed block copy of the pre-computed topological sequence */ + return c_VertexIdList_Copy(out_order, (c_UIntArray_t*)&self->order); +} diff --git a/Graph/c_Topological.h b/Graph/c_Topological.h new file mode 100644 index 0000000..98456aa --- /dev/null +++ b/Graph/c_Topological.h @@ -0,0 +1,53 @@ +#ifndef INCLUDED_C_TOPOLOGICAL_H +#define INCLUDED_C_TOPOLOGICAL_H + +#ifndef INCLUDED_C_DIGRAPH_H +#include +#endif /*INCLUDED_C_DIGRAPH_H*/ + + +#ifndef INCLUDED_C_VERTEXIDLIST_H +#include +#endif /*INCLUDED_C_VERTEXIDLIST_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_VertexIdList_t order; /* Stores the topological order sequence if found */ + c_bool_t has_order; /* Is the graph a DAG (has a valid topological order)? */ + c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ +} c_Topological_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Computes a topological order for the specified digraph. + * @param allocator Explicit allocator context used to allocate internal routing objects + */ +c_err_t c_Topological_Init(c_Topological_t* self, c_Digraph_t* G, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers + */ +void c_Topological_Destroy(c_Topological_t* self); + +/** + * @brief Does the digraph have a topological order (is it a DAG)? + */ +C_STATIC_FORCE_INLINE +c_bool_t c_Topological_HasOrder(c_Topological_t* self) { + return self ? self->has_order : C_FALSE; +} + +/** + * @brief Gets the topological order sequence of vertices. + * @param out_order An initialized c_VertexIdList_t container to collect the sequence. + * @return C_SUCCESS on success, C_ERR_FAIL if no order exists (not a DAG), or parameter error. + */ +c_err_t c_Topological_GetOrder(c_Topological_t* self, c_VertexIdList_t* out_order); + + +#endif /*INCLUDED_C_TOPOLOGICAL_H*/ diff --git a/Graph/c_Topological.t.c b/Graph/c_Topological.t.c new file mode 100644 index 0000000..2aeccaf --- /dev/null +++ b/Graph/c_Topological.t.c @@ -0,0 +1,85 @@ +#include "c_Test.h" +#include "c_Digraph.h" +#include "c_Topological.h" +#include "c_VertexIdList.h" + +TEST_CASE(test_topological_sort_dag) { + c_Digraph_t g; + c_Digraph_Init(&g, 3, NULL); + + /* Construct a valid DAG: + * 0 -> 1 + * 1 -> 2 + * 0 -> 2 + */ + c_Digraph_AddEdge(&g, 0, 1); + c_Digraph_AddEdge(&g, 1, 2); + c_Digraph_AddEdge(&g, 0, 2); + + c_Topological_t topo; + c_err_t err = c_Topological_Init(&topo, &g, 0); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* It must successfully identify the graph as a DAG */ + ASSERT_TRUE(c_Topological_HasOrder(&topo)); + + c_VertexIdList_t result_order; + c_VertexIdList_Init(&result_order, 0, 0); + + err = c_Topological_GetOrder(&topo, &result_order); + ASSERT_INT_EQ(C_SUCCESS, err); + ASSERT_INT_EQ(3, c_VertexIdList_GetSize(&result_order)); + + /* Verify the sequence yields a valid dependency layout (0, 1, 2) */ + c_uint_t v0 = 0, v1 = 0, v2 = 0; + c_VertexIdList_Get(&result_order, 0, &v0); + c_VertexIdList_Get(&result_order, 1, &v1); + c_VertexIdList_Get(&result_order, 2, &v2); + + ASSERT_LL_EQ(0, v0); + ASSERT_LL_EQ(1, v1); + ASSERT_LL_EQ(2, v2); + + c_VertexIdList_Destroy(&result_order); + c_Topological_Destroy(&topo); + c_Digraph_Destroy(&g); +} + +TEST_CASE(test_topological_sort_cyclic_fail) { + c_Digraph_t g; + c_Digraph_Init(&g, 2, NULL); + + /* Construct a cyclic graph (0 -> 1 -> 0) */ + c_Digraph_AddEdge(&g, 0, 1); + c_Digraph_AddEdge(&g, 1, 0); + + c_Topological_t topo; + c_err_t err = c_Topological_Init(&topo, &g, &g.allocator); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* It should gracefully determine that no valid topological order exists */ + ASSERT_FALSE(c_Topological_HasOrder(&topo)); + + c_VertexIdList_t result_order; + c_VertexIdList_Init(&result_order, 0, 0); + + err = c_Topological_GetOrder(&topo, &result_order); + ASSERT_INT_EQ(C_ERR_FAIL, err); /* Query must fail */ + + c_VertexIdList_Destroy(&result_order); + c_Topological_Destroy(&topo); + c_Digraph_Destroy(&g); +} + +int main(int argc, char** argv){ + + TEST_START(c_NonrecursiveDFS Component Tests); + + // Execution list configurations + RUN_TEST(test_topological_sort_dag); + RUN_TEST(test_topological_sort_cyclic_fail); + + TEST_REPORT(); + + RETURN_TEST_STATUS; +} diff --git a/Graph/c_TransitiveClosure.c b/Graph/c_TransitiveClosure.c new file mode 100644 index 0000000..2ab2011 --- /dev/null +++ b/Graph/c_TransitiveClosure.c @@ -0,0 +1,51 @@ +#include + +c_err_t c_TransitiveClosure_Init(c_TransitiveClosure_t* self, c_Digraph_t* graph, c_Allocator_t* allocator) { + if (!self || !graph) return C_ERR_PARAM; + + self->allocator = allocator ? *allocator : c_DefaultAllocator; + self->V = graph->V; + self->dfs_matrix = NULL; + + if (self->V == 0) return C_SUCCESS; + + /* 1. Allocate an array of non-recursive DFS structures */ + self->dfs_matrix = (c_NonrecursiveDirectedDFS_t*)c_Allocator_Calloc( + &self->allocator, + self->V, + sizeof(c_NonrecursiveDirectedDFS_t) + ); + if (!self->dfs_matrix) return C_ERR_NOMEM; + + /* 2. Run a non-recursive DFS starting from each individual vertex */ + for (c_size_t v = 0; v < self->V; ++v) { + c_err_t err = c_NonrecursiveDirectedDFS_Init(&self->dfs_matrix[v], graph, v, allocator); + + if (err != C_SUCCESS) { + /* Clean up previously initialized instances to avoid memory leaks if OOM occurs mid-way */ + for (c_size_t i = 0; i < v; ++i) { + c_NonrecursiveDirectedDFS_Destroy(&self->dfs_matrix[i]); + } + c_Allocator_Free(&self->allocator, self->dfs_matrix); + self->dfs_matrix = NULL; + self->V = 0; + return err; + } + } + + return C_SUCCESS; +} + +void c_TransitiveClosure_Destroy(c_TransitiveClosure_t* self) { + if (!self) return; + + if (self->dfs_matrix) { + /* Deallocate every independent tracking instance matrix block */ + for (c_size_t i = 0; i < self->V; ++i) { + c_NonrecursiveDirectedDFS_Destroy(&self->dfs_matrix[i]); + } + c_Allocator_Free(&self->allocator, self->dfs_matrix); + self->dfs_matrix = NULL; + } + self->V = 0; +} diff --git a/Graph/c_TransitiveClosure.h b/Graph/c_TransitiveClosure.h new file mode 100644 index 0000000..bb2d56c --- /dev/null +++ b/Graph/c_TransitiveClosure.h @@ -0,0 +1,46 @@ +#ifndef INCLUDED_C_TRANSITIVECLOSURE_H +#define INCLUDED_C_TRANSITIVECLOSURE_H + +#ifndef INCLUDED_C_DIGRAPH_H +#include +#endif /*INCLUDED_C_DIGRAPH_H*/ + +#ifndef INCLUDED_C_NONRECURSIVEDIRECTEDDFS +#include +#endif /*INCLUDED_C_NONRECURSIVEDIRECTEDDFS*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_NonrecursiveDirectedDFS_t* dfs_matrix; /* Array of DFS instances (size of graph->V) */ + c_size_t V; /* Stored vertex count for boundary safe checks */ + c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ +} c_TransitiveClosure_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Computes the transitive closure of a digraph to support all-pairs reachability queries. + * @param allocator Explicit allocator context used to allocate internal search objects + */ +c_err_t c_TransitiveClosure_Init(c_TransitiveClosure_t* self, c_Digraph_t* graph, c_Allocator_t* allocator); + +/** + * @brief Releases internal tracking buffers and all embedded search matrices + */ +void c_TransitiveClosure_Destroy(c_TransitiveClosure_t* self); + +/** + * @brief Is vertex 'v' reachable from vertex 'u'? + * @return C_TRUE if there is a directed path from 'u' to 'v', C_FALSE otherwise + */ +C_STATIC_FORCE_INLINE +c_bool_t c_TransitiveClosure_Reachable(c_TransitiveClosure_t* self, c_size_t u, c_size_t v) { + if (!self || !self->dfs_matrix || u >= self->V || v >= self->V) return C_FALSE; + return c_NonrecursiveDirectedDFS_HasPathTo(&self->dfs_matrix[u], v, self->V); +} + +#endif /*INCLUDED_C_TRANSITIVECLOSURE_H*/ diff --git a/Graph/c_TransitiveClosure.t.c b/Graph/c_TransitiveClosure.t.c new file mode 100644 index 0000000..72f073d --- /dev/null +++ b/Graph/c_TransitiveClosure.t.c @@ -0,0 +1,49 @@ +#include "c_Test.h" +#include "c_Digraph.h" +#include "c_TransitiveClosure.h" + +TEST_CASE(test_transitive_closure_reachability) { + c_Digraph_t g; + c_err_t err = c_Digraph_Init(&g, 4, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Construct a graph topology: + * 0 -> 1 -> 2 + * 3 (Isolated component node) + */ + c_Digraph_AddEdge(&g, 0, 1); + c_Digraph_AddEdge(&g, 1, 2); + + c_TransitiveClosure_t tc; + err = c_TransitiveClosure_Init(&tc, &g, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Verify standard forward reachability mappings */ + ASSERT_TRUE(c_TransitiveClosure_Reachable(&tc, 0, 1)); + ASSERT_TRUE(c_TransitiveClosure_Reachable(&tc, 0, 2)); /* Transitive step: 0 can reach 2 via 1 */ + ASSERT_TRUE(c_TransitiveClosure_Reachable(&tc, 1, 2)); + + /* Verify unreachable path invariants */ + ASSERT_FALSE(c_TransitiveClosure_Reachable(&tc, 2, 0)); /* Backwards parsing is invalid */ + ASSERT_FALSE(c_TransitiveClosure_Reachable(&tc, 0, 3)); /* 3 is completely disconnected */ + ASSERT_FALSE(c_TransitiveClosure_Reachable(&tc, 3, 2)); + + /* Verify safe fallback check limits on out-of-bounds queries */ + ASSERT_FALSE(c_TransitiveClosure_Reachable(&tc, 0, 99)); + ASSERT_FALSE(c_TransitiveClosure_Reachable(&tc, 99, 1)); + + c_TransitiveClosure_Destroy(&tc); + c_Digraph_Destroy(&g); +} + +int main(int argc, char** argv){ + + TEST_START(Component Tests); + + // Execution list configurations + RUN_TEST(test_transitive_closure_reachability); + + TEST_REPORT(); + + RETURN_TEST_STATUS; +} diff --git a/Graph/c_VertexIdList.c b/Graph/c_VertexIdList.c new file mode 100644 index 0000000..4eee58d --- /dev/null +++ b/Graph/c_VertexIdList.c @@ -0,0 +1 @@ +#include diff --git a/Graph/c_VertexIdList.h b/Graph/c_VertexIdList.h new file mode 100644 index 0000000..39f7ef0 --- /dev/null +++ b/Graph/c_VertexIdList.h @@ -0,0 +1,28 @@ +#ifndef INCLUDED_C_VERTEXIDLIST_H +#define INCLUDED_C_VERTEXIDLIST_H + +#ifndef INCLUDED_C_UINTARRAY_H +#include +#endif /*INCLUDED_C_UINTARRAY_H*/ + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef c_UIntArray_t c_VertexIdList_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +#define c_VertexIdList_Init c_UIntArray_Init +#define c_VertexIdList_Destroy c_UIntArray_Destroy +#define c_VertexIdList_Resize c_UIntArray_Resize +#define c_VertexIdList_Append c_UIntArray_Append +#define c_VertexIdList_Set c_UIntArray_Set +#define c_VertexIdList_Get c_UIntArray_Get +#define c_VertexIdList_Remove c_UIntArray_Remove +#define c_VertexIdList_IsEmpty c_UIntArray_IsEmpty +#define c_VertexIdList_GetSize c_UIntArray_GetSize +#define c_VertexIdList_Copy c_UIntArray_Copy + + +#endif /*INCLUDED_C_VERTEXIDLIST_H*/ diff --git a/Memory/c_Allocator.h b/Memory/c_Allocator.h index 3404fe2..878250b 100644 --- a/Memory/c_Allocator.h +++ b/Memory/c_Allocator.h @@ -71,7 +71,13 @@ void* c_Allocator_Alloc(c_Allocator_t* self, c_size_t nBytes) { C_STATIC_FORCE_INLINE void* c_Allocator_Realloc(c_Allocator_t* self, void* ptr, c_size_t nOldSize, c_size_t nNewSize) { - if (!self || !ptr || nNewSize==0 || !self->realloc) return NULL; + if (!self || nNewSize==0) return NULL; + + if (ptr==NULL || nOldSize==0) { + return c_Allocator_Alloc(self, nNewSize); + } + + if (!self->realloc) return NULL; return self->realloc(ptr, nOldSize, nNewSize, self->ud); }