210 lines
6.1 KiB
C
210 lines
6.1 KiB
C
#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;
|
||
|
|
}
|
||
|
|
|
||
|
|
|