139 lines
5.3 KiB
C
139 lines
5.3 KiB
C
#include <c_DirectedEulerianPath.h>
|
|||
|
|
#include "c_NonrecursiveDirectedDFS.h"
|
||
|
|
|
||
|
|
c_err_t c_DirectedEulerianPath_Init(c_DirectedEulerianPath_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->path, 0, allocator);
|
||
|
|
|
||
|
|
/* Edge case: An empty graph with no edges trivially has an empty path */
|
||
|
|
if (graph->E == 0) return C_SUCCESS;
|
||
|
|
|
||
|
|
/* 1. Degree tracking analysis to identify the unique start node candidate */
|
||
|
|
c_size_t start_vertex = graph->V;
|
||
|
|
c_size_t end_vertex = graph->V;
|
||
|
|
c_size_t start_nodes_count = 0;
|
||
|
|
c_size_t end_nodes_count = 0;
|
||
|
|
c_size_t fallback_start = 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 > 0 && fallback_start == graph->V) {
|
||
|
|
fallback_start = v; /* Used if the graph is a full cycle loop */
|
||
|
|
}
|
||
|
|
|
||
|
|
if (out_deg > in_deg) {
|
||
|
|
if (out_deg - in_deg > 1) return C_SUCCESS; /* Fails condition: open paths allow a max delta of 1 */
|
||
|
|
start_vertex = v;
|
||
|
|
start_nodes_count++;
|
||
|
|
} else if (in_deg > out_deg) {
|
||
|
|
if (in_deg - out_deg > 1) return C_SUCCESS;
|
||
|
|
end_vertex = v;
|
||
|
|
end_nodes_count++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Validate structural structural configuration cases */
|
||
|
|
if (start_nodes_count == 0 && end_nodes_count == 0) {
|
||
|
|
/* Case A: It's an Eulerian Cycle, choose the first non-isolated node as start */
|
||
|
|
start_vertex = fallback_start;
|
||
|
|
} else if (start_nodes_count == 1 && end_nodes_count == 1) {
|
||
|
|
/* Case B: It's an open Eulerian Path from start_vertex to end_vertex */
|
||
|
|
// start_vertex is already correctly captured here
|
||
|
|
} else {
|
||
|
|
return C_SUCCESS; /* Fails degree criteria layout, return with empty path */
|
||
|
|
}
|
||
|
|
|
||
|
|
if (start_vertex == graph->V) return C_SUCCESS;
|
||
|
|
|
||
|
|
/* 2. Strong connectivity check over edge elements using NonrecursiveDFS */
|
||
|
|
c_NonrecursiveDirectedDFS_t dfs;
|
||
|
|
c_err_t err = c_NonrecursiveDirectedDFS_Init(&dfs, graph, start_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; /* Edges are fragmented in isolated structures */
|
||
|
|
}
|
||
|
|
}
|
||
|
|
c_NonrecursiveDirectedDFS_Destroy(&dfs);
|
||
|
|
|
||
|
|
/* 3. Hierholzer's Iterative Engine Routine */
|
||
|
|
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;
|
||
|
|
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
|
||
|
|
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;
|
||
|
|
|
||
|
|
path_stack[path_stack_size++] = start_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 edge track */
|
||
|
|
|
||
|
|
if (err == C_SUCCESS) {
|
||
|
|
path_stack[path_stack_size++] = (c_size_t)target_w;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
output_stack[output_stack_size++] = curr_v;
|
||
|
|
path_stack_size--;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Unwind tracking queue directly to the VertexIdList matching forward travel steps */
|
||
|
|
while (output_stack_size > 0) {
|
||
|
|
c_VertexIdList_Append(&self->path, (c_uint_t)output_stack[--output_stack_size]);
|
||
|
|
}
|
||
|
|
|
||
|
|
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_DirectedEulerianPath_Destroy(c_DirectedEulerianPath_t* self) {
|
||
|
|
if (!self) return;
|
||
|
|
c_VertexIdList_Destroy(&self->path);
|
||
|
|
}
|
||
|
|
|
||
|
|
c_err_t c_DirectedEulerianPath_GetPath(c_DirectedEulerianPath_t* self, c_VertexIdList_t* out_path) {
|
||
|
|
if (!self || !out_path) return C_ERR_PARAM;
|
||
|
|
if (!c_DirectedEulerianPath_HasPath(self)) return C_ERR_FAIL;
|
||
|
|
|
||
|
|
c_size_t size = (c_size_t)c_VertexIdList_GetSize(&self->path);
|
||
|
|
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->path, i, &val);
|
||
|
|
if (err == C_SUCCESS) {
|
||
|
|
c_err_t app_err = c_VertexIdList_Append(out_path, val);
|
||
|
|
if (app_err != C_SUCCESS) return app_err;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return C_SUCCESS;
|
||
|
|
}
|