Graphs - osvaldoandrade/ova-lib GitHub Wiki

Graphs

Read this page when the graph itself is a first-class object in your program and you want one API for structure, traversal, shortest paths, spanning trees, and topological work.

Construct the Right Graph First

The constructor is:

graph *create_graph(graph_type type,
                    graph_representation rep,
                    graph_traversal_strategy traversal_strategy,
                    graph_min_path_strategy min_path_strategy);

type chooses directed or undirected semantics. rep chooses adjacency lists or an adjacency matrix.

traversal_strategy chooses which concrete algorithm powers traverse.

min_path_strategy chooses which concrete algorithm powers min_path.

Use GRAPH_ADJACENCY_LIST when the graph is sparse or traversal-heavy.

Use GRAPH_ADJACENCY_MATRIX when direct edge lookup matters more than edge-count scaling, or when dense-graph work is the point.

Use GRAPH_TRAVERSE_BFS when level order is the point. Use one of the DFS strategies when depth-first visitation is the point and you want that choice fixed in the graph instance.

Use GRAPH_MIN_PATH_DIJKSTRA for non-negative edge weights. Use GRAPH_MIN_PATH_BELLMAN_FORD when negative edges may exist and you want reachable negative-cycle detection.

Graph Model

Vertices are identified by non-negative int ids.

add_vertex adds one vertex explicitly, called as g->add_vertex(g, ...).

add_edge also creates missing endpoint vertices implicitly. If the edge list is your source of truth, you can build the graph by adding edges only.

Edge weights are double. Missing edges are represented internally by GRAPH_NO_EDGE, which is INFINITY in the current source.

Structural API

Operation Meaning
add_vertex adds one vertex if the id is non-negative and not already present
add_edge adds one weighted edge and, for undirected graphs, the symmetric edge
remove_edge removes the edge and, for undirected graphs, the symmetric edge
has_edge returns whether an edge exists
get_neighbors returns a new list container of neighbor vertex ids
vertex_count returns the number of present vertices
has_vertex returns whether a vertex id is present
get_edge_weight returns the stored weight or GRAPH_NO_EDGE

get_neighbors returns a list container even when the vertex is missing. In that case the list is empty.

Traversal Strategy and Topology

Function Result shape Notes
traverse list of vertex ids invalid start vertex returns an empty list; the visit order comes from the graph's configured traversal strategy
topological_sort list of vertex ids or NULL returns NULL on cyclic directed graphs
connected_components outer list of inner vertex-id lists use for undirected connectivity
strongly_connected_components outer list of inner vertex-id lists for directed graphs
has_cycle bool detects cycles under the graph type

Traversal result lists contain graph-owned int * vertex-id pointers. Free the list container. Do not free the integers inside it.

The traversal strategy is fixed per graph instance. If you need both BFS and DFS over the same structure, create two graph instances with different traversal strategies or rebuild the graph in the second configuration.

Single-Source Minimum Path

Function Return contract Caller cleanup
min_path returns 1 on success, 0 on failure free the returned vector if one is produced

min_path uses the graph's configured shortest-path strategy. With GRAPH_MIN_PATH_DIJKSTRA, min_path returns 0 on invalid input or allocation failure. With GRAPH_MIN_PATH_BELLMAN_FORD, min_path also returns 0 when it finds a reachable negative cycle.

The returned distance storage is sized to the graph vertex capacity, not only to the count of present vertices. Missing or unreachable vertices therefore still occupy slots in the result.

Minimum Spanning Trees

mst_prim and mst_kruskal are for undirected graphs. On directed graphs they return NULL.

mst_prim accepts a start vertex, but if that vertex is invalid it falls back to the first present vertex.

Both functions return a list of heap-allocated graph_weighted_edge * records. You must free each edge and then free the list container.

Representation Diagram

flowchart LR
    G[graph] --> T{graph_type}
    T --> D[GRAPH_DIRECTED]
    T --> U[GRAPH_UNDIRECTED]
    G --> R{graph_representation}
    R --> AL[GRAPH_ADJACENCY_LIST]
    R --> AM[GRAPH_ADJACENCY_MATRIX]
    G --> TS{graph_traversal_strategy}
    TS --> BFS[GRAPH_TRAVERSE_BFS]
    TS --> DFI[GRAPH_TRAVERSE_DFS_ITERATIVE]
    TS --> DFR[GRAPH_TRAVERSE_DFS_RECURSIVE]
    G --> PS{graph_min_path_strategy}
    PS --> DIJ[GRAPH_MIN_PATH_DIJKSTRA]
    PS --> BF[GRAPH_MIN_PATH_BELLMAN_FORD]

Example

#include "graph.h"

int main(void) {
    graph *g = create_graph(GRAPH_DIRECTED,
                            GRAPH_ADJACENCY_LIST,
                            GRAPH_TRAVERSE_BFS,
                            GRAPH_MIN_PATH_DIJKSTRA);
    vector *dist = NULL;

    g->add_edge(g, 0, 1, 1.0);
    g->add_edge(g, 1, 2, 2.0);
    g->min_path(g, 0, &dist);

    double d = dist ? dist->get(dist, 2) : -1.0;
    if (dist) {
        dist->free(dist);
    }
    g->free(g);
    return d == 3.0 ? 0 : 1;
}

Ownership and Cleanup

Destroy the graph with g->free(g).

Destroy neighbor lists, traversal lists, component lists, SCC lists, MST edge lists, and distance vectors on the caller side.

The graph object frees its own adjacency storage and its own vertex-id pointers.

Read Next

If the graph choice is clear and the next problem is a queue, heap, or matrix used around the graph code, move to Queues-and-Stacks, Heaps-and-Priority-Queues, or Matrix-and-Vector.