Download as pdf or txt
Download as pdf or txt
You are on page 1of 11

What is a Graph?

Introduction

When we talk about graphs, what comes to mind are the conventional graphs used to model data. In
computer science, the term “graph” has a completely different meaning.

The graph data structure plays a fundamental role in several applications such as GPS, neural
networks, peer to peer networks, search engine crawlers, and even social networking websites.

This section will explore their functionality and power. We will also look at how they are used to
solve a diverse range of problems.

Now, let’s talk about what a graph really is.

Graph Structure

A graph is a set of nodes that are connected to each other in the form of a network. First of all, we’ll
define the two basic components of a graph.

Vertex

A vertex is the most essential part of a graph. A collection of vertices forms a graph. In that sense,
vertices are similar to linked list nodes.

Edge

An edge is the link between two vertices. It can be uni-directional or bi-directional depending on
your graph. An edge can also have a cost associated with it (will be discussed in detail later).

Graph Terminologies

Degree of a Vertex: The total number of edges connected to a vertex. There are two types of
degrees:

In-Degree: The total number of incoming edges connected to a vertex.

Out-Degree: The total number of outgoing edges connected to a vertex.

Parallel Edges: Two undirected edges are parallel if they have the same end vertices. Two directed
edges are parallel if they have the same origin and destination.
Self-Loop: This occurs when an edge starts and ends on the same vertex.

Adjacency: Two vertices are said to be adjacent if there is an edge connecting them directly.

In the illustration above, the in-degree of both a and b is 1. Same goes for the out-degree of the
vertices. The in-degree and out-degree for c is 2 as it contains a self loop.

There are two common types of graphs:

1. Undirected

2. Directed

Undirected Graph #

In an undirected graph, the edges are by default, bi-directional. For a pair (2, 3), there exists an edge
between vertex 2 and 3 without any specific direction. You can go from vertex 2 to 3 or ,vice versa,
from 3 to 2 .

Let’s calculate the maximum possible edges for an undirected graph. We are denoting an edge
between vertex a and b as (a, b). So, the maximum possible edges of a graph with n vertices will be
all possible pairs of vertices of that graph, assuming that there are no self loops.

If a graph has n vertices, then there are C(n,2) possible pairs of vertices according to Combinatorics.
Solving C(n,2) by binomial coefficients gives us \frac{n(n-1)}{2}2n(n−1). Hence, there are \frac{n(n-
1)}{2}2n(n−1) maximum possible edges in an undirected graph.

You can see an example of an undirected graph below:

Directed Graph #

In a directed graph, the edges are unidirectional. For a pair (2, 3), there exists an edge from
vertex 2 towards vertex 3 and the only way to traverse is to go from 2 to 3, not the other way
around.
This changes the number of edges that can exist in the graph. For a directed graph with n vertices,
the minimum number of edges that can connect a vertex with every other vertex is n-1. This
excludes self loops.

If you have n vertices, then all the possible edges become n*(n-1).

Here’s an example of a directed graph:

Representation of Graphs
Ways to Represent a Graph
The two most common ways to represent a graph are:

Adjacency Matrix

Adjacency List

Adjacency Matrix

The adjacency matrix is a two dimensional matrix where each cell can contain a 0 or 1. The row and
column headings represent the vertices.

If a cell contains 1, there exists an edge between the corresponding vertices,


e.g., Matrix[0][1]=1Matrix[0][1]=1 shows that an edge exists between vertex 0 and 1.
In the image above, there is a directed graph that has an edge going from vertex 0 to vertex 1, so
there is a 1 at Matrix[0][1]Matrix[0][1] in the adjacency matrix. In the case of the undirected graph,
we would have Matrix[1][0] = 1Matrix[1][0]=1 as well since the edge is bidirectional.

For a directed graph, the usual convention is to think of the rows as sources and the columns as
destinations.

Adjacency List

An array of linked list is used to store all the edges in the graph. The size of the array is equal to the
number of vertices.

Each index in this array represents a specific vertex in the graph. The entry at index i of the array
contains a linked list containing the vertices that are adjacent to vertex i.

As you can see in the above figure, the linked list at index 0 contains vertices 1 and 2 to represent
the fact that vertices 1 and 2 are adjacent to vertex 0.
Graph Implementation
This lesson will cover the implementation of a unidirectional graph via adjacency list in C++. We will
also go through the time complexity of basic graph operations.

Introduction

At this point, we’ve understood the theoretical logic behind graphs. In this lesson, we will use the
knowledge we have to implement the graph data structure in C++. Our graph will have directed
edges.

The implementation will be based on the adjacency list model. The linked list class we created
earlier will be used to represent adjacent vertices.

As a refresher, here is the illustration of the graph we’ll be producing using an adjacency list:

The Graph Class

Graph class consists of two data members:

• The total number of vertices in the graph

• A list of linked lists to store adjacent vertices

So let’s get down to the implementation!

class Graph {
private:
//Total number of vertices
int vertices;
//definining an array which can hold multiple LinkedLists equal to the number
of vertices in the graph
LinkedList * array;
public:

Graph(int vertices) {
//Creating a new LinkedList for each vertex/index of the array
array=new LinkedList[vertices];
}
};

int main() {
Graph myGraph(5);
return 0;
}
Additional Functionality

Now, we’ll add two methods to make this class functional:

1. printGraph() - Prints the contents of the graph

2. addEdge() - Connects a source with a destination

3. class Graph {
4. private:
5. int vertices;
6. LinkedList * array;
7. public:
8. Graph(int v) {
9. array=new LinkedList[v];
10. vertices=v;
11. }
12.
13. void addEdge(int source, int destination) {
14. if (source < vertices && destination < vertices)
15. array[source].insertAtHead(destination);
16. }
17.
18. void printGraph() {
19. cout << "Adjacency List of Directed Graph" <<endl;
20. Node*temp;
21. for(int i=0; i<vertices; i++) {
22. cout << "|" << i << "| => ";
23. temp = (array[i]).getHead();
24.
25. while(temp != NULL) {
26. cout << "[" <<temp->data << "] -> ";
27. temp = temp->nextElement;
28. }
29. cout <<"NULL"<<endl;
30. }
31. }
32. };
33.
34. int main() {
35. Graph g(4);
36. g.addEdge(0, 1);
37. g.addEdge(0, 2);
38. g.addEdge(1, 3);
39. g.addEdge(2, 3);
40. cout << endl;
41. g.printGraph();
42. return 0;
43. }
addEdge (source, destination)

Thanks to the graph constructor, source and destination are already stored as
indices of our array. This function simply inserts a destination vertex into
the adjacency linked list of the source vertex by running the following line
of code:

array[source].insertAtHead(destination)

One important thing to note is that we are implementing a directed graph,


so addEdge(0, 1) is not equal to addEdge(1, 0).

printGraph()

This function uses a simple nested loop to iterate through the adjacency list.
Each linked list is being traversed here.

Undirected graph

So far, we have covered the implementation of a directed graph.

In the case of an undirected graph, we will have to create an edge from the
source to the destination and from the destination to the source, making it
a bidirectional edge:

array[source].insertAtHead(destination)
array[destination].insertAtHead(source)

Complexities of Graph Operations


Time Complexities

Below, you can find the time complexities for the 4 basic graph functions.

Note, that in this table V means the total number of vertices and E means the total number of edges
in the Graph.

Operation Adjacency List Adjacency Matrix

Add Vertex O(1) O(V2)

Remove Vertex O(V+E) O(V2)

Add Edge O(1) O(1)

Remove Edge O(E) O(1)


Adjacency List

Addition operations in adjacency lists take constant time as we only need to insert at the head node
of the corresponding vertex.

Removing an edge takes O(E) time, because in the worst case, all the edges could be at a single
vertex and, hence, we would have to traverse all E edges to reach the last one.

Removing a vertex takes O(V + E) time because we have to delete all its edges and then reindex the
rest of the list one step back in order to fill the deleted spot.

Adjacency Matrix

Edge operations are performed in constant time as we only need to manipulate the value in the
particular cell.

Vertex operations are performed in O(V2) since we need to add rows and columns. We will also
need to fill all the new cells.

Comparison

Both representations are suitable for different situations. If your model frequently manipulates
vertices, the adjacency list is a better choice.

If you are dealing primarily with edges, the adjacency matrix is the more efficient approach.

Keep these complexities in mind because they will give you a better idea about the time
complexities of the several algorithms we’ll see in this section.

What is a Bipartite Graph?


Introduction

The bipartite graph is a special member of the graph family. The vertices of this graph are divided
into two disjoint parts in such a way that no two vertices in the same part are adjacent to each
other.

The bipartite graph is a type of k-partite graph where k is 2. In a 5-partite graph, we would have 5
disjoint sets and in members of a set would not be adjacent to each other.
Can a Cyclic Graph be Bi-Partite?

A cyclic graph is one in which the edges form a cycle between the vertices. If you traverse a cyclic
graph, you would come back to a vertex which you have already visited. Here’s an example:

The question arises whether a cyclic graph can be a bipartite graph.

Yes it can.

In the illustration below, you can see that the graph has an even number of nodes, which means that
they can be divided into two disjoint sets having non-adjacent vertices.

If there were an odd number of vertices, the nodes could never be divided into two disjoint and non-
adjacent sets.

All the acyclic graphs can be bi-partite, but in the case of cyclic graphs, they must contain an even
number of vertices.
Types of bipartite graphs

Some popular types of bipartite graphs are:

Star Graph

Acyclic Graph

Path Graph

Graph Traversal Algorithms


Types of Graph Traversals

There are two basic techniques used for graph traversal:

Breadth First Search (BFS)

Depth First Search (DFS)

In order to understand these algorithms, we will have to view graphs from a slightly different
perspective.

Any traversal needs a starting point, but a graph does not have a linear structure like lists or stacks.
So how do we give graph traversal a better sense of direction?

This is where the concept of levels is introduced. Take any vertex as the starting point. This is the
lowest level in your search. The next level consists of all the vertices adjacent to your vertex. A level
higher would mean the vertices adjacent to these nodes.

With this is in mind, let’s begin our discussion on the two graph traversal algorithms.

1. Breadth First Search

The BFS algorithm earns its name because it grows breadth-wise. All the nodes in a certain level are
traversed before moving on to the next level.

The level-wise expansion ensures that for any starting vertex, you can reach all others, one level at a
time.
1→2→3→6→4→5

This happens because there is a bidirectional link between 1 and 6, making them adjacent to each
other.

Depth First Traversal

The DFS algorithm is the opposite of BFS in the sense that it grows depth-wise.

Starting from any node, we keep moving to an adjacent node until we reach the farthest level, then
we move back to the starting point and pick another adjacent node. Once again, we probe till the
farthest level and move back. This process continues until all nodes are visited.

You might also like