Graph Start
This commit is contained in:
@@ -88,6 +88,8 @@ static TestRegistry g_test_registry = {0, 0, 0.0};
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define ASSERT_FALSE(condition) ASSERT_TRUE(!(condition))
|
||||
|
||||
#define ASSERT_INT_EQ(expected, actual) \
|
||||
do { \
|
||||
if ((expected) != (actual)) { \
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
#include <c_QuickFindUF.h>
|
||||
@@ -0,0 +1,84 @@
|
||||
#ifndef INCLUDED_C_QUICKFINDUF_H
|
||||
#define INCLUDED_C_QUICKFINDUF_H
|
||||
|
||||
#ifndef INCLUDED_C_TYPES_H
|
||||
#include <c_Types.h>
|
||||
#endif /*INCLUDED_C_TYPES_H*/
|
||||
|
||||
#ifndef INCLUDED_C_ALLOCATOR_H
|
||||
#include <c_Allocator.h>
|
||||
#endif /*INCLUDED_C_ALLOCATOR_H*/
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
typedef struct {
|
||||
c_size_t* id;
|
||||
c_size_t n;
|
||||
c_size_t count;
|
||||
c_Allocator_t allocator;
|
||||
}c_QuickFindUF_t;
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
C_STATIC_FORCE_INLINE
|
||||
c_err_t c_QuickFindUF_Init(c_QuickFindUF_t* self, c_size_t n, c_Allocator_t* allocator) {
|
||||
if (!self) return C_ERR_PARAM;
|
||||
self->allocator = allocator?*allocator:c_DefaultAllocator;
|
||||
self->n = n;
|
||||
self->id = c_Allocator_Alloc(&self->allocator, n * sizeof(*self->id));
|
||||
if (!self->id) return C_ERR_NOMEM;
|
||||
self->count = n;
|
||||
for (c_size_t i=0; i<n; i++) {
|
||||
self->id[i] = i;
|
||||
}
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
C_STATIC_FORCE_INLINE
|
||||
void c_QuickFindUF_Destroy(c_QuickFindUF_t* self) {
|
||||
if (!self) return;
|
||||
if (self->id) {
|
||||
c_Allocator_Free(&self->allocator, self->id);
|
||||
self->id = NULL;
|
||||
}
|
||||
self->n = 0;
|
||||
self->count = 0;
|
||||
}
|
||||
|
||||
C_STATIC_FORCE_INLINE
|
||||
c_err_t c_QuickFindUF_Find(c_QuickFindUF_t* self, c_size_t p, c_size_t* out) {
|
||||
if (!self) return C_ERR_PARAM;
|
||||
if (p >= self->n) return C_ERR_OUTOFBOUND;
|
||||
if (out) {
|
||||
*out = self->id[p];
|
||||
}
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
C_STATIC_FORCE_INLINE
|
||||
c_err_t c_QuickFindUF_Union(c_QuickFindUF_t* self, c_size_t p, c_size_t q) {
|
||||
if (!self) return C_ERR_PARAM;
|
||||
if (p >= self->n || q>=self->n) return C_ERR_OUTOFBOUND;
|
||||
c_size_t pID = self->id[p];
|
||||
c_size_t qID = self->id[q];
|
||||
if (pID == qID) return C_ERR_OK;
|
||||
for (c_size_t i=0; i<self->n; i++) {
|
||||
if (self->id[i] == pID) {
|
||||
self->id[i] = qID;
|
||||
}
|
||||
}
|
||||
self->count--;
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
C_STATIC_FORCE_INLINE
|
||||
bool c_QuickFindUF_IsConnected(c_QuickFindUF_t* self, c_size_t p, c_size_t q) {
|
||||
if (!self) return false;
|
||||
if (p >= self->n || q>=self->n) return false;
|
||||
return self->id[p] == self->id[q];
|
||||
}
|
||||
|
||||
#endif /*INCLUDED_C_QUICKFINDUF_H*/
|
||||
@@ -0,0 +1,109 @@
|
||||
#include <c_AdjList.h>
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#ifndef INCLUDED_C_ADJLIST_H
|
||||
#define INCLUDED_C_ADJLIST_H
|
||||
|
||||
#ifndef INCLUDED_C_TYPES_H
|
||||
#include <c_Types.h>
|
||||
#endif /*INCLUDED_C_TYPES_H*/
|
||||
|
||||
#ifndef INCLUDED_C_ALLOCATOR_H
|
||||
#include <c_Allocator.h>
|
||||
#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;
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
|
||||
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*/
|
||||
@@ -0,0 +1,140 @@
|
||||
#include "c_AdjList.h"
|
||||
#include "c_Test.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
TEST_CASE(test_array_init_and_destroy) {
|
||||
c_AdjList_t array;
|
||||
c_err_t err = c_AdjList_Init(&array, 10, 0);
|
||||
|
||||
ASSERT_INT_EQ_MSG(C_SUCCESS, err, "Initialization should succeed");
|
||||
ASSERT_PTR_NOT_NULL(array.array);
|
||||
ASSERT_LL_EQ(10, array.capacity);
|
||||
ASSERT_LL_EQ(0, array.size);
|
||||
|
||||
c_AdjList_Destroy(&array);
|
||||
ASSERT_TRUE(array.array == NULL);
|
||||
ASSERT_LL_EQ(0, array.capacity);
|
||||
ASSERT_LL_EQ(0, array.size);
|
||||
}
|
||||
|
||||
TEST_CASE(test_array_append_and_auto_resize) {
|
||||
c_AdjList_t array;
|
||||
// Start with a small capacity of 2 to force automatic geometric doubling
|
||||
c_AdjList_Init(&array, 2, 0);
|
||||
|
||||
c_err_t err1 = c_AdjList_Append(&array, 42);
|
||||
c_err_t err2 = c_AdjList_Append(&array, 84);
|
||||
|
||||
ASSERT_INT_EQ(C_SUCCESS, err1);
|
||||
ASSERT_INT_EQ(C_SUCCESS, err2);
|
||||
ASSERT_LL_EQ(2, array.size);
|
||||
ASSERT_LL_EQ(2, array.capacity);
|
||||
|
||||
// This 3rd append forces an automatic geometric growth event
|
||||
c_err_t err3 = c_AdjList_Append(&array, 168);
|
||||
ASSERT_INT_EQ(C_SUCCESS, err3);
|
||||
ASSERT_LL_EQ(3, array.size);
|
||||
ASSERT_TRUE(array.capacity > 2); // Capacity must double to 4
|
||||
|
||||
c_uint_t val;
|
||||
c_AdjList_Get(&array, 0, &val); ASSERT_LL_EQ(42, val);
|
||||
c_AdjList_Get(&array, 1, &val); ASSERT_LL_EQ(84, val);
|
||||
c_AdjList_Get(&array, 2, &val); ASSERT_LL_EQ(168, val);
|
||||
|
||||
c_AdjList_Destroy(&array);
|
||||
}
|
||||
|
||||
TEST_CASE(test_array_get_and_set) {
|
||||
c_AdjList_t array;
|
||||
c_AdjList_Init(&array, 5, 0);
|
||||
|
||||
c_AdjList_Append(&array, 10);
|
||||
c_AdjList_Append(&array, 20);
|
||||
|
||||
// Test mutating an existing valid slot index
|
||||
c_err_t err_set = c_AdjList_Set(&array, 1, 99);
|
||||
ASSERT_INT_EQ_MSG(C_SUCCESS, err_set, "Set inside array size bounds should succeed");
|
||||
|
||||
c_uint_t val;
|
||||
c_AdjList_Get(&array, 1, &val);
|
||||
ASSERT_LL_EQ(99, val);
|
||||
|
||||
// Error Contract Tests: Index boundary violation validation
|
||||
c_err_t err_set_bad = c_AdjList_Set(&array, 5, 555);
|
||||
c_err_t err_get_bad = c_AdjList_Get(&array, 5, &val);
|
||||
ASSERT_INT_EQ_MSG(C_ERR_PARAM, err_set_bad, "Out-of-bounds mutation must fail");
|
||||
ASSERT_INT_EQ_MSG(C_ERR_PARAM, err_get_bad, "Out-of-bounds retrieval must fail");
|
||||
|
||||
c_AdjList_Destroy(&array);
|
||||
}
|
||||
|
||||
TEST_CASE(test_array_manual_resize) {
|
||||
c_AdjList_t array;
|
||||
c_AdjList_Init(&array, 4, 0);
|
||||
c_AdjList_Append(&array, 100);
|
||||
c_AdjList_Append(&array, 200);
|
||||
|
||||
// Expand capacity manually
|
||||
c_err_t err_expand = c_AdjList_Resize(&array, 20);
|
||||
ASSERT_INT_EQ(C_SUCCESS, err_expand);
|
||||
ASSERT_LL_EQ(20, array.capacity);
|
||||
ASSERT_LL_EQ(2, array.size); // Content sizing remains unchanged
|
||||
|
||||
// Compress capacity down safely
|
||||
c_err_t err_shrink = c_AdjList_Resize(&array, 4);
|
||||
ASSERT_INT_EQ(C_SUCCESS, err_shrink);
|
||||
ASSERT_LL_EQ(4, array.capacity);
|
||||
|
||||
// Error Contract Test: Trying to shrink capacity below the active tracking size
|
||||
c_err_t err_bad_shrink = c_AdjList_Resize(&array, 1);
|
||||
ASSERT_INT_EQ_MSG(C_ERR_PARAM, err_bad_shrink, "Shrinking below active elements count should be rejected");
|
||||
|
||||
c_AdjList_Destroy(&array);
|
||||
}
|
||||
|
||||
TEST_CASE(test_array_remove_element) {
|
||||
c_AdjList_t array;
|
||||
c_AdjList_Init(&array, 5, 0);
|
||||
|
||||
c_AdjList_Append(&array, 11); // Index 0
|
||||
c_AdjList_Append(&array, 22); // Index 1
|
||||
c_AdjList_Append(&array, 33); // Index 2
|
||||
c_AdjList_Append(&array, 44); // Index 3
|
||||
|
||||
// Remove middle element (22 at index 1)
|
||||
c_err_t err = c_AdjList_Remove(&array, 1);
|
||||
ASSERT_INT_EQ(C_SUCCESS, err);
|
||||
ASSERT_LL_EQ(3, array.size); // Total size scales down
|
||||
|
||||
c_uint_t val;
|
||||
// Order verification check: Elements must shift leftwards
|
||||
c_AdjList_Get(&array, 0, &val); ASSERT_LL_EQ(11, val);
|
||||
c_AdjList_Get(&array, 1, &val); ASSERT_LL_EQ(33, val); // Old index 2 shifted here
|
||||
c_AdjList_Get(&array, 2, &val); ASSERT_LL_EQ(44, val); // Old index 3 shifted here
|
||||
|
||||
// Error Contract Test: Remove non-existent index tracking profile
|
||||
c_err_t err_out_of_bounds = c_AdjList_Remove(&array, 10);
|
||||
ASSERT_INT_EQ(C_ERR_PARAM, err_out_of_bounds);
|
||||
|
||||
c_AdjList_Destroy(&array);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Main Test Runner Execution Rig */
|
||||
|
||||
int main(void) {
|
||||
TEST_START(c_AdjList Component Tests);
|
||||
|
||||
// Execution list configurations
|
||||
RUN_TEST(test_array_init_and_destroy);
|
||||
RUN_TEST(test_array_append_and_auto_resize);
|
||||
RUN_TEST(test_array_get_and_set);
|
||||
RUN_TEST(test_array_manual_resize);
|
||||
RUN_TEST(test_array_remove_element);
|
||||
|
||||
TEST_REPORT();
|
||||
|
||||
RETURN_TEST_STATUS;
|
||||
}
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
#include <c_Graph.h>
|
||||
|
||||
|
||||
c_err_t c_Graph_Init(c_Graph_t* self, c_size_t V, c_Allocator_t* allocator) {
|
||||
if (!self || V==0) return C_ERR_PARAM;
|
||||
self->allocator = (allocator!=NULL)?*allocator:c_DefaultAllocator;
|
||||
self->V = V;
|
||||
self->E = 0;
|
||||
self->adj_list = c_Allocator_Alloc(&self->allocator,V * sizeof(*self->adj_list));
|
||||
if (!self->adj_list) {
|
||||
return C_ERR_NOMEM;
|
||||
}
|
||||
for (c_size_t v = 0; v<V; v++) {
|
||||
c_AdjList_Init(&self->adj_list[v], 0, allocator);
|
||||
}
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
void c_Graph_Destroy(c_Graph_t* self) {
|
||||
if (!self) return;
|
||||
if (self->adj_list) {
|
||||
for (c_size_t v = 0; v<self->V; v++) {
|
||||
c_AdjList_Destroy(&self->adj_list[v]);
|
||||
}
|
||||
c_Allocator_Free(&self->allocator,self->adj_list);
|
||||
self->adj_list = NULL;
|
||||
}
|
||||
self->V = 0;
|
||||
self->E = 0;
|
||||
}
|
||||
|
||||
|
||||
c_err_t c_Graph_AddEdge(c_Graph_t* self, c_size_t v, c_size_t w) {
|
||||
if (!self || v >= self->V || w >= self->V) {
|
||||
return C_ERR_PARAM;
|
||||
}
|
||||
|
||||
// Step 1: Duplicate validation check.
|
||||
// If the edge already exists, we return success without duplicate entries.
|
||||
c_AdjList_t* list_v = &self->adj_list[v];
|
||||
for (c_size_t i = 0; i < list_v->size; i++) {
|
||||
if (list_v->array[i] == w) {
|
||||
return C_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Add edge path v -> w
|
||||
c_err_t err = c_AdjList_Append(list_v, w);
|
||||
if (err != C_SUCCESS) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// Step 3: Handle Self-Loops.
|
||||
// If a node links to itself (v == w), appending it once is sufficient.
|
||||
if (v == w) {
|
||||
self->E++;
|
||||
return C_SUCCESS;
|
||||
}
|
||||
|
||||
// Step 4: Add symmetric edge path w -> v (Undirected Graph Invariant)
|
||||
c_AdjList_t* list_w = &self->adj_list[w];
|
||||
err = c_AdjList_Append(list_w, v);
|
||||
if (err != C_SUCCESS) {
|
||||
// Rollback step: Remove the appended 'w' from 'v' if 'w' allocation fails
|
||||
c_AdjList_Remove(list_v, list_v->size - 1);
|
||||
return err;
|
||||
}
|
||||
|
||||
self->E++;
|
||||
return C_SUCCESS;
|
||||
}
|
||||
|
||||
c_err_t c_Graph_RemoveEdge(c_Graph_t* self, c_size_t v, c_size_t w) {
|
||||
if (!self || v >= self->V || w >= self->V) {
|
||||
return C_ERR_PARAM;
|
||||
}
|
||||
|
||||
c_AdjList_t* list_v = &self->adj_list[v];
|
||||
c_AdjList_t* list_w = &self->adj_list[w];
|
||||
|
||||
// Step 1: Locate the target index within v's array
|
||||
c_size_t index_in_v = self->V; // Use self->V as a sentinel for "not found"
|
||||
for (c_size_t i = 0; i < list_v->size; i++) {
|
||||
if (list_v->array[i] == w) {
|
||||
index_in_v = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If edge v -> w doesn't exist, the edge isn't in the graph
|
||||
if (index_in_v == self->V) {
|
||||
return C_ERR_FAIL;
|
||||
}
|
||||
|
||||
// Step 2: Handle Self-Loops.
|
||||
// If it's a self-loop (v == w), removing it once from its own list is sufficient.
|
||||
if (v == w) {
|
||||
c_AdjList_Remove(list_v, index_in_v);
|
||||
self->E--;
|
||||
return C_SUCCESS;
|
||||
}
|
||||
|
||||
// Step 3: Locate the target index within w's array
|
||||
c_size_t index_in_w = self->V;
|
||||
for (c_size_t i = 0; i < list_w->size; i++) {
|
||||
if (list_w->array[i] == v) {
|
||||
index_in_w = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Structural integrity guard: in an undirected graph, if v has w, w must have v.
|
||||
// If it's missing, the graph's internal symmetry invariant is broken.
|
||||
if (index_in_w == self->V) {
|
||||
return C_ERR_FAIL;
|
||||
}
|
||||
|
||||
// Step 4: Perform the actual removals (shifts memory elements leftward)
|
||||
c_AdjList_Remove(list_v, index_in_v);
|
||||
c_AdjList_Remove(list_w, index_in_w);
|
||||
|
||||
self->E--;
|
||||
return C_SUCCESS;
|
||||
}
|
||||
|
||||
c_bool_t c_Graph_HasEdge(const c_Graph_t* self, c_size_t v, c_size_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;
|
||||
}
|
||||
|
||||
const c_AdjList_t* list_v = &self->adj_list[v];
|
||||
|
||||
// High-efficiency linear scan over flat contiguous primitive integer array
|
||||
for (c_size_t i = 0; i < list_v->size; i++) {
|
||||
if (list_v->array[i] == w) {
|
||||
return C_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return C_FALSE;
|
||||
}
|
||||
|
||||
|
||||
c_size_t c_Graph_Degree(c_Graph_t* self, c_size_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) {
|
||||
if (!self || v >= self->V) {
|
||||
return NULL;
|
||||
}
|
||||
return &self->adj_list[v];
|
||||
}
|
||||
|
||||
c_err_t c_Graph_Copy(c_Graph_t* self, const c_Graph_t* src, c_Allocator_t* allocator) {
|
||||
if (!self || !src) return C_ERR_PARAM;
|
||||
|
||||
// Step 1: Initialize top-level boundaries and fallbacks safely
|
||||
self->allocator = allocator ? *allocator : c_DefaultAllocator;
|
||||
self->V = src->V;
|
||||
self->E = src->E;
|
||||
self->adj_list = NULL;
|
||||
|
||||
if (src->V == 0) {
|
||||
return C_SUCCESS;
|
||||
}
|
||||
|
||||
// Step 2: Allocate the master pointer container track array block
|
||||
self->adj_list = (c_AdjList_t*)c_Allocator_Alloc(&self->allocator, src->V * sizeof(*self->adj_list));
|
||||
if (!self->adj_list) {
|
||||
self->V = 0;
|
||||
self->E = 0;
|
||||
return C_ERR_NOMEM;
|
||||
}
|
||||
|
||||
// Step 3: Deep copy individual contiguous internal buffers
|
||||
for (c_size_t i = 0; i < src->V; i++) {
|
||||
const c_AdjList_t* src_list = &src->adj_list[i];
|
||||
c_AdjList_t* dst_list = &self->adj_list[i];
|
||||
|
||||
// Initialize the tracking container with matching capacity constraints
|
||||
if (c_AdjList_Init(dst_list, src_list->size, &self->allocator) != C_SUCCESS) {
|
||||
// CRITICAL BUG FIX: Rollback strategy to eliminate memory leakage
|
||||
for (c_size_t j = 0; j < i; j++) {
|
||||
c_AdjList_Destroy(&self->adj_list[j]);
|
||||
}
|
||||
c_Allocator_Free(&self->allocator, self->adj_list);
|
||||
|
||||
self->adj_list = NULL;
|
||||
self->V = 0;
|
||||
self->E = 0;
|
||||
return C_ERR_NOMEM;
|
||||
}
|
||||
|
||||
// CRITICAL BUG FIX: Sync size invariant because Init sets active size to 0
|
||||
dst_list->size = src_list->size;
|
||||
|
||||
// High-performance block copy via consecutive primitive mapping
|
||||
if (dst_list->size > 0) {
|
||||
memcpy(dst_list->array, src_list->array, dst_list->size * sizeof(c_uint_t));
|
||||
}
|
||||
}
|
||||
|
||||
return C_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
#ifndef INCLUDED_C_GRAPH_H
|
||||
#define INCLUDED_C_GRAPH_H
|
||||
|
||||
#ifndef INCLUDED_C_ADJLIST_H
|
||||
#include <c_AdjList.h>
|
||||
#endif /*INCLUDED_C_ADJLIST_H*/
|
||||
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
typedef struct {
|
||||
c_size_t V;
|
||||
c_size_t E;
|
||||
c_AdjList_t* adj_list;
|
||||
c_Allocator_t allocator;
|
||||
}c_Graph_t;
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
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_RemoveEdge(c_Graph_t* self, c_size_t v, c_size_t w);
|
||||
|
||||
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_AdjList_t* c_Graph_GetAdjList(c_Graph_t* self, c_size_t v);
|
||||
|
||||
/**
|
||||
* @brief Creates a complete deep copy of a source graph.
|
||||
* @param dst Pointer to the target destination graph structure to initialize.
|
||||
* @param src Pointer to the constant source graph to replicate.
|
||||
* @param allocator Pointer to the allocator to be used by the new cloned graph.
|
||||
* @return C_SUCCESS on success, or an error code (e.g., C_ERR_NOMEM, C_ERR_PARAM).
|
||||
*/
|
||||
c_err_t c_Graph_Copy(c_Graph_t* dst, const c_Graph_t* src, c_Allocator_t* allocator);
|
||||
|
||||
#endif /*INCLUDED_C_GRAPH_H*/
|
||||
@@ -0,0 +1,205 @@
|
||||
#include "c_Graph.h"
|
||||
#include "c_Test.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
TEST_CASE(test_array_graph_memcpy_deep_copy) {
|
||||
c_Graph_t original;
|
||||
c_Graph_Init(&original, 3, 0);
|
||||
|
||||
// Build connections: 0 -> 1, 0 -> 2, 1 -> 2
|
||||
c_Graph_AddEdge(&original, 0, 1);
|
||||
c_Graph_AddEdge(&original, 0, 2);
|
||||
c_Graph_AddEdge(&original, 1, 2);
|
||||
|
||||
// Perform the high-efficiency deep copy
|
||||
c_Graph_t cloned;
|
||||
c_err_t err = c_Graph_Copy(&cloned, &original, 0);
|
||||
|
||||
// Assert scalar metrics validation matches perfectly
|
||||
ASSERT_INT_EQ(C_SUCCESS, err);
|
||||
ASSERT_LL_EQ(original.V, cloned.V);
|
||||
ASSERT_LL_EQ(original.E, cloned.E);
|
||||
|
||||
// Assert that structural internal memory segments match completely
|
||||
for (c_size_t i = 0; i < original.V; i++) {
|
||||
ASSERT_LL_EQ(original.adj_list[i].size, cloned.adj_list[i].size);
|
||||
|
||||
c_size_t bytes_to_compare = original.adj_list[i].size * sizeof(c_uint_t);
|
||||
if (bytes_to_compare > 0) {
|
||||
// Memory addresses must be completely distinct (Deep Copy checking rule)
|
||||
ASSERT_TRUE(original.adj_list[i].array != cloned.adj_list[i].array);
|
||||
// Element contents must be identical bitwise
|
||||
int mem_cmp_res = memcmp(original.adj_list[i].array, cloned.adj_list[i].array, bytes_to_compare);
|
||||
ASSERT_INT_EQ(0, mem_cmp_res);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up graph allocation structures
|
||||
c_Graph_Destroy(&original);
|
||||
c_Graph_Destroy(&cloned);
|
||||
}
|
||||
|
||||
TEST_CASE(test_graph_init_and_destroy) {
|
||||
c_Graph_t graph;
|
||||
c_err_t err = c_Graph_Init(&graph, 5, 0);
|
||||
|
||||
ASSERT_INT_EQ_MSG(C_SUCCESS, err, "Graph initialization should succeed");
|
||||
ASSERT_LL_EQ(5, graph.V);
|
||||
ASSERT_LL_EQ(0, graph.E);
|
||||
ASSERT_PTR_NOT_NULL(graph.adj_list);
|
||||
|
||||
// Verify all individual adjacency rows are empty and correctly initialized
|
||||
for (c_size_t i = 0; i < 5; i++) {
|
||||
ASSERT_TRUE(c_AdjList_IsEmpty(&graph.adj_list[i]));
|
||||
ASSERT_LL_EQ(0, graph.adj_list[i].size);
|
||||
}
|
||||
|
||||
c_Graph_Destroy(&graph);
|
||||
ASSERT_TRUE(graph.adj_list == NULL);
|
||||
ASSERT_LL_EQ(0, graph.V);
|
||||
ASSERT_LL_EQ(0, graph.E);
|
||||
}
|
||||
|
||||
// Test Case 2: Standard Undirected Edge Insertion & Degree Tracking
|
||||
TEST_CASE(test_graph_add_edge_and_degree) {
|
||||
c_Graph_t graph;
|
||||
c_Graph_Init(&graph, 4, 0);
|
||||
|
||||
// Insert an edge between 0 and 1
|
||||
c_err_t err1 = c_Graph_AddEdge(&graph, 0, 1);
|
||||
ASSERT_INT_EQ(C_SUCCESS, err1);
|
||||
ASSERT_LL_EQ(1, graph.E);
|
||||
|
||||
// Verify symmetric property (Undirected graph invariant)
|
||||
ASSERT_LL_EQ(1, c_Graph_Degree(&graph, 0));
|
||||
ASSERT_LL_EQ(1, c_Graph_Degree(&graph, 1));
|
||||
|
||||
// Add more edges to create a small triangle with an outer arm: 0-1, 1-2, 2-0, 2-3
|
||||
c_Graph_AddEdge(&graph, 1, 2);
|
||||
c_Graph_AddEdge(&graph, 2, 0);
|
||||
c_Graph_AddEdge(&graph, 2, 3);
|
||||
|
||||
ASSERT_LL_EQ(4, graph.E);
|
||||
|
||||
// Verify individual vertex degrees
|
||||
ASSERT_LL_EQ(2, c_Graph_Degree(&graph, 0)); // neighbors: 1, 2
|
||||
ASSERT_LL_EQ(2, c_Graph_Degree(&graph, 1)); // neighbors: 0, 2
|
||||
ASSERT_LL_EQ(3, c_Graph_Degree(&graph, 2)); // neighbors: 1, 0, 3
|
||||
ASSERT_LL_EQ(1, c_Graph_Degree(&graph, 3)); // neighbor: 2
|
||||
|
||||
// Check specific neighbor values within the array-backed list
|
||||
c_AdjList_t* list_v2 = c_Graph_GetAdjList(&graph, 2);
|
||||
ASSERT_PTR_NOT_NULL(list_v2);
|
||||
|
||||
c_uint_t neighbor_val;
|
||||
c_AdjList_Get(list_v2, 0, &neighbor_val); ASSERT_LL_EQ(1, neighbor_val);
|
||||
c_AdjList_Get(list_v2, 1, &neighbor_val); ASSERT_LL_EQ(0, neighbor_val);
|
||||
c_AdjList_Get(list_v2, 2, &neighbor_val); ASSERT_LL_EQ(3, neighbor_val);
|
||||
|
||||
c_Graph_Destroy(&graph);
|
||||
}
|
||||
|
||||
// Test Case 3: Edge Case Bounds & Self-Loops Validation
|
||||
TEST_CASE(test_graph_edge_cases) {
|
||||
c_Graph_t graph;
|
||||
c_Graph_Init(&graph, 3, 0);
|
||||
|
||||
// Out of bounds vertex parameter checks
|
||||
c_err_t err_bad1 = c_Graph_AddEdge(&graph, 0, 5);
|
||||
c_err_t err_bad2 = c_Graph_AddEdge(&graph, 3, 1);
|
||||
ASSERT_INT_EQ_MSG(C_ERR_PARAM, err_bad1, "Out-of-bounds destination should return parameter error");
|
||||
ASSERT_INT_EQ_MSG(C_ERR_PARAM, err_bad2, "Out-of-bounds source should return parameter error");
|
||||
|
||||
// Self-loop validation (0 <-> 0)
|
||||
c_err_t err_loop = c_Graph_AddEdge(&graph, 0, 0);
|
||||
ASSERT_INT_EQ(C_SUCCESS, err_loop);
|
||||
ASSERT_LL_EQ(1, graph.E);
|
||||
ASSERT_LL_EQ(1, c_Graph_Degree(&graph, 0)); // Standard self-loop updates list once
|
||||
|
||||
c_Graph_Destroy(&graph);
|
||||
}
|
||||
|
||||
TEST_CASE(test_graph_remove_edge) {
|
||||
c_Graph_t graph;
|
||||
c_Graph_Init(&graph, 4, 0);
|
||||
|
||||
// Build paths: 0-1, 1-2, 2-2 (Self loop)
|
||||
c_Graph_AddEdge(&graph, 0, 1);
|
||||
c_Graph_AddEdge(&graph, 1, 2);
|
||||
c_Graph_AddEdge(&graph, 2, 2);
|
||||
ASSERT_LL_EQ(3, graph.E);
|
||||
|
||||
// 1. Test standard edge removal (1 <-> 2)
|
||||
c_err_t err = c_Graph_RemoveEdge(&graph, 1, 2);
|
||||
ASSERT_INT_EQ(C_SUCCESS, err);
|
||||
ASSERT_LL_EQ(2, graph.E);
|
||||
ASSERT_LL_EQ(1, c_Graph_Degree(&graph, 1)); // should only have 0 left
|
||||
ASSERT_LL_EQ(1, c_Graph_Degree(&graph, 2)); // should only have self-loop left
|
||||
|
||||
// 2. Test self-loop removal (2 <-> 2)
|
||||
err = c_Graph_RemoveEdge(&graph, 2, 2);
|
||||
ASSERT_INT_EQ(C_SUCCESS, err);
|
||||
ASSERT_LL_EQ(1, graph.E);
|
||||
ASSERT_LL_EQ(0, c_Graph_Degree(&graph, 2)); // now isolated
|
||||
|
||||
// 3. Test removing a non-existent edge
|
||||
err = c_Graph_RemoveEdge(&graph, 0, 3);
|
||||
ASSERT_INT_EQ_MSG(C_ERR_FAIL, err, "Removing non-existent edge must return failure status");
|
||||
|
||||
// 4. Test boundary parameter filtering
|
||||
err = c_Graph_RemoveEdge(&graph, 0, 99);
|
||||
ASSERT_INT_EQ_MSG(C_ERR_PARAM, err, "Out-of-bounds parameters must return param error");
|
||||
|
||||
c_Graph_Destroy(&graph);
|
||||
}
|
||||
|
||||
TEST_CASE(test_graph_has_edge) {
|
||||
c_Graph_t graph;
|
||||
c_Graph_Init(&graph, 4, 0);
|
||||
|
||||
// Form edges: 0-1, 1-2, 3-3 (Self-loop)
|
||||
c_Graph_AddEdge(&graph, 0, 1);
|
||||
c_Graph_AddEdge(&graph, 1, 2);
|
||||
c_Graph_AddEdge(&graph, 3, 3);
|
||||
|
||||
// 1. Verify standard existing edges (Check symmetry explicitly)
|
||||
ASSERT_TRUE(c_Graph_HasEdge(&graph, 0, 1));
|
||||
ASSERT_TRUE(c_Graph_HasEdge(&graph, 1, 0));
|
||||
ASSERT_TRUE(c_Graph_HasEdge(&graph, 1, 2));
|
||||
ASSERT_TRUE(c_Graph_HasEdge(&graph, 2, 1));
|
||||
|
||||
// 2. Verify self-loop edge existence
|
||||
ASSERT_TRUE(c_Graph_HasEdge(&graph, 3, 3));
|
||||
|
||||
// 3. Verify non-existent edges
|
||||
ASSERT_FALSE(c_Graph_HasEdge(&graph, 0, 2));
|
||||
ASSERT_FALSE(g_test_registry.failed_count > 0); // Framework status checkpoint
|
||||
ASSERT_FALSE(c_Graph_HasEdge(&graph, 0, 3));
|
||||
|
||||
// 4. Verify boundary input error filters return C_FALSE safely
|
||||
ASSERT_FALSE(c_Graph_HasEdge(&graph, 0, 99));
|
||||
ASSERT_FALSE(c_Graph_HasEdge(&graph, 99, 1));
|
||||
|
||||
c_Graph_Destroy(&graph);
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char** argv){
|
||||
TEST_START(Unit Tests);
|
||||
|
||||
// 运行普通无环境要求的用例
|
||||
RUN_TEST(test_graph_init_and_destroy);
|
||||
RUN_TEST(test_graph_add_edge_and_degree);
|
||||
RUN_TEST(test_graph_edge_cases);
|
||||
RUN_TEST(test_array_graph_memcpy_deep_copy);
|
||||
RUN_TEST(test_graph_remove_edge);
|
||||
RUN_TEST(test_graph_has_edge);
|
||||
|
||||
// 打印最终统计报告
|
||||
TEST_REPORT();
|
||||
|
||||
RETURN_TEST_STATUS;
|
||||
}
|
||||
Reference in New Issue
Block a user