Graph
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
#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;
|
||||
}
|
||||
Reference in New Issue
Block a user