Graphs 101: Understanding Graph Data Structures

Elif İrem Kara Kadyrov
8 min readApr 3, 2023

--

Imagine a world where you have a bunch of friends and you want to visualize how you all are connected to each other. You might start by drawing a circle for each friend and then draw lines connecting the circles to show who is friends with whom. Congratulations, you’ve just created a graph!

In computer science, a graph is simply a collection of nodes (also called vertices) and edges that connect them. Just like your friends, the nodes in a graph can represent anything, like cities, websites, or even molecules. The edges represent the relationships between the nodes, such as roads between cities, hyperlinks between web pages, or chemical bonds between atoms.

So, why are graphs an important data structure?

Well, graphs are incredibly versatile and can be used to model many different kinds of real-world systems, from social networks to transportation systems to computer networks. By using graphs, we can better understand how these systems are connected and how they behave, which can help us make better decisions and solve complex problems.

Understanding the Different Types of Graphs and Their Applications

Graphs are a powerful data structure used to model and analyze relationships between objects or entities. There are different types of graphs, each with its unique characteristics and applications.

Directed Graphs (Digraphs)

A directed graph is a type of graph where the edges have a direction and can only be traversed in one direction. This means that each edge has a source node and a target node.

Directed graphs are useful for modeling systems where there is a clear direction or flow of information, such as electrical circuits, computer networks, and workflow systems. For example, a social media platform could use a directed graph to model the flow of information between users, with edges representing messages, posts, or comments.

Undirected Graphs

An undirected graph is a type of graph where the edges do not have a direction and can be traversed in either direction. In other words, there is no source or target node for each edge.

Undirected graphs are useful for modeling systems where the relationship between nodes is symmetric, such as social networks, transportation networks, or biological networks. For example, an undirected graph could be used to model a road network, where edges represent the roads and nodes represent the intersections.

Weighted Graphs

A weighted graph is a type of graph where each edge has a weight or cost associated with it. This weight can represent things like distance, time, or cost, depending on the context of the graph.

Weighted graphs are useful for modeling systems where the edges have different costs, such as transportation networks, supply chain networks, or social networks. For example, a weighted graph could be used to model a transportation network, where edges represent roads or rail lines and the weights represent the travel time or distance between them.

Unweighted Graphs

An unweighted graph is a type of graph where each edge has the same weight or cost. This means that all edges are equally important and can be traversed with the same cost.

Unweighted graphs are useful for modeling systems where all edges have the same importance, such as social networks, biological networks, or computer networks. For example, an unweighted graph could be used to model a social network, where edges represent relationships between individuals and nodes represent the individuals themselves.

Cyclic Graphs

A cyclic graph contains at least one cycle, which is a path that starts and ends at the same vertex. In other words, there is a sequence of edges that leads you back to where you started.

Cyclic graphs are commonly used to represent systems with feedback loops, such as control systems or social networks. They can also be used to model processes that repeat themselves over time, such as the seasons of the year or the phases of the moon.

Acyclic Graphs

Acyclic graphs, also known as directed acyclic graphs (DAGs), are used in many areas of computer science and mathematics. They are particularly useful in representing dependencies between tasks or events, such as in project management or scheduling problems. They are also used in machine learning and artificial intelligence, for example, in Bayesian networks, which represent probabilistic relationships between variables.

Different Ways of Representing a Graph

Graphs can be represented in different ways depending on the context and the type of operations that will be performed on them. Let’s explore two of the common representations.

Adjacency Matrix

An adjacency matrix is a two-dimensional array that represents the edges of a graph. The rows and columns of the matrix represent the vertices of the graph, and the value in each cell indicates whether there is an edge between the corresponding vertices or not. For an undirected graph, the matrix is symmetric. For a directed graph, the matrix probably is not symmetric.

Advantages: It is easy to determine whether there is an edge between two vertices in constant time (O(1)). It is also easy to perform matrix multiplication to compute certain graph properties.

Disadvantages: It requires O(V²) space, where V is the number of vertices in the graph, even if the graph is sparse (i.e., has few edges). Adding or removing edges can also be slow, as it requires updating the matrix entries.

Adjacency List

An adjacency list is a list of lists that represents the edges of a graph. Each vertex has a list of its adjacent vertices. For a directed graph, each edge is represented only once, whereas for an undirected graph, each edge is represented twice, once for each of its endpoints.

Advantages: It requires only O(V+E) space, where E is the number of edges in the graph, making it more memory-efficient for sparse graphs. Adding or removing edges is also faster, as it only requires updating the adjacency lists.

Disadvantages: It can be slower to determine whether there is an edge between two vertices, as it requires searching the adjacency list for one of the vertices.

Graph Traversal

As you may know, there are two commonly used graph traversal algorithms: Depth First Search(DFS) and Breadth First Search(BFS).

BFS is used to explore all the nodes at the current depth before moving on to nodes at the next depth. It uses a queue to store the nodes that are yet to be visited. The algorithm starts at a specified node and explores all the nodes that are reachable from it.

DFS, on the other hand, explores as far as possible along each branch before backtracking. It uses a stack to store the nodes that are yet to be visited. The algorithm starts at a specified node and explores as far as possible along each branch before backtracking.

Real-life examples of graph traversal methods include finding the shortest path between two cities in a road network, navigating a maze, and finding the optimal route for delivering packages to different destinations.

Let’s explore how can we perform different traversal methods with a simple graph.

Let’s say we have a simple undirected graph like above.

DFS Approach

Let’s assume we start at vertex A and we want to traverse the entire graph using DFS.

First, we represent the graph as an adjacency list:

const graph = {
A: ["B", "C"],
B: ["A", "D"],
C: ["A", "D"],
D: ["B", "C"]
};

Next, we define a dfs function that takes a starting vertex and uses a stack to keep track of the vertices to visit. We also use a visited object to keep track of which vertices we've already visited:

function dfs(startVertex) {
const stack = [startVertex];
const visited = {};

while (stack.length > 0) {
const currentVertex = stack.pop();

if (!visited[currentVertex]) {
visited[currentVertex] = true;
console.log(currentVertex);

graph[currentVertex].forEach((neighbor) => {
stack.push(neighbor);
});
}
}
}

// we will call the function like
dfs("A");

The output will be:

A
C
D
B

BFS Approach

We define a function that takes a starting vertex and uses a queue to keep track of the vertices to visit. We also use a visited object to keep track of which vertices we've already visited:

function bfs(startVertex) {
const queue = [startVertex];
const visited = {};

while (queue.length > 0) {
const currentVertex = queue.shift();

if (!visited[currentVertex]) {
visited[currentVertex] = true;
console.log(currentVertex);

graph[currentVertex].forEach((neighbor) => {
queue.push(neighbor);
});
}
}
}

// call the function
bfs("A");

The output will be:

A
B
C
D

Conclusion

Graphs might seem intimidating at first, but they’re actually pretty cool! They can be used to model all sorts of things, from your social network to your favorite transportation system. You can think of them as a web of interconnected nodes, each with its own story to tell.

To help you understand graphs better, we’ve gone over the different types of graphs you might encounter, like directed and undirected graphs, weighted and unweighted graphs, and cyclic and acyclic graphs. But don’t worry, you don’t have to memorize all these terms — just remember that each type of graph has its own quirks and is suited for different purposes.

We’ve also talked about different ways of representing graphs, like using adjacency matrices and adjacency lists. These are just fancy ways of organizing the information in your graph, and they each have their own strengths and weaknesses.

But what’s the point of all this if we can’t have some fun with it? That’s where graph traversal comes in! Think of it as a treasure hunt, where you get to visit each node in the graph in a specific order. We showed you two different methods for graph traversal — Depth-First Search (DFS) and Breadth-First Search (BFS). It’s up to you to decide which one you like best!

--

--