Graph
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
#include <c_DirectedEulerianCycle.h>
|
||||
#include "c_NonrecursiveDirectedDFS.h"
|
||||
|
||||
c_err_t c_DirectedEulerianCycle_Init(c_DirectedEulerianCycle_t* self, c_Digraph_t* graph, c_Allocator_t* allocator) {
|
||||
if (!self || !graph) return C_ERR_PARAM;
|
||||
|
||||
self->allocator = allocator ? *allocator : c_DefaultAllocator;
|
||||
c_VertexIdList_Init(&self->cycle, 0, allocator);
|
||||
|
||||
/* Edge case: An empty graph with no edges trivially has an empty Eulerian cycle path */
|
||||
if (graph->E == 0) return C_SUCCESS;
|
||||
|
||||
/* 1. Necessary condition verification: In-degree must equal Out-degree for all nodes */
|
||||
c_size_t non_isolated_vertex = graph->V;
|
||||
for (c_size_t v = 0; v < graph->V; ++v) {
|
||||
c_size_t out_deg = c_Digraph_GetOutDegree(graph, v);
|
||||
c_size_t in_deg = c_Digraph_GetInDegree(graph, v);
|
||||
|
||||
if (out_deg != in_deg) {
|
||||
return C_SUCCESS; /* No Eulerian cycle exists, return gracefully with empty cycle path */
|
||||
}
|
||||
if (out_deg > 0 && non_isolated_vertex == graph->V) {
|
||||
non_isolated_vertex = v; /* Track a starting node candidates containing edges */
|
||||
}
|
||||
}
|
||||
|
||||
if (non_isolated_vertex == graph->V) return C_SUCCESS; // Graph has no edges
|
||||
|
||||
/* 2. Necessary condition verification: Connectivity check via non-recursive DFS */
|
||||
c_NonrecursiveDirectedDFS_t dfs;
|
||||
c_err_t err = c_NonrecursiveDirectedDFS_Init(&dfs, graph, non_isolated_vertex, allocator);
|
||||
if (err != C_SUCCESS) return err;
|
||||
|
||||
for (c_size_t v = 0; v < graph->V; ++v) {
|
||||
if (c_Digraph_GetOutDegree(graph, v) > 0 && !c_NonrecursiveDirectedDFS_HasPathTo(&dfs, v, graph->V)) {
|
||||
c_NonrecursiveDirectedDFS_Destroy(&dfs);
|
||||
return C_SUCCESS; /* Graph edges are disconnected */
|
||||
}
|
||||
}
|
||||
c_NonrecursiveDirectedDFS_Destroy(&dfs);
|
||||
|
||||
/* 3. Hierholzer's Algorithm: Construct the cycle path */
|
||||
/* Maintain local tracking copies of current edge indexing to safely 'consume' them */
|
||||
c_size_t* edge_index_iterator = (c_size_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_size_t));
|
||||
if (!edge_index_iterator) return C_ERR_NOMEM;
|
||||
|
||||
/* Stack setup for path assembly tracking: capacity bounded by E + 1 */
|
||||
c_size_t max_stack_cap = graph->E + 1;
|
||||
c_size_t* path_stack = (c_size_t*)c_Allocator_Calloc(&self->allocator, max_stack_cap, sizeof(c_size_t));
|
||||
if (!path_stack) {
|
||||
c_Allocator_Free(&self->allocator, edge_index_iterator);
|
||||
return C_ERR_NOMEM;
|
||||
}
|
||||
|
||||
/* Temporary collector stack tracking for final inversion layout */
|
||||
c_size_t* output_stack = (c_size_t*)c_Allocator_Calloc(&self->allocator, max_stack_cap, sizeof(c_size_t));
|
||||
if (!output_stack) {
|
||||
c_Allocator_Free(&self->allocator, edge_index_iterator);
|
||||
c_Allocator_Free(&self->allocator, path_stack);
|
||||
return C_ERR_NOMEM;
|
||||
}
|
||||
|
||||
c_size_t path_stack_size = 0;
|
||||
c_size_t output_stack_size = 0;
|
||||
|
||||
/* Start the path construction from our candidate node */
|
||||
path_stack[path_stack_size++] = non_isolated_vertex;
|
||||
|
||||
while (path_stack_size > 0) {
|
||||
c_size_t curr_v = path_stack[path_stack_size - 1];
|
||||
c_AdjList_t* adj = &graph->adj_list[curr_v];
|
||||
c_size_t out_degree = (c_size_t)c_UIntArray_GetSize(adj);
|
||||
|
||||
if (edge_index_iterator[curr_v] < out_degree) {
|
||||
c_uint_t target_w = 0;
|
||||
err = c_UIntArray_Get(adj, edge_index_iterator[curr_v], &target_w);
|
||||
edge_index_iterator[curr_v]++; /* Consume the edge */
|
||||
|
||||
if (err == C_SUCCESS) {
|
||||
path_stack[path_stack_size++] = (c_size_t)target_w;
|
||||
}
|
||||
} else {
|
||||
/* Backtrack step: Node has no remaining active edges out, push to tour results */
|
||||
output_stack[output_stack_size++] = curr_v;
|
||||
path_stack_size--;
|
||||
}
|
||||
}
|
||||
|
||||
/* Push collected route into the formal c_VertexIdList storage layout matching target sequential format */
|
||||
while (output_stack_size > 0) {
|
||||
c_VertexIdList_Append(&self->cycle, (c_uint_t)output_stack[--output_stack_size]);
|
||||
}
|
||||
|
||||
/* Clean up local working variables */
|
||||
c_Allocator_Free(&self->allocator, edge_index_iterator);
|
||||
c_Allocator_Free(&self->allocator, path_stack);
|
||||
c_Allocator_Free(&self->allocator, output_stack);
|
||||
|
||||
return C_SUCCESS;
|
||||
}
|
||||
|
||||
void c_DirectedEulerianCycle_Destroy(c_DirectedEulerianCycle_t* self) {
|
||||
if (!self) return;
|
||||
c_VertexIdList_Destroy(&self->cycle);
|
||||
}
|
||||
|
||||
c_err_t c_DirectedEulerianCycle_GetCycle(c_DirectedEulerianCycle_t* self, c_VertexIdList_t* out_cycle) {
|
||||
if (!self || !out_cycle) return C_ERR_PARAM;
|
||||
if (!c_DirectedEulerianCycle_HasCycle(self)) return C_ERR_FAIL;
|
||||
|
||||
c_size_t size = (c_size_t)c_VertexIdList_GetSize(&self->cycle);
|
||||
for (c_size_t i = 0; i < size; ++i) {
|
||||
c_uint_t val = 0;
|
||||
c_err_t err = c_VertexIdList_Get((c_VertexIdList_t*)&self->cycle, i, &val);
|
||||
if (err == C_SUCCESS) {
|
||||
c_err_t app_err = c_VertexIdList_Append(out_cycle, val);
|
||||
if (app_err != C_SUCCESS) return app_err;
|
||||
}
|
||||
}
|
||||
return C_SUCCESS;
|
||||
}
|
||||
Reference in New Issue
Block a user