From 15647167317e8eda049b376d198600fcf1d5126b Mon Sep 17 00:00:00 2001 From: Chen Peng Date: Sat, 5 Sep 2026 13:36:43 +0800 Subject: [PATCH] Graph Start --- Base/c_Test.h | 2 + Foundation/c_QuickFindUF.c | 1 + Foundation/c_QuickFindUF.h | 84 +++++++++++++++ Graph/c_AdjList.c | 109 +++++++++++++++++++ Graph/c_AdjList.h | 48 +++++++++ Graph/c_AdjList.t.c | 140 +++++++++++++++++++++++++ Graph/c_Graph.c | 209 +++++++++++++++++++++++++++++++++++++ Graph/c_Graph.h | 46 ++++++++ Graph/c_Graph.t.c | 205 ++++++++++++++++++++++++++++++++++++ 9 files changed, 844 insertions(+) create mode 100644 Foundation/c_QuickFindUF.c create mode 100644 Foundation/c_QuickFindUF.h create mode 100644 Graph/c_AdjList.c create mode 100644 Graph/c_AdjList.h create mode 100644 Graph/c_AdjList.t.c create mode 100644 Graph/c_Graph.c create mode 100644 Graph/c_Graph.h create mode 100644 Graph/c_Graph.t.c diff --git a/Base/c_Test.h b/Base/c_Test.h index 6995981..a1a8eb2 100644 --- a/Base/c_Test.h +++ b/Base/c_Test.h @@ -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)) { \ diff --git a/Foundation/c_QuickFindUF.c b/Foundation/c_QuickFindUF.c new file mode 100644 index 0000000..43ec9e7 --- /dev/null +++ b/Foundation/c_QuickFindUF.c @@ -0,0 +1 @@ +#include diff --git a/Foundation/c_QuickFindUF.h b/Foundation/c_QuickFindUF.h new file mode 100644 index 0000000..1a273e6 --- /dev/null +++ b/Foundation/c_QuickFindUF.h @@ -0,0 +1,84 @@ +#ifndef INCLUDED_C_QUICKFINDUF_H +#define INCLUDED_C_QUICKFINDUF_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_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; iid[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; in; 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*/ diff --git a/Graph/c_AdjList.c b/Graph/c_AdjList.c new file mode 100644 index 0000000..3e0f906 --- /dev/null +++ b/Graph/c_AdjList.c @@ -0,0 +1,109 @@ +#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 new file mode 100644 index 0000000..ab43707 --- /dev/null +++ b/Graph/c_AdjList.h @@ -0,0 +1,48 @@ +#ifndef INCLUDED_C_ADJLIST_H +#define INCLUDED_C_ADJLIST_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_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*/ diff --git a/Graph/c_AdjList.t.c b/Graph/c_AdjList.t.c new file mode 100644 index 0000000..d58f1cc --- /dev/null +++ b/Graph/c_AdjList.t.c @@ -0,0 +1,140 @@ +#include "c_AdjList.h" +#include "c_Test.h" + +#include +#include + +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; +} \ No newline at end of file diff --git a/Graph/c_Graph.c b/Graph/c_Graph.c new file mode 100644 index 0000000..ba962ce --- /dev/null +++ b/Graph/c_Graph.c @@ -0,0 +1,209 @@ +#include + + +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; vadj_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; vV; 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; +} + + diff --git a/Graph/c_Graph.h b/Graph/c_Graph.h new file mode 100644 index 0000000..ff7b0d7 --- /dev/null +++ b/Graph/c_Graph.h @@ -0,0 +1,46 @@ +#ifndef INCLUDED_C_GRAPH_H +#define INCLUDED_C_GRAPH_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_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*/ diff --git a/Graph/c_Graph.t.c b/Graph/c_Graph.t.c new file mode 100644 index 0000000..cd667c8 --- /dev/null +++ b/Graph/c_Graph.t.c @@ -0,0 +1,205 @@ +#include "c_Graph.h" +#include "c_Test.h" + +#include +#include + + +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; +}