0% found this document useful (0 votes)
46 views20 pages

Unit 3 DSA

The document discusses graph representation and algorithms for graph traversal including depth-first search, breadth-first search, minimum spanning trees, Dijkstra's algorithm, and the shortest path problem. Examples are provided for implementing various graph algorithms.

Uploaded by

ronak828743
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
46 views20 pages

Unit 3 DSA

The document discusses graph representation and algorithms for graph traversal including depth-first search, breadth-first search, minimum spanning trees, Dijkstra's algorithm, and the shortest path problem. Examples are provided for implementing various graph algorithms.

Uploaded by

ronak828743
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 20

Unit 3

Graph representation-

Graphs are a fundamental data structure used in computer science and


mathematics to represent relationships or connections between objects. A graph
consists of a set of vertices (or nodes) and a set of edges that connect these
vertices. Graphs can be used to model a wide range of real-world systems, such
as social networks, transportation networks, and more.

Adjacency Matrix:

 An adjacency matrix is a two-dimensional array where the rows and


columns correspond to the vertices of the graph.
 The value at matrix[i][j] is typically 1 if there is an edge from vertex i to
vertex j, and 0 if there is no edge.
 For weighted graphs, the values in the matrix can represent the weights of
the edges.

DFS –

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 step by step process to implement the DFS traversal is given as follows -
1. First, create a stack with the total number of vertices in the graph.
2. Now, choose any vertex as the starting point of traversal, and push that
vertex into the stack.
3. After that, push a non-visited vertex (adjacent to the vertex on the top of
the stack) to the top of the stack.
4. Now, repeat steps 3 and 4 until no vertices are left to visit from the vertex
on the stack's top.
5. If no vertex is left, go back and pop a vertex from the stack.
6. Repeat steps 2, 3, and 4 until the stack is empty.

DFS(G,v) ( v is the vertex where the search starts )


Stack S := {}; ( start with an empty stack )
for each vertex u, set visited[u] := false;
push S, v;
while (S is not empty) do
u := pop S;
if (not visited[u]) then
visited[u] := true;
for each unvisited neighbour w of uu
push S, w;
end if
end while
END DFS()

Application of DFS Algorithm :

 For finding the path


 To test if the graph is bipartite
 For finding the strongly connected components of a graph
 For detecting cycles in a graph
Example of DFS

BFS –

BFS is an algorithm that explores a graph level by level, visiting all neighbours
of the current node before moving to the next level.
Starting from the root, all the nodes at a particular level are visited first and then
the nodes of the next level are traversed till all the nodes are visited.
To do this a queue is used. All the adjacent unvisited nodes of the current level
are pushed into the queue and the nodes of the current level are marked visited
and popped from the queue.

The step by step process to implement the BFS traversal is given as follows –

1. A queue (FIFO-First in First Out) data structure is used by BFS.


2. You mark any node in the graph as root and start traversing the data
from it.
3. BFS traverses all the nodes in the graph and keeps dropping them as
completed.
4. BFS visits an adjacent unvisited node, marks it as done, and inserts
it into a queue.
5. Removes the previous vertex from the queue in case no adjacent
vertex is found.
6. BFS algorithm iterates until all the vertices in the graph are
successfully traversed and marked as completed.
7. There are no loops caused by BFS during the traversing of data
from any node.

def bfs (graph, start):


visited = set() # To keep track of visited vertices
queue = deque([start]) # Initialize the queue with the starting
vertex

while queue:
vertex = queue.popleft() # Dequeue a vertex from the front of the
queue
if vertex not in visited:
print(vertex) # Process the visited vertex (you can replace
this with any desired operation)
visited.add(vertex)
# Enqueue unvisited neighbors
for neighbor in graph[vertex]:
if neighbor not in visited:
queue.append(neighbor)
Example of BFS

Minimum Spanning Tree –

A Minimum Spanning Tree (MST) is a fundamental concept in graph theory and


computer science. It is a subset of the edges of a connected, undirected graph
that connects all the vertices together with the minimum possible total edge
weight. MSTs are commonly used in network design, circuit design, and various
applications where you want to minimize the cost while ensuring connectivity.
Two well-known algorithms for finding the Minimum Spanning Tree are
Kruskal's algorithm and Prim's algorithm:
Kruskal's Algorithm:
Kruskal's algorithm is a greedy algorithm for finding the MST of a graph. It
works by repeatedly adding the smallest weight edge that does not form a cycle
in the MST. Here's an overview of the algorithm:
a. Sort all the edges in the graph by their weights in non-decreasing order.
b. Initialize an empty set to represent the MST.
c. Iterate through the sorted edges and add each edge to the MST if it does not
create a cycle.
d. Repeat step c until the MST contains (V - 1) edges, where V is the number of
vertices in the graph.
Kruskal's algorithm is relatively straightforward to implement and works well
for sparse graphs.

Prim's Algorithm:
Prim's algorithm is another greedy algorithm for finding the MST. It starts with
an arbitrary vertex and repeatedly adds the nearest vertex that is not in the MST.
Here's an overview of the algorithm:
a. Initialize the MST with a single vertex.
b. Repeat the following steps until the MST contains all vertices:
Select the minimum weight edge that connects a vertex in the MST to a vertex
outside the MST.
Add the selected edge and the new vertex to the MST.
Prim's algorithm is often more efficient for dense graphs or situations where you
have fast access to a data structure that efficiently finds the minimum edge.
Que: Define Shortest Path Problem with example ?
The shortest path problem involves finding the shortest path between two
vertices (or nodes) in a graph. Algorithms such as the Floyd-Warshall algorithm
and different variations of Dijkstra's algorithm are used to find solutions to the
shortest path problem. Applications of the shortest path problem include those
in road networks, logistics, communications, electronic design, power grid
contingency analysis, and community detection.
 Variations of the Shortest Path Problem

 Single-source shortest path (or SSSP) problem requires finding the


shortest path from a source node to all other nodes in a weighted graph
i.e. the sum of the weights of the edges in the paths is minimized.
 Breadth-first search (or BFS) is finding the shortest path from a
source node to all other nodes in an unweighted graph i.e. the number
of edges in the paths is minimized. Breadth-first search is a core
primitive for graph traversal and a basis for many higher-level graph
analysis algorithms. Algorithms for analyzing sparse relationships
represented as graphs provide crucial tools in many computational
fields ranging from genomics to electronic design automation to social
network analysis.
 All-pairs shortest path (or APSP) problem requires finding the
shortest path between all pairs of nodes in a graph.
 Single-source widest path (or SSWP) problem requires finding the
path from a source node to all other nodes in a weighted graph such
that the weight of the minimum-weight edge of the path is maximized.

Example :

How to find Shortest Paths from Source to all Vertices using Dijkstra’s
Algorithm ?

#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
/ Number of vertices in the graph
#define V 9

// A utility function to find the vertex with minimum


// distance value, from the set of vertices not yet included
// in shortest path tree
int minDistance(int dist[], bool sptSet[])
{
// Initialize min value
int min = INT_MAX, min_index;
for (int v = 0; v < V; v++)
if (sptSet[v] == false && dist[v] <= min)
min = dist[v], min_index = v;
return min_index;
}

// A utility function to print the constructed distance


// array
void printSolution(int dist[])
{
printf("Vertex \t\t Distance from Source\n");
for (int i = 0; i < V; i++)
printf("%d \t\t\t\t %d\n", i, dist[i]);
// Function that implements Dijkstra's single source
// shortest path algorithm for a graph represented using
// adjacency matrix representation
void dijkstra(int graph[V][V], int src)
{
int dist[V]; // The output array. dist[i] will hold the
// shortest
// distance from src to i

bool sptSet[V]; // sptSet[i] will be true if vertex i is


// included in shortest
// path tree or shortest distance from src to i is
// finalized

// Initialize all distances as INFINITE and stpSet[] as


// false
for (int i = 0; i < V; i++)
dist[i] = INT_MAX, sptSet[i] = false;

// Distance of source vertex from itself is always 0


dist[src] = 0;

// Find shortest path for all vertices


for (int count = 0; count < V - 1; count++) {
// Pick the minimum distance vertex from the set of
// vertices not yet processed. u is always equal to
// src in the first iteration.
int u = minDistance(dist, sptSet);
// Mark the picked vertex as processed
sptSet[u] = true;

// Update dist value of the adjacent vertices of the


// picked vertex.
for (int v = 0; v < V; v++)
//Update dist[v] only if is not in sptSet,
//there is an edge from u to v, and total
//weight of path from src to v through u is
//smaller than current value of dist[v]
if(!sptSet[v] && graph[u][v]
&& dist[u] != INT_MAX
&& dist[u] + graph[u][v] < dist[v])
dist[v] = dist[u] + graph[u][v];
}

// print the constructed distance array


printSolution(dist);
}

// driver's code
int main()
{
/* Let us create the example graph discussed above */
int graph[V][V] = { { 0, 4, 0, 0, 0, 0, 0, 8, 0 },
{ 4, 0, 8, 0, 0, 0, 0, 11, 0 },
{ 0, 8, 0, 7, 0, 4, 0, 0, 2 },
{ 0, 0, 7, 0, 9, 14, 0, 0, 0 },
{ 0, 0, 0, 9, 0, 10, 0, 0, 0 },
{ 0, 0, 4, 14, 10, 0, 2, 0, 0 },
{ 0, 0, 0, 0, 0, 2, 0, 1, 6 },
{ 8, 11, 0, 0, 0, 0, 1, 0, 7 },
{ 0, 0, 2, 0, 0, 0, 6, 7, 0 } };

// Function call
dijkstra(graph, 0);

return 0;
}

Output:
Vertex Distance from Source
0 0

1 4

2 12

3 19

4 21

5 11

6 9

7 8

8 14

Time Complexity: O(V2)


Auxiliary Space: O(V)

Example of Dijkstra’s Algorithm

Binary Search
Binary Search is defined as a searching algorithm used in a sorted array by repeatedly
dividing the search interval in half. The idea of binary search is to use the information that
the array is sorted and reduce the time complexity to O(log N).

Conditions for when to apply Binary Search in a Data Structure:


To apply Binary Search algorithm:
 The data structure must be sorted.
 Access to any element of the data structure takes constant time.

In this algorithm,
 Divide the search space into two halves by finding the middle index “mid”.

 Compare the middle element of the search space with the key.
 If the key is found at middle element, the process is terminated.
 If the key is not found at middle element, choose which half will be used as the
next search space.
 If the key is smaller than the middle element, then the left side is used
for next search.
 If the key is larger than the middle element, then the right side is used
for next search.
 This process is continued until the key is found or the total search space is
exhausted.

# Binary Search in python


def binarySearch(array, x, low, high):

# Repeat until the pointers low and high meet each other
while low <= high:

mid = low + (high - low)//2

if array[mid] == x:
return mid

elif array[mid] < x:


low = mid + 1

else:
high = mid - 1

return -1

array = [3, 4, 5, 6, 7, 8, 9]
x=4

result = binarySearch(array, x, 0, len(array)-1)

if result != -1:
print("Element is present at index " + str(result))
else:
print("Not found")

How does QuickSort work?

The key process in quickSort is a partition(). The target of partitions is to place the pivot
(any element can be chosen to be a pivot) at its correct position in the sorted array and put all
smaller elements to the left of the pivot, and all greater elements to the right of the
pivot.Partition is done recursively on each side of the pivot after the pivot is placed in its
correct position and this finally sorts the array.
Algorithm
1. QUICKSORT (array A, start, end)
{
2. if (start < end)
{
3. p = partition(A, start, end)
4. QUICKSORT (A, start, p - 1)
5. QUICKSORT (A, p + 1, end)
}
}
The partition algorithm
1. PARTITION (array A, start, end)
{
2. pivot ? A[end]
3. i ? start-1
4. for j ? start to end -1 {
5. do if (A[j] < pivot) {
6. then i ? i + 1
7. swap A[i] with A[j]
}
}
8. swap A[i+1] with A[end]
9. return i+1 }
How does Heap sort work ?

Heaps can be used in sorting an array. In max-heaps, maximum element will always be at the
root. Heap Sort uses this property of heap to sort the array.

Consider an array [Arr] which is to be sorted using Heap Sort.

 Initially build a max heap of elements in [Arr] .


 The root element, that is Arr[1], will contain maximum element of [Arr]. After that,
swap this element with the last element of [Arr] and heapify the max heap excluding
the last element which is already in its correct position and then decrease the length of
heap by one.
 Repeat the step 2, until all the elements are in their correct position.

Merge Sort
Merge sort is defined as a sorting algorithm that works by dividing an array into smaller
subarrays, sorting each subarray, and then merging the sorted subarrays back together to
form the final sorted array. In simple terms, we can say that the process of merge sort is to
divide the array into two halves, sort each half, and then merge the sorted halves back
together. This process is repeated until the entire array is sorted.

MergeSort(A, p, r):

if p > r
return

q = (p+r)/2

mergeSort(A, p, q)

mergeSort(A, q+1, r)

merge(A, p, q, r)

// Merge two subarrays L and M into arr


void merge(int arr[], int p, int q, int r) {

// Create L ← A[p..q] and M ← A[q+1..r]


int n1 = q - p + 1;
int n2 = r - q;

int L[n1], M[n2];

for (int i = 0; i < n1; i++)


L[i] = arr[p + i];
for (int j = 0; j < n2; j++)
M[j] = arr[q + 1 + j];

// Maintain current index of sub-arrays and main array


int i, j, k;
i = 0;
j = 0;
k = p;

// 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[p..r]
while (i < n1 && j < n2) {
if (L[i] <= M[j]) {
arr[k] = L[i];
i++;
} else {
arr[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[p..r]
while (i < n1) {
arr[k] = L[i];
i++;
k++;
}

while (j < n2) {


arr[k] = M[j];
j++;
k++;
}
}

Complexity of different Sorting Algorithms :

Hash Table
Hash Table is a data structure which stores data in an associative manner. In a hash table, data
is stored in an array format, where each data value has its own unique index value. Access of
data becomes very fast if we know the index of the desired data. Thus, it becomes a data
structure in which insertion and search operations are very fast irrespective of the size of the
data. Hash Table uses an array as a storage medium and uses hash technique to generate an
index where an element is to be inserted or is to be located from.

Hashing
Hashing is a technique to convert a range of key values into a range of indexes of an array.
We're going to use modulo operator to get a range of key values. Consider an example of
hash table of size 20, and the following items are to be stored. Item are in the (key, value)
format.
Linear Probing
As we can see, it may happen that the hashing technique is used to create an already used
index of the array. In such a case, we can search the next empty location in the array by
looking into the next cell until we find an empty cell. This technique is called linear probing.

Let us consider a simple hash function as “key mod 7” and a sequence of keys as 50, 700, 76,
85, 92, 73, 101,
which means hash(key)= key% S, here S=size of the table =7,indexed from 0 to 6.We can
define the hash function as per our choice if we want to create a hash table, although it is
fixed internally with a pre-defined formula.

How Quadratic Probing is done?


 If the slot hash(x) % S is full, then we try (hash(x) + 1*1) % S.
 If (hash(x) + 1*1) % S is also full, then we try (hash(x) + 2*2) % S.
 If (hash(x) + 2*2) % S is also full, then we try (hash(x) + 3*3) % S.
 This process is repeated for all the values of i until an empty slot is found.
For example: Let us consider a simple hash function as “key mod 7” and sequence of keys
as 50, 700, 76, 85, 92, 73, 101

You might also like