Graph Start

This commit is contained in:
2026-09-05 13:36:43 +08:00
parent 4e5ae52e54
commit 1564716731
9 changed files with 844 additions and 0 deletions
+2
View File
@@ -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)) { \
+1
View File
@@ -0,0 +1 @@
#include <c_QuickFindUF.h>
+84
View File
@@ -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*/
+109
View File
@@ -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;
}
+48
View File
@@ -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*/
+140
View File
@@ -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
View File
@@ -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;
}
+46
View File
@@ -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*/
+205
View File
@@ -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;
}