146 lines
6.0 KiB
C
146 lines
6.0 KiB
C
#include <c_EdgeWeightedDirectedCycle.h>
|
|
|
|
|
|
#define CYCLE_SENTINEL ((c_size_t)-1)
|
|
|
|
typedef struct {
|
|
c_size_t v; /* Current vertex index */
|
|
c_size_t edge_idx; /* Next neighbor index to pull from adjacency out-list */
|
|
} c_DigraphCycleFrame_t;
|
|
|
|
/* Private non-recursive DFS machine loop processor */
|
|
static void c_EdgeWeightedDirectedCycle_Process(c_EdgeWeightedDirectedCycle_t* self, c_EdgeWeightedDigraph_t* graph, c_size_t root, c_DigraphCycleFrame_t* frame_stack) {
|
|
c_size_t stack_size = 0;
|
|
|
|
/* Push initial root activation block onto structural runtime stack */
|
|
self->marked[root] = C_TRUE;
|
|
self->on_stack[root] = C_TRUE;
|
|
frame_stack[stack_size++] = (c_DigraphCycleFrame_t){ .v = root, .edge_idx = 0 };
|
|
|
|
while (stack_size > 0) {
|
|
c_DigraphCycleFrame_t* current_frame = &frame_stack[stack_size - 1];
|
|
c_size_t u = current_frame->v;
|
|
|
|
c_UIntArray_t* adj = &graph->adj_list[u];
|
|
c_size_t neighbor_count = (c_size_t)c_UIntArray_GetSize(adj);
|
|
|
|
c_bool_t advanced = C_FALSE;
|
|
while (current_frame->edge_idx < neighbor_count) {
|
|
c_uint_t generic_edge_id = 0;
|
|
c_err_t err = c_UIntArray_Get(adj, current_frame->edge_idx, &generic_edge_id);
|
|
current_frame->edge_idx++; /* Advance out-list pointer position */
|
|
|
|
if (err == C_SUCCESS) {
|
|
c_size_t edge_id = (c_size_t)generic_edge_id;
|
|
c_size_t w = graph->edges_pool[edge_id].to;
|
|
|
|
/* Case A: Encountered an unvisited node, simulate recursive call descent */
|
|
if (!self->marked[w]) {
|
|
self->marked[w] = C_TRUE;
|
|
self->on_stack[w] = C_TRUE;
|
|
self->edge_to[w] = edge_id;
|
|
|
|
frame_stack[stack_size++] = (c_DigraphCycleFrame_t){ .v = w, .edge_idx = 0 };
|
|
advanced = C_TRUE;
|
|
break;
|
|
}
|
|
/* Case B: Backedge detected (target node is currently alive on stack) -> Cycle Trapped! */
|
|
else if (self->on_stack[w]) {
|
|
c_size_t* reverse_stack = (c_size_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_size_t));
|
|
if (!reverse_stack) return;
|
|
|
|
c_size_t trace_size = 0;
|
|
reverse_stack[trace_size++] = edge_id; /* Seal final back edge closing the loop */
|
|
|
|
/* Trace backward using edge origin nodes until reaching intersection w */
|
|
c_size_t curr_v = u;
|
|
while (curr_v != w) {
|
|
c_size_t prev_edge_id = self->edge_to[curr_v];
|
|
if (prev_edge_id == CYCLE_SENTINEL) break;
|
|
|
|
reverse_stack[trace_size++] = prev_edge_id;
|
|
curr_v = graph->edges_pool[prev_edge_id].from;
|
|
}
|
|
|
|
/* Append elements forward into persistent container matching execution path */
|
|
while (trace_size > 0) {
|
|
c_VertexIdList_Append(&self->cycle, (c_uint_t)reverse_stack[--trace_size]);
|
|
}
|
|
|
|
c_Allocator_Free(&self->allocator, reverse_stack);
|
|
return; /* Return instantly to stop unnecessary processing */
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Short-circuit bubbling up if an evaluation path already verified a cycle */
|
|
if (c_EdgeWeightedDirectedCycle_HasCycle(self)) return;
|
|
|
|
/* If all outbound avenues from node u are fully parsed, pop it from execution tracking */
|
|
if (!advanced) {
|
|
self->on_stack[u] = C_FALSE;
|
|
stack_size--;
|
|
}
|
|
}
|
|
}
|
|
|
|
c_err_t c_EdgeWeightedDirectedCycle_Init(c_EdgeWeightedDirectedCycle_t* self, c_EdgeWeightedDigraph_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);
|
|
|
|
if (graph->V == 0) return C_SUCCESS;
|
|
|
|
self->marked = (c_bool_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_bool_t));
|
|
self->edge_to = (c_size_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_size_t));
|
|
self->on_stack = (c_bool_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_bool_t));
|
|
|
|
if (!self->marked || !self->edge_to || !self->on_stack) {
|
|
c_EdgeWeightedDirectedCycle_Destroy(self);
|
|
return C_ERR_NOMEM;
|
|
}
|
|
|
|
for (c_size_t i = 0; i < graph->V; ++i) {
|
|
self->edge_to[i] = CYCLE_SENTINEL;
|
|
}
|
|
|
|
/* Allocate continuous explicit compiler-emulated frame scratch buffer stack O(V) */
|
|
c_DigraphCycleFrame_t* frame_stack = (c_DigraphCycleFrame_t*)c_Allocator_Calloc(&self->allocator, graph->V, sizeof(c_DigraphCycleFrame_t));
|
|
if (!frame_stack) {
|
|
c_EdgeWeightedDirectedCycle_Destroy(self);
|
|
return C_ERR_NOMEM;
|
|
}
|
|
|
|
/* Sweep disconnected structural pockets across graph nodes boundary safely */
|
|
for (c_size_t v = 0; v < graph->V; ++v) {
|
|
if (!self->marked[v] && !c_EdgeWeightedDirectedCycle_HasCycle(self)) {
|
|
c_EdgeWeightedDirectedCycle_Process(self, graph, v, frame_stack);
|
|
}
|
|
}
|
|
|
|
c_Allocator_Free(&self->allocator, frame_stack);
|
|
return C_SUCCESS;
|
|
}
|
|
|
|
void c_EdgeWeightedDirectedCycle_Destroy(c_EdgeWeightedDirectedCycle_t* self) {
|
|
if (!self) return;
|
|
if (self->marked) c_Allocator_Free(&self->allocator, self->marked);
|
|
if (self->edge_to) c_Allocator_Free(&self->allocator, self->edge_to);
|
|
if (self->on_stack) c_Allocator_Free(&self->allocator, self->on_stack);
|
|
|
|
c_VertexIdList_Destroy(&self->cycle);
|
|
|
|
self->marked = NULL;
|
|
self->edge_to = NULL;
|
|
self->on_stack = NULL;
|
|
}
|
|
|
|
c_err_t c_EdgeWeightedDirectedCycle_GetCycle(c_EdgeWeightedDirectedCycle_t* self, c_VertexIdList_t* out_cycle) {
|
|
if (!self || !out_cycle) return C_ERR_PARAM;
|
|
if (!c_EdgeWeightedDirectedCycle_HasCycle(self)) return C_ERR_FAIL;
|
|
|
|
/* Copy pre-computed internal cycle sequence using high-speed block layout rules */
|
|
return c_UIntArray_Copy(out_cycle, (c_UIntArray_t*)&self->cycle);
|
|
}
|