We continue with the Daily DSA series. The obvious choice after the Queue yesterday would be the stack. But because this would again be really basic we’re continuing with a bit more of an advanced topic (but no worries, still easy to learn).
Graphs
Simply days a graph is a bunch of points connected with lines. We call the points vertices (or a single one vertex) and the connecting lines edges.
We write this as where is the set of vertices and the set of edges. For analysis we often use and .
Graphs are very useful because they can represent a lot of real-world and also other theoretical problems. Examples would be: A street network, dependencies, a communication network, or a production chain.
Variations
Normally we have graphs in which there is at most a single edge between two vertices (one per direction for directed graphs) and no self loops.
In a Multigraph multiple edges between two vertices are allowed. I don’t even know if there is a special name for graphs with self loops.
Paths and Walks
A path of length is a series of vertices where . Notice that the length is the amount of edges and not the amount of vertices.
A walk is similar, with the difference that it allows duplicate vertices.
Vertices
Vertices are the nodes of the graph. They can theoretically be any object and have metadata associated with them. In practical applications most often they’re just a single number. If they aren’t it’s really useful to have a unique key associated with each one.
Properties
Degree
Every vertex has a degree. We denote it with . For an undirected graph this is simple the amount of connected edges. For directed graphs a node can have both an in- and an out-degree, denoting the incoming and the outgoing edges. We can denote this with e.g. and .
In every directed graph the Degree Sum Formula holds. It describes a simple relation between the degrees and the amount of edges. It’s explanation is really easy: Every edge connects to two nodes and therefore adds two to the sum of degrees.
For undirected graphs the sum of in-degrees must match the sum out the out-degrees. The sum of both of them must again be twice the number of edges.
Undirected graph:
Directed graph:
Edges
Properties
(Un)Directed
The edges can be directed (you can just go through them in one direction) or undirected. Graphically we often illustrate this using arrows vs just simple lines.
Weight
A graph can either be weighted or unweighted. In a weighted graph we associate a weight (commonly either in or ) with every edge. Sometimes this is also called cost. Mathematically we do this via a weight function .
We can use this to represent e.g. a distance between nodes (for shortest path problems) or a cost to build a connection (for MST (you’ll learn about that later) problems).
Graph Representations
There are many different ways. They all have some advantages and disadvantages.
Adjacency Matrix
An adjacency matrix is a matrix .
represents the edge from to . In an unweighted graph we can just use the
values 0 and 1, in a weighted graph the value can be the weight of an edge. If 0
is a possible weight we can use INF (e.g. u32::MAX) to represent the absence
of an edge.
Analysis
- Space
- Degree:
- Adding/removing edge:
- Adding/removing vertex: (requires copying/shifting the whole thing)
- Checking for adjacency:
- Neighbours:
Cool Tricks
These all work for an unweighted graph, .
Counting Paths
The entry is the exact number of walks (paths allowing repeated edges and vertices) of length from to .
Counting Triangles
We can count the amount of triangles in the graph using the trace of the adjacency matrix.
The reason this works is because each triangle contributes to the paths 6 times: 3 different vertices and 2 directions for the walk for each.
Adjacency List
Here we just store for each vertex it’s adjacent vertices (aka neighbours). For undirected edges we store as neighbour of and as neighbour of .
I’d say from my experience this is for most algorithms the most common and
useful representation. In Rust this can be as simple as a Vec<Vec<usize>>, if
you need weights make it a tuple with the weight.
Analysis
I’ll assume we do this in Rust, and use a Vec for this, so we can get the
length of the neighbours in constant time.
- Space
- Degree:
- Adding/removing edge:
- Adding/removing vertex: (requires copying/shifting the whole thing)
- Checking for adjacency:
- Neighbours:
Edge List
We can represent a graph with just a list of the edges, the nodes we can then figure out from that. This has an issue that you can’t directly represent isolated nodes ( with ). Therefore you’d need some additional data.
This is only used rarely from my experience.
Special Graphs
There are some special graphs which have special properties and therefore are used or allow for special algorithms.
Tree
A tree is a graph which is connected and has no cycles. For some algorithms we also use rooted trees. There is a special vertex called root.
Vertices with we call leaves.
Trees have because some special properties of their form.
The following statements are all equivalent for a finite graph with .
- is connected and acyclic (aka a tree)
- For every pair there is a unique path
- is minimally connected. Deleting any edge makes it disconnected.
- is maximally acyclic1. That means that adding any new edge to it, makes it cyclic.
- is connected with .
- is acyclic with .
Others
There would be many more things which we could list here. For many I haven’t yet really found out about many specific algorithms etc. I’ll perhaps make posts for them or update this one. Examples would include
- Bipartite graphs (all trees are bipartite)
- Planar graphs
Footnotes
-
I admit, looked that term up, but knew about the property. ↩