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

Graphs:

A Graph is a non-linear data structure consisting of nodes and edges. The nodes are sometimes also referred to as
vertices and the edges are lines or arcs that connect any two nodes in the graph. More formally a Graph can be
defined as,
A Graph consists of a finite set of vertices (or nodes) and set of Edges which connect a pair of nodes

In the above Graph, the set of vertices V = {0, 1, 2, 3, 4} and the set of edges E = {01, 12, 23,
34, 04, 14, 13}.
Graph Terminology

Vertex-Each node of the graph is represented as a vertex.


Edge- Edge represents a path between two vertices or a line between two vertices.
Path- Path represents a sequence of edges between the two vertices.
Loop- In a graph, if an edge is drawn from vertex to itself, it is called a loop.
In this example, we have a path from a vertex ‘V’ to itself. Such can be said as
a loop.

Degree of the Node


A degree of a node is the number of edges that are connected with that node. A node with degree 0 is
called as isolated node.
Adjacent nodes or neighbours For every edge, e = (u, v) that connects nodes u and v, the nodes u and v are the end-
points and are said to be the adjacent nodes or neighbours.

A graph can be directed or undirected.


In an undirected graph, edges do not have any direction associated with them. That is, if an edge is drawn
between nodes A and B, then the nodes can be traversed from A to B as well as from B to A.
Figure above shows an undirected graph because it does not give any information about the direction of the edges.

Figure above shows a directed graph. In a directed graph, edges form an ordered pair. If there is an edge from A to B, then
there is a path from A to B but not from B to A. The edge (A, B) is said to initiate from node A (also known as initial node)
and terminate at node B (terminal node).
Regular graph It is a graph where each vertex has the same number of neighbours. That is, every node has the same
degree. A regular graph with vertices of degree k is called a k–regular graph or a regular graph of degree k. Figure below
shows regular graphs.

4. Multi graph
A multigraph is an undirected graph in which multiple edges (and sometimes loops) are allowed.
Multiple edges are two or more edges that connect the same two vertices. A loop is an edge (directed
or undirected) that connects a vertex to itself; it may be permitted or not.
5. Simple graph
A simple graph is an undirected graph in which both multiple edges and loops are disallowed as
opposed to a multigraph. In a simple graph with n vertices, every vertex’s degree is at most n-1.
6. Weighted and Unweighted graph
A weighted graph associates a value (weight) with every edge in the graph. We can also use words
cost or length instead of weight.
An unweighted graph does not have any value (weight) associated with every edge in the graph. In
other words, an unweighted graph is a weighted graph with all edge weight as 1. Unless specified
otherwise, all graphs are assumed to be unweighted by default.

7. Complete graph
A complete graph is one in which every two vertices are adjacent: all edges that could exist are
present.
8. Connected graph
A Connected graph has a path between every pair of vertices. In other words, there are no
unreachable vertices. A disconnected graph is a graph that is not connected.

 Parallel Edges

In a graph, if a pair of vertices is connected by more than one edge,


then those edges are called parallel edges.
Multi Graph

A graph having parallel edges or self-loops or both is known as a


Multigraph.
Null Graph

A graph having no edges is called a Null Graph.


In the above graph, there are three vertices named ‘a’, ‘b’, and ‘c’, but there are no edges among
them. Hence it is a Null Graph.
Trivial Graph

A graph with only one vertex is called a Trivial Graph.

Most commonly used terms in Graphs


 An edge is (together with vertices) one of the two basic units out of which graphs are constructed.
Each edge has two vertices to which it is attached, called its endpoints.
 Two vertices are called adjacent if they are endpoints of the same edge.
 Outgoing edges of a vertex are directed edges that the vertex is the origin.
 Incoming edges of a vertex are directed edges that the vertex is the destination.
 The degree of a vertex in a graph is the total number of edges incident to it.
 In a directed graph, the out-degree of a vertex is the total number of outgoing edges, and the in-
degree is the total number of incoming edges.
 A vertex with in-degree zero is called a source vertex, while a vertex with out-degree zero is called a
sink vertex.
 An isolated vertex is a vertex with degree zero, which is not an endpoint of an edge.
 Path is a sequence of alternating vertices and edges such that the edge connects each successive
vertex.
 Cycle is a path that starts and ends at the same vertex.
 Simple path is a path with distinct vertices.
 A graph is Strongly Connected if it contains a directed path from u to v and a directed path
from v to u for every pair of vertices u, v.
 A directed graph is called Weakly Connected if replacing all of its directed edges with undirected
edges produces a connected (undirected) graph. The vertices in a weakly connected graph have either
out-degree or in-degree of at least 1.
 Connected component is the maximal connected subgraph of an unconnected graph.
 A bridge is an edge whose removal would disconnect the graph.
 Forest is a graph without cycles.
 Tree is a connected graph with no cycles. If we remove all the cycles from DAG (Directed Acyclic
Graph), it becomes a tree, and if we remove any edge in a tree, it becomes a forest.
 Spanning tree of an undirected graph is a subgraph that is a tree that includes all the vertices of
the graph.
Relationship between number of edges and vertices
For a simple graph with m edges and n vertices, if the graph is
 directed, then m = n×(n-1)
 undirected, then m = n×(n-1)/2
 connected, then m = n-1
 a tree, then m = n-1
 a forest, then m = n-1
 complete, then m = n×(n-1)/2
Adjacency Matrix Representation:
An Adjacency Matrix A [V] [V] is a 2D array of size V×V where V is the number of vertices in
an undirected graph. If there is an edge between Vx to Vy then the value of A [Vx] [Vy] =1
and
A [Vy] [Vx] =1(this is because it is an undirected graph and if it is possible to traverse from Vx to
Vy, the reverse also is possible), otherwise the value will be zero. And for a directed graph, if
there is an edge between Vx to Vy, then the value of A [Vx] [Vy] =1, otherwise the value will be
zero.
Adjacency Matrix of an Undirected Graph
Let us consider the following undirected graph and construct the adjacency matrix −

Adjacency matrix of the above undirected graph will be

a b c d

0 1 1 0
a

b 1 0 1 0

1 1 0 1
c

0 0 1 0
d
Adjacency Matrix of a Directed Graph
Let us consider the following directed graph and construct its adjacency matrix −

Adjacency matrix of the above directed graph will be −

a b c d

0 1 1 0
a

0 0 1 0
b

c 0 0 0 1

0 0 0 0
d

Graph Traversal - DFS


Graph traversal is a technique used for a searching vertex in a graph or visiting all vertices.
The graph traversal is also used to decide the order of vertices is visited in the search
process. A graph traversal finds the edges to be used in the search process without creating
loops. That means using graph traversal we visit all the vertices of the graph without getting
into looping path.

There are two graph traversal techniques and they are as follows..

1. DFS (Depth First Search)

2. BFS (Breadth First Search)

DFS algorithm in the data structure. It is a recursive algorithm to search all the
vertices of a tree data structure or a graph. The depth-first search (DFS) algorithm
starts with the initial node of graph G and goes deeper until we find the goal node or
the node with no children.
Because of the recursive nature, stack data structure can be used to implement the
DFS algorithm. The process of implementing the DFS is similar to the BFS algorithm.
The step by step process to implement the DFS traversal is given as follows -
We use the following steps to implement DFS traversal...
Step 1 - Define a Stack of size total number of vertices in the graph.
Step 2 - Select any vertex as starting point for traversal. Visit that vertex and
push it on to the Stack. Step 3 - Visit any one of the non-visited adjacent
vertices of a vertex which is at the top of stack and push it on to the stack.
Step 4 - Repeat step 3 until there is no new vertex to be visited from the vertex
which is at the top of the stack.
Step 5 - When there is no new vertex to visit then use back tracking and pop one vertex from the
stack.
Step 6 - Repeat steps 3, 4 and 5 until stack becomes Empty.
Step 7 - When stack becomes Empty, then produce final spanning tree by
removing unused edges from the graph

Back tracking is coming back to the vertex from which we reached the current
vertex.

Applications of DFS algorithm


The applications of using the DFS algorithm are given as follows -
o DFS algorithm can be used to implement the topological sorting.
o It can be used to find the paths between two vertices.
o It can also be used to detect cycles in the graph.
o DFS algorithm is also used for one solution puzzles.
o Example:
o
Program:
Breadth-first search (BFS)
BFS is an algorithm that is used to graph data or searching tree or traversing structures. The
full form of BFS is the Breadth-first search.
The algorithm efficiently visits and marks all the key nodes in a graph in an accurate
breadthwise fashion. This algorithm selects a single node (initial or source point) in a graph
and then visits all the nodes adjacent to the selected node. Remember, BFS accesses these
nodes one by one.
Once the algorithm visits and marks the starting node, then it moves towards the nearest
unvisited nodes and analyses them. Once visited, all nodes are marked. These iterations
continue until all the nodes of the graph have been successfully visited and marked.

We use the following steps to implement BFS traversal...

 Step 1 - Define a Queue of size total number of vertices in the graph.


 Step 2 - Select any vertex as starting point for traversal. Visit that vertex
and insert it into the Queue.
 Step 3 - Visit all the non-visited adjacent vertices of the vertex which is at
front of the Queue and insert them into the Queue.
 Step 4 - When there is no new vertex to be visited from the vertex which is
at front of the Queue then delete that vertex.
 Step 5 - Repeat steps 3 and 4 until queue becomes empty.
 Step 6 - When queue becomes empty, then produce final spanning tree by
removing unused edges from the graph
Program:
#include
#define MAX 10
void breadth_first_search(int adj[][MAX],int visited[],int start)
{
int queue[MAX],rear = –1,front =– 1, i;
queue[++rear] = start;
visited[start] = 1;
while(rear != front)
{
start = queue[++front];
if(start == 4)
printf("5\t");
else printf("%c \t",start + 65);
for(i = 0; i < MAX; i++)
{
if(adj[start][i] == 1 && visited[i] == 0)
{
queue[++rear] = i; visited[i] = 1;
}
}
}
}
int main()
{
int visited[MAX] = {0};
int adj[MAX][MAX], i, j;
printf("\n Enter the adjacency matrix: ");
for(i = 0; i < MAX; i++) for(j = 0; j < MAX; j++)
scanf("%d", &adj[i][j]);
breadth_first_search(adj,visited,0); return 0;
}
Output
Enter the adjacency matrix: 0 1 0 1 0 1 0 1 1 0 0 1 0 0 1 1 1 0 0 1 0 0 1 1 0
ABDCE

Quick Sort
QuickSort is a sorting algorithm based on the Divide and Conquer
algorithm that picks an element as a pivot and partitions the given array
around the picked pivot by placing the pivot in its correct position in the
sorted array.
1. An array is divided into subarrays by selecting a pivot
element (element selected from the array).

While dividing the array, the pivot element should be positioned in


such a way that elements less than pivot are kept on the left side and
elements greater than pivot are on the right side of the pivot.
2. The left and right subarrays are also divided using the same
approach. This process continues until each subarray contains a
single element.
3. At this point, elements are already sorted. Finally, elements are
combined to form a sorted array.
Selecting the Pivot Element:
o Pivot can be random, i.e. select the random pivot from the given array.
o Pivot can either be the rightmost element of the leftmost element of the given
array.
o Select median as the pivot element.
Example
Program:
//program to implement Quick sort
#include<stdio.h>
int partition(int A[],int low,int high)
{
int start,end,pivot,t;
pivot=A[low];
start=low;
end=high;
while(start<end)
{
while(A[start]<=pivot)
start++;
while(A[end]>pivot)
end--;
if(start<end)
{
t=A[start];
A[start]=A[end];
A[end]=t;
}
}
t=A[low];
A[low]=A[end];
A[end]=t;
return end;
}
void quicksort(int A[],int low,int high)
{
int loc;
if(low<high)
{
loc=partition(A,low,high);
quicksort(A,low,loc-1);
quicksort(A,loc+1,high);
}
}
int main()
{
int n,i;
int A[30];
printf("enter no of elements\n");
scanf("%d",&n);
printf("enter elements in array A:");
for(i=0;i<n;i++)
scanf("%d",&A[i]);
quicksort(A,0,n-1);
printf(" after sorting\n");
for(i=0;i<n;i++)
printf("%d\t",A[i]);
return 0;
}
Output:
enter no of elements
8
enter elements in array A:50
30
10
90
80
20
40
70
after sorting
10 20 30 40 50 70 80 90
Time Complexity
Advantages of Quick Sort:
 It is a divide-and-conquer algorithm that makes it easier to solve
problems.
 It is efficient on large data sets.
 It has a low overhead, as it only requires a small amount of
memory to function.
Disadvantages of Quick Sort:
 It has a worst-case time complexity of O(N2), which occurs when
the pivot is chosen poorly.
 It is not a good choice for small data sets.

Merge Sort
Merge sort is the sorting technique that follows the divide and conquer approach.
Merge sort is similar to the quick sort algorithm as it uses the divide and conquer
approach to sort the elements. It is one of the most popular and efficient sorting
algorithm. It divides the given list into two equal halves, calls itself for the two halves
and then merges the two sorted halves. We have to define the merge() function to
perform the merging.
The sub-lists are divided again and again into halves until the list cannot be divided
further. Then we combine the pair of one element lists into two-element lists, sorting
them in the process. The sorted two-element pairs is merged into the four-element
lists, and so on until we get the sorted list.
Let the elements of array are –

According to the merge sort, first divide the given array into two equal halves. Merge
sort keeps dividing the list into equal parts until it cannot be further divided.
As there are eight elements in the given array, so it is divided into two arrays of size
4.
Now, again divide these two arrays into halves. As they are of size 4, so divide them
into new arrays of size 2.

Now, again divide these arrays to get the atomic value that cannot be further divided.

Now, combine them in the same manner they were broken.


In combining, first compare the element of each array and then combine them into
another array in sorted order.
So, first compare 12 and 31, both are in sorted positions. Then compare 25 and 8,
and in the list of two values, put 8 first followed by 25. Then compare 32 and 17, sort
them and put 17 first followed by 32. After that, compare 40 and 42, and place them
sequentially.

In the next iteration of combining, now compare the arrays with two data values and
merge them into an array of found values in sorted order.

Now, there is a final merging of the arrays. After the final merging of above arrays,
the array will look like -

Now, the array is completely sorted.


Merge sort complexity
Case Time Complexity

Best Case O(n*logn)


Average Case O(n*logn)

Worst Case O(n*logn)

Program:
// Merge sort implementation
#include <stdio.h>
// Merge two subarrays L and M into arr
void merge(int a[], int low, int mid, int high)
{
// Create L ← A[low..mid] and M ← A[mid+1..high]
int n1 = mid - low + 1;
int n2 = high - mid;
int L[n1], M[n2];
for (int i = 0; i < n1; i++)
L[i] = a[low + i];
for (int j = 0; j < n2; j++)
M[j] = a[mid + 1 + j];
// Maintain current index of sub-arrays and main array
int i, j, k;
i = 0;
j = 0;
k = low;
// Until we reach either end of either L or M, pick larger among
// elements L and M and place them in the correct position at A[low..high]
while (i < n1 && j < n2) {
if (L[i] <= M[j]) {
a[k] = L[i];
i++;
}
else
{
a[k] = M[j];
j++;
}
k++;
}
// When we run out of elements in either L or M,
// pick up the remaining elements and put in A[low..high]
while (i < n1) {
a[k] = L[i];
i++;
k++;
}
while (j < n2) {
a[k] = M[j];
j++;
k++;
}
}
// Divide the array into two subarrays, sort them and merge them
void mergeSort(int a[], int l, int r) {
if (l < r) {
// m is the point where the array is divided into two subarrays
int m =( l + r) / 2;
mergeSort(a, l, m);
mergeSort(a, m + 1, r);
// Merge the sorted subarrays
merge(a, l, m, r);
}
}

// Print the array


void printArray(int a[], int size) {
for (int i = 0; i < size; i++)
printf("%d ", a[i]);
printf("\n");
}
// Driver program
int main()
{
int n,i, a[30];
printf("enter no of elements\n");
scanf("%d",&n);
printf("enter elements in array A:");
for(i=0;i<n;i++)
scanf("%d",&a[i]);
printf("List before sorting\n");
for(i = 0; i <n ; i++)
printf("%d ", a[i]);
mergeSort(a, 0, n - 1);
printf("Sorted array: \n");
printArray(a, n);
}
Output:
enter no of elements
9
enter elements in array A:9
8
1
2
8
7
5
4
3
List before sorting
981287543
Sorted array:
123457889

Merge( ) Function Explained Step-By-Step


Example:

The array A[0..5] contains two sorted subarrays A[0..3] and A[4..5]. Let us
see how the merge function will merge the two arrays.
void merge(int arr[], int low, int mid, int high) {
// Here, low = 0, mid = 4, high = 6 (size of array)
Step 1: Create duplicate copies of sub-arrays to be sorted
// Create L ← A[low..mid] and M ← A[mid+1..high]
int n1 = mid - low + 1 = 3 - 0 + 1 = 4;
int n2 = high - mid = 5 - 3 = 2;
int L[4], M[2];
for (int i = 0; i < 4; i++)
L[i] = a[p + i];
// L[0,1,2,3] = A[0,1,2,3] = [1,5,10,12]

for (int j = 0; j < 2; j++)


M[j] = a[mid+ 1 + j];
// M[0,1] = A[4,5] = [6,9]

Step 2: Maintain current index of sub-arrays and main array


int i, j, k;
i = 0;
j = 0;
k = p;
Step 3: Until we reach the end of either L or M, pick larger among
elements L and M and place them in the correct position at
A[low..high]
while (i < n1 && j < n2) {
if (L[i] <= M[j]) {
a[k] = L[i]; i++;
}
else {
a[k] = M[j];
j++;
}
k++;
}
Step 4: When we run out of elements in either L or M, pick up the
remaining elements and put in A[low..high]
// We exited the earlier loop because j < n2 doesn't hold
while (i < n1)
{
a[k] = L[i];
i++;
k++;
}
// We exited the earlier loop because i < n1 doesn't hold
while (j < n2)
{
a[k] = M[j];
j++;
k++;
}
}

This step would have been needed if the size of M was greater than L.
At the end of the merge function, the subarray A[p..r] is sorted.

Heap Sort
A heap is a complete binary tree, and the binary tree is a tree in which the node can have
the utmost two children. A complete binary tree is a binary tree in which all the levels except
the last level, i.e., leaf node, should be completely filled, and all the nodes should be left-
justified.
Heap sort processes the elements by creating the min-heap or max-heap using the elements
of the given array. Min-heap or max-heap represents the ordering of array in which the root
element represents the minimum or maximum element of the array.
Heap Sort Algorithm
To solve the problem follow the below idea:
First convert the array into heap data structure using heapify, then one by
one delete the root node of the Max-heap and replace it with the last node
in the heap and then heapify the root of the heap. Repeat this process until
size of heap is greater than 1.
 Build a heap from the given input array.
 Repeat the following steps until the heap contains only one
element:
 Swap the root element of the heap (which is the largest
element) with the last element of the heap.
 Remove the last element of the heap (which is now in the
correct position).
 Heapify the remaining elements of the heap.
 The sorted array is obtained by reversing the order of the elements
in the input array.
To understand heap sort more clearly, let’s take an unsorted array and try to
sort it using heap sort.
Consider the array: arr[] = {4, 10, 3, 5, 1}.
Build Complete Binary Tree: Build a complete binary tree from the array.

Transform into max heap: After that, the task is to construct a tree from
that unsorted array and try to convert it into max heap.
 To transform a heap into a max-heap, the parent node should
always be greater than or equal to the child nodes
 Here, in this example, as the parent node 4 is smaller
than the child node 10, thus, swap them to build a max-
heap.
 Now, 4 as a parent is smaller than the child 5, thus swap both of
these again and the resulted heap and array should be like this:

Perform heap sort: Remove the maximum element in each step (i.e., move it
to the end position and remove that) and then consider the remaining
elements and transform it into a max heap.
 Delete the root element (10) from the max heap. In order to delete
this node, try to swap it with the last node, i.e. (1). After removing
the root element, again heapify it to convert it into max heap.
 Resulted heap and array should look like this:

 Repeat the above steps and it will look like the following:

 Now remove the root (i.e. 3) again and perform heapify.

 Now when the root is removed once again it is sorted. and the
sorted array will be like arr[] = {1, 3, 4, 5, 10}.

Implementation of Heap Sort


// Heap Sort in C
#include <stdio.h>
// Function to swap the position of two elements
void swap(int* a, int* b)
{
int temp = *a;
*a = *b;
*b = temp;
}

// To heapify a subtree rooted with node i


// which is an index in arr[].
// n is size of heap
void heapify(int arr[], int N, int i)
{
// Find largest among root, left child and right child
// Initialize largest as root
int largest = i;
int left = 2 * i + 1;
int right = 2 * i + 2;
// If left child is larger than root
if (left < N && arr[left] > arr[largest])
largest = left;

// If right child is larger than largest so far


if (right < N && arr[right] > arr[largest])
largest = right;
// Swap and continue heapifying
// if root is not largest
// If largest is not root
if (largest != i)
{
swap(&arr[i], &arr[largest]);
// Recursively heapify the affected sub-tree
heapify(arr, N, largest);
}
}

// Main function to do heap sort


void heapSort(int arr[], int N)
{
// Build max heap
for (int i = N / 2 - 1; i >= 0; i--)
heapify(arr, N, i);
// Heap sort
for (int i = N - 1; i >= 0; i--) {
swap(&arr[0], &arr[i]);
// Heapify root element to get highest element at
// root again
heapify(arr, i, 0);
}
}

// A utility function to print array of size n


void printArray(int arr[], int N)
{
for (int i = 0; i < N; i++)
printf("%d ", arr[i]);
printf("\n");
}

// Driver's code
int main()
{
int arr[100],i,N;
printf("enter size of array N:");
scanf("%d",&N);
printf("enter array elements:");
for(i=0;i<N;i++)
scanf("%d",&arr[i]);
// Function call
heapSort(arr, N);
printf("Sorted array is\n");
printArray(arr, N);
}

You might also like