UNIT -4 DSA
UNIT -4 DSA
Subject:-DSA
In selection sort, the first smallest element is selected from the unsorted
array and placed at the first position. After that second smallest element is
selected and placed in the second position. The process continues until the
array is entirely sorted.
Step 6 – Exit
To understand the working of the Selection sort algorithm, let's take an unsorted array. It will be
easier to understand the Selection sort via an example.
Selection sort complexity
Now, let's see the time complexity of selection sort in best case, average
case, and in worst case. We will also see the space complexity of the
selection sort.
. Time Complexity
Case Time Complexity
o Best Case Complexity - It occurs when there is no sorting required, i.e. the
array is already sorted. The best-case time complexity of selection sort
is O(n2).
o Average Case Complexity - It occurs when the array elements are in
jumbled order that is not properly ascending and not properly descending.
The average case time complexity of selection sort is O(n2).
o Worst Case Complexity - It occurs when the array elements are required to
be sorted in reverse order. That means suppose you have to sort the array
elements in ascending order, but its elements are in descending order. The
worst-case time complexity of selection sort is O(n2).
o
2. Space Complexity
Stable YES
Step 5 – Exit.
1. Time Complexity
Case Time Complexity
o Best Case Complexity - It occurs when there is no sorting required, i.e. the
array is already sorted. The best-case time complexity of bubble sort is O(n).
o Average Case Complexity - It occurs when the array elements are in
jumbled order that is not properly ascending and not properly descending.
The average case time complexity of bubble sort is O(n2).
o Worst Case Complexity - It occurs when the array elements are required to
be sorted in reverse order. That means suppose you have to sort the array
elements in ascending order, but its elements are in descending order. The
worst-case time complexity of bubble sort is O(n2).
2. Space Complexity
Space Complexity O(1)
Stable YES
The same approach is applied in insertion sort. The idea behind the insertion
sort is that first take one element, iterate it through the sorted array.
Although it is simple to use, it is not appropriate for large data sets as the
time complexity of insertion sort in the average case and worst case
is O(n2), where n is the number of items. Insertion sort is less efficient than
the other sorting algorithms like heap sort, quick sort, merge sort, etc.
o Simple implementation
o Efficient for small data sets
o Adaptive, i.e., it is appropriate for data sets that are already
substantially sorted.
Algorithm
The simple steps of achieving the insertion sort are listed as follows -
Step 1 - If the element is the first element, assume that it is already sorted.
Return 1.
Step3 - Now, compare the key with all elements in the sorted array.
Step 4 - If the element in the sorted array is smaller than the current
element, then move to the next element. Else, shift greater elements in the
array towards the right.
1. Time Complexity
Case Time Complexity
o Best Case Complexity - It occurs when there is no sorting required, i.e. the
array is already sorted. The best-case time complexity of insertion sort
is O(n).
o Average Case Complexity - It occurs when the array elements are in
jumbled order that is not properly ascending and not properly descending.
The average case time complexity of insertion sort is O(n2).
o Worst Case Complexity - It occurs when the array elements are required to
be sorted in reverse order. That means suppose you have to sort the array
elements in ascending order, but its elements are in descending order. The
worst-case time complexity of insertion sort is O(n2).
2. Space Complexity
Space Complexity O(1)
Stable YES
Divide: In Divide, first pick a pivot element. After that, partition or rearrange
the array into two sub-arrays such that each element in the left sub-array is
less than or equal to the pivot element and each element in the right sub-
array is larger than the pivot element.
Quicksort picks an element as pivot, and then it partitions the given array
around the picked pivot element. In quick sort, a large array is divided into
two arrays in which one holds values that are smaller than the specified
value (Pivot), and another array holds the values that are greater than the
pivot.
After that, left and right sub-arrays are also partitioned using the same
approach. It will continue until the single element remains in the sub-array.
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.
Algorithm
Algorithm:
Partition Algorithm:
In the given array, we consider the leftmost element as pivot. So, in this
case, a[left] = 24, a[right] = 27 and a[pivot] = 24.
Since, pivot is at left, so algorithm starts from right and move towards left.
Now, a[pivot] < a[right], so algorithm moves forward one position towards
left, i.e. -
Now, a[left] = 19, a[right] = 24, and a[pivot] = 24. Since, pivot is at right, so
algorithm starts from left and moves to right.
Now, a[left] = 9, a[right] = 24, and a[pivot] = 24. As a[pivot] > a[left], so algorithm moves one
position to right as -
Now, a[left] = 29, a[right] = 24, and a[pivot] = 24. As a[pivot] < a[left], so, swap a[pivot] and
a[left], now pivot is at left, i.e. -
Since, pivot is at left, so algorithm starts from right, and move to left. Now, a[left] = 24, a[right]
= 29, and a[pivot] = 24. As a[pivot] < a[right], so algorithm moves one position to left, as -
Now, a[pivot] = 24, a[left] = 24, and a[right] = 14. As a[pivot] > a[right], so, swap a[pivot] and
a[right], now pivot is at right, i.e. -
Now, a[pivot] = 24, a[left] = 14, and a[right] = 24. Pivot is at right, so the algorithm starts from
left and move to right.
Now, a[pivot] = 24, a[left] = 24, and a[right] = 24. So, pivot, left and right are pointing the same
element. It represents the termination of procedure.
Element 24, which is the pivot element is placed at its exact position.
Elements that are right side of element 24 are greater than it, and the
elements that are left side of element 24 are smaller than it.
Now, in a similar manner, quick sort algorithm is separately applied to the
left and right sub-arrays. After sorting gets done, the array will be -
Quicksort complexity
Now, let's see the time complexity of quicksort in best case, average case,
and in worst case. We will also see the space complexity of quicksort.
1. Time Complexity
Case Time Complexity
o Best Case Complexity - In Quicksort, the best-case occurs when the pivot
element is the middle element or near to the middle element. The best-case
time complexity of quicksort is O(n*logn).
o Average Case Complexity - It occurs when the array elements are in
jumbled order that is not properly ascending and not properly descending.
The average case time complexity of quicksort is O(n*logn).
o Worst Case Complexity - In quick sort, worst case occurs when the pivot
element is either greatest or smallest element. Suppose, if the pivot element
is always the last element of the array, the worst case would occur when the
given array is sorted already in ascending or descending order. The worst-
case time complexity of quicksort is O(n2).
2. Space Complexity
Space Complexity O(n*logn)
Stable NO
Before knowing more about the heap sort, let's first see a brief description
of Heap.
What is a heap?
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.
Algorithm
1. HeapSort(arr)
2. BuildMaxHeap(arr)
3. for i = length(arr) to 2
4. swap arr[1] with arr[i]
5. heap_size[arr] = heap_size[arr] ? 1
6. MaxHeapify(arr,1)
7. End
BuildMaxHeap(arr)
1. BuildMaxHeap(arr)
2. heap_size(arr) = length(arr)
3. for i = length(arr)/2 to 1
4. MaxHeapify(arr,i)
5. End
MaxHeapify(arr,i)
1. MaxHeapify(arr,i)
2. L = left(i)
3. R = right(i)
4. if L ? heap_size[arr] and arr[L] > arr[i]
5. largest = L
6. else
7. largest = i
8. if R ? heap_size[arr] and arr[R] > arr[largest]
9. largest = R
10.if largest != i
11.swap arr[i] with arr[largest]
12.MaxHeapify(arr,largest)
13.End
Working of Heap sort Algorithm
Now, let's see the working of the Heap sort Algorithm.
In heap sort, basically, there are two phases involved in the sorting of
elements. By using the heap sort algorithm, they are as follows -
o The first step includes the creation of a heap by adjusting the elements of the
array.
o After the creation of heap, now remove the root element of the heap
repeatedly by shifting it to the end of the array, and then store the heap
structure with the remaining elements.
Now let's see the working of heap sort in detail by using an example. To
understand it more clearly, let's take an unsorted array and try to sort it
using heap sort. It will make the explanation clearer and easier.
First, we have to construct a heap from the given array and convert it into
max heap.
After converting the given heap into max heap, the array elements are -
Next, we have to delete the root element (89) from the max heap. To delete
this node, we have to swap it with the last node, i.e. (11). After deleting the
root element, we again have to heapify it to convert it into max heap.
After swapping the array element 89 with 11, and converting the heap into
max-heap, the elements of array are -
In the next step, again, we have to delete the root element (81) from the
max heap. To delete this node, we have to swap it with the last node,
i.e. (54). After deleting the root element, we again have to heapify it to
convert it into max heap.
After swapping the array element 81 with 54 and converting the heap into
max-heap, the elements of array are -
In the next step, we have to delete the root element (76) from the max heap
again. To delete this node, we have to swap it with the last node,
i.e. (9). After deleting the root element, we again have to heapify it to
convert it into max heap.
After swapping the array element 76 with 9 and converting the heap into
max-heap, the elements of array are -
In the next step, again we have to delete the root element (54) from the
max heap. To delete this node, we have to swap it with the last node,
i.e. (14). After deleting the root element, we again have to heapify it to
convert it into max heap.
After swapping the array element 54 with 14 and converting the heap into
max-heap, the elements of array are -
In the next step, again we have to delete the root element (22) from the
max heap. To delete this node, we have to swap it with the last node,
i.e. (11). After deleting the root element, we again have to heapify it to
convert it into max heap.
After swapping the array element 22 with 11 and converting the heap into
max-heap, the elements of array are -
In the next step, again we have to delete the root element (14) from the
max heap. To delete this node, we have to swap it with the last node,
i.e. (9). After deleting the root element, we again have to heapify it to
convert it into max heap.
After swapping the array element 14 with 9 and converting the heap into
max-heap, the elements of array are -
In the next step, again we have to delete the root element (11) from the
max heap. To delete this node, we have to swap it with the last node,
i.e. (9). After deleting the root element, we again have to heapify it to
convert it into max heap.
After swapping the array element 11 with 9, the elements of array are -
Now, heap has only one element left. After deleting it, heap will be empty.
After completion of sorting, the array elements are -
1. Time Complexity
Case Time Complexity
o Best Case Complexity - It occurs when there is no sorting required, i.e. the
array is already sorted. The best-case time complexity of heap sort is O(n
logn).
o Average Case Complexity - It occurs when the array elements are in
jumbled order that is not properly ascending and not properly descending.
The average case time complexity of heap sort is O(n log n).
o Worst Case Complexity - It occurs when the array elements are required to
be sorted in reverse order. That means suppose you have to sort the array
elements in ascending order, but its elements are in descending order. The
worst-case time complexity of heap sort is O(n log n).
The time complexity of heap sort is O(n logn) in all three cases (best case,
average case, and worst case). The height of a complete binary tree having n
elements is logn.
2. Space Complexity
Space Complexity O(1)
Stable N0
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.
Algorithm
In the following algorithm, arr is the given array, beg is the starting
element, and end is the last element of the array.
The important part of the merge sort is the MERGE function. This function
performs the merging of two sorted sub-arrays that are A[beg…
mid] and A[mid+1…end], to build one sorted array A[beg…end]. So, the
inputs of the MERGE function are A[], beg, mid, and end.
Working of Merge sort Algorithm
Now, let's see the working of merge sort Algorithm.
To understand the working of the merge sort algorithm, let's take an unsorted array. It will be
easier to understand the merge sort via an example.
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.
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 -
1. Time Complexity
Case Time Complexity
o Best Case Complexity - It occurs when there is no sorting required, i.e. the
array is already sorted. The best-case time complexity of merge sort
is O(n*logn).
o Average Case Complexity - It occurs when the array elements are in
jumbled order that is not properly ascending and not properly descending.
The average case time complexity of merge sort is O(n*logn).
o Worst Case Complexity - It occurs when the array elements are required to
be sorted in reverse order. That means suppose you have to sort the array
elements in ascending order, but its elements are in descending order. The
worst-case time complexity of merge sort is O(n*logn).
2. Space Complexity
Space Complexity O(n)
Stable YES
The process of radix sort works similar to the sorting of students names,
according to the alphabetical order. In this case, there are 26 radix formed
due to the 26 alphabets in English. In the first pass, the names of students
are grouped according to the ascending order of the first letter of their
names. After that, in the second pass, their names are grouped according to
the ascending order of the second letter of their name. And the process
continues until we find the sorted list.
Now, let's see the algorithm of Radix sort.
Algorithm
1. radixSort(arr)
2. max = largest element in the given array
3. d = number of digits in the largest element (or, max)
4. Now, create d buckets of size 0 - 9
5. for i -> 0 to d
6. sort the array elements using counting sort (or any stable sort) according to the digi
ts at
7. the ith place
In Hashing technique, the hash table and hash function are used. Using the
hash function, we can calculate the address at which the value can be
stored.
The main idea behind the hashing is to create the (key/value) pairs. If the
key is given, then the algorithm computes the index at which the value
would be stored. It can be written as:
Index = hash(key)
- In this method, each key is divided by a suitable number so that the quotients can be
used for addressing the records, eg. Suppose the keys are 101, 201, 301, 401, 501 and
let suitable number 100 then the addresses are 1, 2, 3, 4, 5 respectively.
- In this method, the keys are divided by a suitable number and the remainder is used
for addressing the records.
In this method
Extract a suitable number of digits from the middle of the squared value to get the
address of the record on the disk.
e.g. Let there be a file with 80 records then 2 digits address is required. Let the key
value of the record be 583, then square of 583-339889. Now drop the first two and last
two digits of this number. We get 98. So physical address of the location of the record
will be 98 which is the middle digits of the square of the key value.
- Truncation method is also a division method which uses powers of 10 for division.
Suppose a digits key field is to be converted into 4 digit addresses, then the rightmost 4
digits of the keys can be used as addresses.
Thus if the key-field is 234658329, then the address of the key is 8329.
Truncation gives desirable results if the keys are continuous.
In this method any suitable group of digits can be extracted from the key-field.
- Let the given key value be 987654321, the three digit address can be extracted from
the middle to give address as 654. The extraction need not be a continuous string of
digits. The address can be formed by extracting the second, fourth and seventh digit,
giving the address as 863. Extraction is better than truncation method.
- In this method the key is split into pieces and suitable arithmetic operations are done
on the pieces.
e.g.
(i) Folding:-
Key: 285652
Address: 937
Key: 198765432
In this method reverse the digits from the left half of the key before addition, which is
known as boundary folding.
Key : 289649
Address 631.
Collision-handling techniques:-
(b) Chaining.
In open addressing if the address location to which the key is hashed is free, the
element is stored at that location.
In case it is already filled, then other address locations are examined systematically to
find a free address location.
Definition
A graph G can be defined as an ordered set G(V, E) where V(G) represents the set of vertices
and E(G) represents the set of edges which are used to connect these vertices.
A Graph G(V, E) with 5 vertices (A, B, C, D, E) and six edges ((A,B), (B,C), (C,E), (E,D),
(D,B), (D,A)) is shown in the following figure.
Closed Path
A path will be called as closed path if the initial node is same as terminal
node. A path will be closed path if V0=VN.
Simple Path
If all the nodes of the graph are distinct with an exception V 0=VN, then such
path P is called as closed simple path.
Cycle
A cycle can be defined as the path which has no repeated edges or vertices
except the first and last vertices.
Connected Graph
A connected graph is the one in which some path exists between every two
vertices (u, v) in V. There are no isolated nodes in connected graph.
Complete Graph
A complete graph is the one in which every node is connected with all other
nodes. A complete graph contain n(n-1)/2 edges where n is the number of
nodes in the graph.
Weighted Graph
In a weighted graph, each edge is assigned with some data such as length or
weight. The weight of an edge e can be given as w(e) which must be a
positive (+) value indicating the cost of traversing the edge.
Digraph
A digraph is a directed graph in which each edge of the graph is associated
with some direction and the traversing can be done only in the specified
direction.
Loop
An edge that is associated with the similar end points can be called as Loop.
Adjacent Nodes
If two nodes u and v are connected via an edge e, then the nodes u and v
are called as neighbours or adjacent nodes.
Types of Graph
1. Undirected graph
An undirected graph (graph) is a graph in which edges have no orientation.
The edge (x, y) is identical to edge (y, x) , i.e., they are not ordered pairs.
The maximum number of edges possible in an undirected graph without a
loop is n×(n-1)/2 .
2. Directed graph
A Directed graph (digraph) is a graph in which edges have orientations, i.e.,
The edge (x, y) is not identical to edge (y, x) .
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.
Graph Representations
In graph theory, a graph representation is a technique to store graph into the
memory of computer.
To represent a graph, we just need the set of vertices, and for each vertex
the neighbors of the vertex (vertices which is directly connected to it by an
edge). If it is a weighted graph, then the weight will be associated with each
edge.
1. Adjacency Matrix
o Adjacency matrix is a sequential representation.
o It is used to represent which nodes are adjacent to each other. i.e. is there
any edge connecting nodes to a graph.
o In this representation, we have to construct a nXn matrix A. If there is any
edge from a vertex i to vertex j, then the corresponding element of A, a i,j = 1,
otherwise ai,j= 0.
Note, even if the graph on 100 vertices contains only 1 edge, we still have to have a
100x100 matrix with lots of zeroes.
o If there is any weighted graph then instead of 1s and 0s, we can store the
weight of the edge.
Example
Consider the following undirected graph representation: Undirected
graph representation
Directed graph represenation
Cons: It takes a lot of space and time to visit all the neighbors of a vertex,
we have to traverse all the vertices in the graph, which takes quite some
time.
Adjacency List
o Adjacency list is a linked representation.
o In this representation, for each vertex in the graph, we maintain the list of its
neighbors. It means, every vertex of the graph contains list of its adjacent
vertices.
o We have an array of vertices which is indexed by the vertex number and for
each vertex v, the corresponding array element points to a singly linked
list of neighbors of v.
Example
Let's see the following directed graph representation implemented using
linked list:
We can also implement this representation using array as follows:
Pros:
Cons:
o The adjacency list allows testing whether two vertices are adjacent to
each other but it is slower to support this operation.
Graph Traversal - BFS
Graph traversal is a technique used for searching a vertex in a graph. 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...
BFS traversal of a graph produces a spanning tree as final result. Spanning Tree is a graph
without loops. We use Queue data structure with maximum size of total number of vertices in the
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
Step 4 - When there is no new vertex to be visited from the vertex which is at front of the
Example
DFS (Depth First Search) algorithm
In this article, we will discuss the 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.
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.
Algorithm
Step 1: SET STATUS = 1 (ready state) for each node in G
Step 2: Push the starting node A on the stack and set its STATUS = 2
(waiting state)
Step 4: Pop the top node N. Process it and set its STATUS = 3 (processed
state)
Step 5: Push on the stack all the neighbors of N that are in the ready state
(whose STATUS = 1) and set their STATUS = 2 (waiting state)
[END OF LOOP]
Step 6: EXIT
1. STACK: H
Step 2 - POP the top element from the stack, i.e., H, and print it. Now, PUSH
all the neighbors of H onto the stack that are in ready state.
1. Print: H]STACK: A
Step 3 - POP the top element from the stack, i.e., A, and print it. Now, PUSH
all the neighbors of A onto the stack that are in ready state.
1. Print: A
2. STACK: B, D
Step 4 - POP the top element from the stack, i.e., D, and print it. Now, PUSH
all the neighbors of D onto the stack that are in ready state.
1. Print: D
2. STACK: B, F
Step 5 - POP the top element from the stack, i.e., F, and print it. Now, PUSH
all the neighbors of F onto the stack that are in ready state.
1. Print: F
2. STACK: B
Step 6 - POP the top element from the stack, i.e., B, and print it. Now, PUSH
all the neighbors of B onto the stack that are in ready state.
1. Print: B
2. STACK: C
Step 7 - POP the top element from the stack, i.e., C, and print it. Now, PUSH
all the neighbors of C onto the stack that are in ready state.
1. Print: C
2. STACK: E, G
Step 8 - POP the top element from the stack, i.e., G and PUSH all the
neighbors of G onto the stack that are in ready state.
1. Print: G
2. STACK: E
Step 9 - POP the top element from the stack, i.e., E and PUSH all the
neighbors of E onto the stack that are in ready state.
1. Print: E
2. STACK:
Now, all the graph nodes have been traversed, and the stack is empty.