A powerful and lightweight C library for graph theory. A single, common API is provided for all kinds of graphs (directed, weighted, flow networks, etc.), making it easy to learn and use.
The fundamental type in this library is the struct Graph
. A Graph
of Graph
is of a particular
type, as indicated by one of the following bit-flags:
STD_FLAG
: A "standard" graph, i.e. unweighted, uncolored, undirected.D_FLAG
: A directed graph.W_FLAG
: A weighted graph.COL_FLAG
: A colored graph.CAP_FLAG
: A graph whose edges have a capacity (i.e. an upper bound).NETFLOW_FLAG
: A flow network.
One may combine these flags with the |
operator. For example, D_FLAG | W_FLAG
specifies a directed weighted graph. Since a flow network is a
directed graph with weights (flows) and capacities,NETFLOW_FLAG
is simply
((W_FLAG | D_FLAG) | CAP_FLAG)
by definition.
A stuct Graph *
pointer may be initialized or read from a .txt
file in a
special format, which we call the Penazzi format. A .txt
file is in the
Penazzi format if it satisfies the following conditions:
- Its first line is of the form
p n m FLAG
, withn, m
natural numbers andFLAG
one of the bit-flags introduced above. - The rest of the lines are of the form:
-
e x y
for standard graphs or directed graphs, indicating that$\{x,y\} \in E(G)$ (or$(x, y) \in E(G)$ in the directed case). -
e x y w
for weighted graphs, wherew
is the weight$w$ of the edge$\{x, y\}$ or$(x, y)$ . -
e x y w c
for flow networks, wherec
specifies the capacity of the edge andw
its weight (i.e. its current flow).
-
For instance,
p edge 4 3 STD_FLAG
e 0 1
e 1 2
e 1 3
defines the graph
0
|
|
1
/ \
2 3
and
p edge 4 3 W_FLAG
e 0 1 10
e 1 2 20
e 1 3 30
specifies
0
|
| (10)
|
1
(20) / \ (30)
2 3
To read a graph from a .txt
in Penazzi format, use readGraph(char *filename)
function, which returns a pointer to a Graph
.
A Graph *G
can be written in a .txt
file in Penazzi format sing the
writeGraph(Graph *G, char *fname)
function.
To initialize a graph with n
vertices, m
edges, use the function
Graph G* initGraph(n, m, FLAG) // Set FLAG to a valid flag
Upon initialization, all edges in G
are set to {0, 0}
. No unnecessary
memory is allocated - i.e. memory for the colors, weights, or capacities is
only allocated for colored, weighted, or flow-network graphs.
To set the value of an edge in a Graph
, call
void setEdge(Graph* G, u32 i, u32 x, u32 y, u32 *w, u32 *c)
This function sets the i
th edge to (x, y)
. w
is either NULL
(if the
graph is not weighted) or a pointer to a u32
, if the graph is weighted.
Naturally, w
and c
pointers (weight
and capacity) should be NULL if the graph is not weighted (w
) or is not a
flow network (c
).
It is fundamental to call to call formatEdges(G)
after dynamically
creating a graph with calls to the setEdge
function.
For instance,
Graph *G = initGraph(4, 3, STD_FLAG);
setEdge(G, 0, 0, 1, NULL, NULL);
setEdge(G, 1, 1, 2, NULL, NULL);
setEdge(G, 2, 1, 3, NULL, NULL);
formatEdges(G);
also defines the graph
0
|
|
1
/ \
2 3
A graph initialized with m
edges allocates the exact amount of memory
required for storing this number of edges. However, new edges can be added with
the function
void addEdge(Graph *G, u32 x, u32 y, u32 *w, u32 *c)
Existing edges can be removed with the void
void removeEdge(struct Graph *G, u32 x, u32 y)
function. Both functions perform the necessary memory reallocations and update
the graph attributes (e.g. formatEdges(G)
implicitly, so there is no need for an implicit call to this
function afterwards.
Here are some common graph properties and the function calls they relate to.
The number of edges or vertices in a graph can be found with the
numberOfVertices(Graph *G)
or numberOfEdges(Graph *G)
functions. The same
is true of the Δ(Graph *G)
.
Since edges in a graph are formatted and ordered, we can find the j
th
neighbour of vertex i
with the neighbour(u32 j, u32 i, Graph G*)
function. We are assuming the neighbours of a vertex are ordered increasingly.
For instance, in the graph generated above, the j=0
(first) neighbour of
vertex 1
is 2
, and the j=1
neighbour of vertex 1
is 3
.
If the graph is directed, the j
th neighbour is understood to be the
j
th target, i.e. the neighbours of a vertex are those vertices it
leads to.
The bool isNeighbour(u32 x, u32 y, Graph *G)
returns true
if false
otherwise.
The degree of vertex i
can be found with degree(u32 i, Graph *G)
. Again, if
the graph is directed, the degree is taken to be the number of vertices the
vertex leads to. To find the number of vertices which lead into the vertex, use
inDegree(u32 i, Graph *G)
.
The color of a vertex be obtained or set with the getColor(u32 i, Graph G*)
or setColor(color x, u32 i, Graph *G)
functions. If a graph has not been
colored, the color of all vertices is set to zero.
The library provides an implementation of Dijkstra's algorithm to find the
shortest distance, from a starting vertex
The function
u32 *dijkstra(u32 s, Graph *G){
returns an array u32
integers s.t.
The library provides an implementation of Prim's algorithm to find a
minimum spanning tree (MST) of a weighted graph
The function
Graph *prim(Graph *G, u32 startVertex) {
returns a pointer to a Graph
, and the Graph
pointed to is the found MST of
G
.
The flag NETFLOW_FLAG
specifies that a Graph
is a flow network.
A flow network is formally a
The weight of each edge getEdgeWeight, setEdgeWeight
, etc.) should be use to manipulate
A flow network can be read from the Penazzi format by setting the flag as
NETFLOW_FLAG
and adding the capacity next to the weight of each edge. For instance,
p edge 6 8 NETFLOW_FLAG
e 0 1 0 10
e 0 2 0 20
e 1 3 0 10
e 1 4 0 5
e 2 3 0 5
e 2 4 0 5
e 3 5 0 10
e 4 5 0 10
specifies a network with
The simplest approach to finding a flow in a network is to greedily find
non-saturated paths from the source
is non-saturated if
The greedy flow algorithm has some nice properties:
- It always produces a valid flow.
- It always halts, since it saturates at least an edge per iteration, and saturated edges are never re-emptied.
- Its complexity is
$O(m^2)$ , since it performs$O(m)$ iterations, each of which has complexity$O(m)$ .
The downside is that the flow found is not necessarily maximal.
To find a flow Graph *N
with the NETFLOW_FLAG
, call either
u32 flowValue = greedyFlow(N, s, t, flowBFS)
or
u32 flowValue = greedyFlow(N, s, t, flowDFS)
The two calls differ in the way they find non-saturated paths from
The function N
.
Two approaches are given for generating necessarily connected graphs of
The bottom-up approach consists in generating a random Prufer sequence and
from it a random tree of
Graph *G = genFromRandomTree(n, m);
assuming n, m
are defined integers.
A formal analysis of this algorithm reveals that its complexity is
Note: Some graphs of
$n$ vertices,$m$ edges, have more spanning trees than others. This entailsgenFromRandomTree
does not sample uniformly from the space of all possible such graphs - i.e. it is biased towards graphs with many spanning trees, as opposed to graphs with fewer spanning trees.
The top-down approach consists in generating a
To use this algorithm, call
Graph *G = genFromKn(n, m)
assuming n
, m
are integers.
The algorithm is
There are simpler generating functions provided:
- A function
genFromRandomTreeUnbound(u32 n)
, which uses the bottom-up approach without specifying a desired number of edges, instead deciding it randomly. - A function
randomTree(u32 n)
, which generates a random tree ofn
vertices, and is effectively equivalent togenFromRandomTree(n, n - 1)
. - A function
fromPruferSequence(u32* seq, u32 seq_len)
, which generates a random tree from a Prufer sequenceseq
of lengthseq_len
.
Contributions to C-Graphs are welcome! Please fork the repository, create a feature branch, and submit a pull request.
C-Graphs is licensed under the MIT License. See the LICENSE file for details.