The Programmer’s Short and Practical Guide to Graph Theory
By: Andre Ye
Tags: Coding, Computer Science, Python, Data Science, Programming

An undirected and unweighted graph is the simplest form of a graph (besides a single node). It consists of two types of elements only: nodes, which can be thought of as points, and edges, which connect these points together. There is no idea of distance/cost or direction, which is why it is undirected and unweighted.

Image source: Author

For instance, consider the following search problem, and represent it as an undirected and unweighted graph:

You have a padlock with two digits, initialized to 00. Each move, you can move one of the four two up or down (moving 0 up is 1, moving 0 down is 9 — the wheels are circular). There are “dead combinations,” in which the padlock will permanently lock if the combination ever equals one of those values. Find the minimum number of moves it takes to reach a target combination without locking, or if it is possible at all.

Dead combinations: [10, 90, 12]. Target: 11.

First, we construct a root, or head of the graph, which is necessary in scenarios where we need to generate the graph as we go. This is 00, or the root case from which branching cases will be generated.

The four neighbors of node 00 are 01, 10, 90, and 09, corresponding to various combinations of moving wheels up and down. Our graph already has five nodes and four edges.

Image source: Author

For each newly added node, we will continue searching for its neighbors and add it to the graph, unless it is a dead node.

Image source: Author

When we have found the target combination, we can retrace our steps and count how many it takes to reach the root node again. Alternatively, we could have been keeping track of the steps each time a node is generated.

Image source: Author

If you want to implement the solution to this problem, you can use a queue and breadth-first search, which is more efficient than actually coding a graph.

This was an example of using graph-based thinking. Since graphs are very ordered and clean structures, thinking and implementing possibilities as nodes and variants as neighbors can result in a complex but clean and understandable search. Additionally, using graphs allows for many studied methods in graph theory to be implemented to speed up search.

However, in this padlock problem, graphs did not actually need to be implemented. The most common method to implement a complete graph is to use two objects (classes), a Node , the primary building block, and a Graph , which is comprised of Node s and provides an interface to access information about the graph as a whole.

Image source: Author

For example, each element in the graph below can be represented in code as their own Node . Each is connected to each other through their Neighbors . If we were to call something like NodeA.Neighbors[1].Neighbors[1].Value , we should receive 2. This is because the second index of Node A’s neighbors is Node C, and the second index of Node C’s neighbors is Node B, which has a value of 2. This kind of easy connectivity allows for easy traversal.

For notation, ‘A 1’ means ‘Node A has value 1’. (Image source: Author)

A directed graph, or a graph where edges only go in one direction, is easy to implement with this design. For example, if a one-way edge were to connect Node A to Node B, Node A’s neighbor would be Node B, but Node B would have no neighbors. Rephrased, neighbors only indicate outbound edges. Note that bidirectional edges can still exist in directed graphs.

Image source: Author

If one were to traverse the graph according to neighbors and end up at Node C or Node F, they would be stuck because those nodes have no neighbors and hence no outbound directions.

Alternatively, a graph can be represented more simply, but at the cost of being less easy to traverse, with two lists representing the edges and nodes (vertices). These are sometimes called adjacency lists since they express in list format the adjacencies between edges (adjacencies being edges and adjacent nodes being neighbors).

V = [A, B, C, D, E, F]

E = [AB, AC, BC, CF, CE, DF, EA, FB, FD]

In the example above, V declares the nodes that exist, and E declares an edge from one node to another ( AC means A → C ). Since it is compact and simple notation, graphs will often be presented this way.

Alternatively, it could be written as a dictionary (map), in which the key is a starting node and its value is a list of elements it points to.

adj_l = {A:[B,C], B:[C], C:[F,E], D:[F], E:[A], F:{B,D]}

Graphs, both directed and undirected, can contain loops. A cycle graph is a graph consisting of only one cycle, in which there are no terminating nodes and one could traverse infinitely throughout the graph. A cyclic graph is a graph that consists of several cycle graphs, where traversals can still be infinite but more complex.

Image source: Author

For example, within the complete cyclic graph, A→B→C→D→A is a four-cycle graph, and E→F→G→E is a three-cycle graph. More hidden is another four-cycle graph, B→E→F→G→B.

Certain types of cycles within cyclic graphs, or other components within graphs in which each node is connected to each other node, are known as strongly connected components. For instance, E→F→G→E is a strongly connected component because each of the nodes {E, F, G} has a path to another, regardless of direction. B→E→F→G→B is also a strongly connected component. On the other hand, A→B→C→D→A is not because there is no connection between component members B and D.

On the other hand, as the name suggests, acyclic graphs are ones where no cycle exists, and any traversal long enough will eventually terminate. In the graph below, no matter which node you start on, a traversal will always terminate.

Image source: Author

It is not always true in more complex problems that edges can naively be treated as equal to travel. For instance, if you’re planning the best route to go from a start to an end destination, you’re going to consider not only the number of segments but also the distance and the cost.

For instance, the shortest path to go from S→E is S→D→F→E, which requires only three edge traversals. However, that route takes a very small, crowded street. Alternatively, S→A→B→C→E takes four edges but travels most of the distance along a highway, and the overall cost is lessened. When the notions of distance and cost are added to a graph, it becomes weighted.

Image source: Author

To implement this into our existing framework for graph structure, we can include for each element in Neighbors another number describing the cost to reach that neighbor. For instance, it may be stored in tuples [(n, c), (n, c)] , where n represents the node and c represents the cost.

Image source: Author

Often, graphs will also be presented in the form of a matrix, known as an adjacency matrix. This is not as compact as an adjacency list but can represent weighted graphs more naturally. In the matrix, each row and column represents a node and the cell located at (x, y) represents the edge y→x (or vice versa, it’s a matter of notation). If there is no edge, the value is 0. If there is, the value is the cost of that edge.

Image source: Author

Adjacency matrices also have the advantage of easy lookup of costs, even with unweighted graphs, over adjacency lists and an object-oriented representation. Note that undirected graphs will have symmetrical adjacency matrices. Since matrices are also easier to manipulate, many graph operations and algorithms are commonly implemented on adjacency matrices.

Various algorithms have been created to find the shortest path for weighted graphs, like Dijkstra’s algorithm (pronounced dike-strah). Essentially, Dijkstra’s algorithm is very similar to the brute-force style search discussed earlier with the padlock problem but does so in a way that is most logical. The rough outline of the algorithm is as follows:

Begin at the start node and initialize a list (priority queue) to keep track of which nodes to process. At each iteration of the algorithm, find the first element of the list. Process the element by finding all its neighboring nodes (that haven’t been explored before). For each neighbor, calculate the total distance/cost to reach that node from the start node. Put these neighbor nodes into the list such that the nodes with the lowest costs are at the front. Repeat until the end node has been processed.

There are plenty more in-depth resources about Dijkstra’s algorithm, but primarily, its main difference from a brute-force search is that it processes nodes currently with the smallest costs first, which is logically correct. This can speed up redundant searching by taking into account weights.

While powerful in many instances, Dijkstra is naïve in that it only chooses to process nodes that currently hold the best costs, in the hopes that the complete path will also hold a similarly small cost, when this may not be the case at all. This can be a problem in large graphs.

For instance, consider this grid of nodes, where each connection has the same cost to traverse; Dijkstra’s algorithm (slightly varying depending on implementation) will search through all the light nodes before arriving at the end node E . It’s like pouring a bucket of water at the location of node S and hoping it eventually spreads to the target node.

Image source: Author

The A Star algorithm and many other variants take into account these weaknesses and add enhancements, like stronger memory and direction to improve traversals throughout graphs. Machine learning, particularly reinforcement learning, is central to more recent methods of highly efficient graph traversal. In reinforcement learning, probabilities and states are often represented as graphs an agent traverses.

Graphs and graph-based thinking can be used in many other computer science problems, even when it is not obvious. Any time you approach a difficult problem, attempting to represent it using vertices and edges can inspire new ideas, simplify and reduce the problem, or even be one solution to the problem.

Some applications of graph theory in computer science include: