Files
cKit/Graph/c_EulerianCycle.c
T
2026-09-07 18:48:16 +08:00

177 lines
7.1 KiB
C

#include <c_EulerianCycle.h>
/* ------------------------------------------------------------------------------------------------------------------ */
/* */
// 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;
}