Unit-1 Introduction To Algorithms-: Precise Finite Input Output Effective
Unit-1 Introduction To Algorithms-: Precise Finite Input Output Effective
Introduction to Algorithms-
"Algorithms" refers to a set of rules or steps followed in problem-solving operations, particularly
by a computer. They are fundamental to the field of computer science, as they define the solution
to a problem in terms of the steps needed to solve it. Here's a breakdown of key aspects of
algorithms:
Definition: An algorithm is a finite sequence of well-defined, computer-implementable
instructions, typically to solve a class of problems or to perform a computation.
Characteristics:
Precise: Each step is clearly defined.
Finite: It must terminate after a finite number of steps.
Input: It may have zero or more inputs.
Output: It should produce at least one output.
Effective: Each step must be basic enough to be carried out, in principle, by a person using only
pencil and paper.
Types of Algorithms:
Sorting Algorithms: Such as Quick Sort, Merge Sort, and Bubble Sort.
Search Algorithms: Such as Linear Search and Binary Search.
Graph Algorithms: Such as Dijkstra's algorithm for shortest paths, and Depth-First Search
(DFS).
Dynamic Programming Algorithms: Solving complex problems by breaking them into simpler
subproblems.
Applications:
Used in software development for problem-solving.
Essential in data analysis and processing.
Basis for machine learning and artificial intelligence models.
Analysis of Algorithms:
Focuses on time complexity (how the time to complete an algorithm increases with the size of
the input) and space complexity (the amount of memory space required).
Notation such as Big O (O(n)) is commonly used to describe the upper limit of time or space
requirements.
Examples: Let's explore a detailed example of an algorithm: the Binary Search Algorithm. This
algorithm is an efficient method for finding an item from a sorted list of items. It works by
repeatedly dividing in half the portion of the list that could contain the item, until you've
narrowed down the possible locations to just one.
Algorithm Description - Binary Search:
Purpose: To find the position of a target value within a sorted array.
Approach:
Compare the target value to the middle element of the array.
If they are not equal, the half in which the target cannot lie is eliminated, and the search
continues on the remaining half.
Repeat this until the target value is found or the remaining array is empty.
Steps:
Begin with two variables representing the start and end of the array (initially, the first and last
elements).
Find the middle element of the array.
If the middle element is equal to the target, return the index of the middle element.
If the target is less than the middle element, repeat the search with the end set to the middle
element - 1.
If the target is greater than the middle element, repeat the search with the start set to the middle
element + 1.
If the start index exceeds the end index, the target is not in the array.
Complexity:
Time Complexity: O(log n) - because the algorithm divides the search interval in half each time.
Space Complexity: O(1) - it uses a constant amount of space.
Example:
Consider an array [3, 4, 5, 6, 7, 8, 9] and the target value 6.
The binary search will start by comparing 6 with the middle element (6), and find a match
immediately.
Binary search is a classic example of a divide and conquer algorithm and illustrates how
algorithms can be much more efficient than a simple linear search, especially for large datasets.
In summary, algorithms are the backbone of computer programming and data processing. They
allow us to solve problems efficiently and are integral in the development of software and
technology.
Attributes:-
Algorithms are fundamental to computing and problem-solving. Understanding their attributes
helps in evaluating and choosing the right algorithm for a particular problem. Here are the key
attributes of algorithms:
Correctness: An algorithm should produce the correct output for all possible valid inputs. It
involves two aspects:
Partial Correctness: The algorithm gives the correct output when it terminates.
Termination: The algorithm eventually terminates or stops.
Efficiency: This refers to how well an algorithm performs in terms of time and space:
Time Complexity: The amount of time an algorithm takes to complete in relation to its input size.
It's usually expressed using Big O notation (e.g., O(n), O(log n)).
Space Complexity: The amount of memory space required by an algorithm as a function of its
input size.
Determinism: Each step of the algorithm must be precisely defined. Given the same input, an
algorithm should always produce the same output.
Finiteness: An algorithm must always terminate after a finite number of steps.
Input and Output: An algorithm should have 0 or more well-defined inputs and 1 or more well-
defined outputs. Inputs are the data to be transformed, and outputs are the data that has been
processed.
Generality: The ability of an algorithm to apply to a set of different problems rather than a single
specific instance. A good algorithm should be general enough to solve a category of problems.
Scalability: The ability of an algorithm to maintain its efficiency even when the size of the
problem scales up.
Optimality: An optimal algorithm is the most efficient algorithm in terms of time and space for a
particular problem. However, achieving optimality is not always possible.
Stability (in sorting algorithms): Stability in sorting algorithms means that two objects with equal
keys appear in the same order in sorted output as they appear in the input unsorted array. Some
sorting algorithms are stable by nature, like Bubble Sort, while others are not, like Quick Sort.
Simplicity and Understandability: The ease with which an algorithm can be understood and
implemented. Simpler algorithms are generally preferred as they are easier to analyze and debug.
These attributes are critical for algorithm analysis and selection, and they are taken into
consideration during algorithm design and implementation in various fields of computing and
data processing.
Design Techniques:
Design techniques in the context of algorithms refer to the methodologies and strategies used to
craft algorithms that efficiently solve computational problems. Understanding these techniques is
crucial for developing algorithms that are not only correct but also optimized for performance
and resource utilization. Here are some key algorithm design techniques:
Divide and Conquer:
Principle: Break a problem into smaller sub-problems, solve each sub-problem independently,
and then combine their solutions to solve the original problem.
Applications: Quicksort, Mergesort, Binary Search, Strassen’s Matrix Multiplication.
Dynamic Programming:
Principle: Break the problem into simpler sub-problems in a recursive manner and store the
results of sub-problems to avoid computing the same results again.
Applications: Fibonacci sequence computation, Knapsack problem, Shortest path problems like
Bellman-Ford.
Greedy Method:
Principle: Always choose the best immediate or local solution while finding an answer. It doesn't
reconsider choices.
Applications: Kruskal’s and Prim’s algorithms for Minimum Spanning Tree, Dijkstra’s algorithm
for shortest paths.
Backtracking:
Principle: It involves recursive, systematic trial and error. If a solution doesn't work, it backtracks
to find another possible path.
Applications: The N-Queens problem, solving puzzles like Sudokus, and combinatorial
problems.
Branch and Bound:
Principle: It's similar to backtracking but with an optimization strategy (bounding function) to
speed up the search process.
Applications: Traveling Salesperson Problem (TSP), Knapsack problem.
Randomized Algorithms:
Principle: Use randomness as part of the logic to solve problems where deterministic strategies
are not viable.
Applications: Randomized Quicksort, Monte Carlo algorithms, randomized data structures like
hash tables.
Iterative Improvement:
Principle: Start with a feasible solution and iteratively improve it. Each step attempts to improve
the current solution.
Applications: Algorithms in optimization and network flows, like the Simplex method in linear
programming.
Recursive Algorithms:
Principle: The method where the solution to a problem depends on solutions to smaller instances
of the same problem.
Applications: Tree traversals, DFS in graphs, Merge Sort, Quick Sort.
Heuristic Algorithms:
Principle: Practical approach to find a satisfactory solution where finding an optimal solution is
impractical.
Applications: Used in complex problems like scheduling, network design.
Each of these techniques has its strengths and is suitable for specific types of problems. The
choice of technique largely depends on the problem's nature, the required efficiency, and
resource constraints.
Data Structures: -
Data structures are a way of organizing and storing data in a computer so that it can be accessed
and modified efficiently. They are crucial in the field of computer science and software
engineering, as they provide the foundation for efficient algorithm implementation. There are
various types of data structures, each suited to different kinds of applications. Here's an overview
of some common data structures:
Arrays:
Description: A collection of elements, each identified by an array index or key. It's the simplest
and most widely used data structure.
Usage: Used for storing data in contiguous memory locations. Efficient for accessing elements at
a known index.
Linked Lists:
Description: A sequential collection of elements, but unlike arrays, the elements are linked using
pointers.
Types: Singly linked lists, doubly linked lists, and circular linked lists.
Usage: Useful for dynamic size and ease of insertion/deletion without reallocation or
reorganization of the entire structure.
Stacks:
Description: An abstract data type that serves as a collection of elements, with two principal
operations: push (adds an element) and pop (removes the most recently added element).
Usage: Used in scenarios where data needs to be stored and retrieved in a Last In First Out
(LIFO) manner.
Queues:
Description: Similar to stacks, but the removal happens in a First In First Out (FIFO) order.
Types: Simple queue, circular queue, priority queue, double-ended queue (deque).
Usage: Common in scenarios where data needs to be processed in the order it arrives.
Trees:
Description: A hierarchical data structure consisting of nodes, with a single node designated as
the root, and all other nodes connected by edges.
Types: Binary tree, binary search tree, AVL tree, red-black tree, segment tree, B-tree, etc.
Usage: Used in databases for efficient storage and retrieval, in file systems, and for various
hierarchical relationships.
Graphs:
Description: A collection of nodes (or vertices) and edges connecting some or all of them.
Types: Directed and undirected graphs, weighted and unweighted graphs.
Usage: Widely used in network systems, for representing connections and pathways, and in
solving complex computational problems.
Hash Tables:
Description: Implements an associative array, a structure that can map keys to values. A hash
function is used to compute an index into an array of buckets or slots, from which the desired
value can be found.
Usage: Effective for implementing databases, caches, sets, and more, where quick lookup of data
is essential.
Heaps:
Description: A specialized tree-based data structure that satisfies the heap property: if P is a
parent node of C, then the key of P is either greater than or equal to (in a max heap) or less than
or equal to (in a min heap) the key of C.
Usage: Used in priority queues, for efficient sorting algorithms, and in graph algorithms like
Dijkstra's shortest path.
Each data structure has its own strengths and weaknesses and is chosen based on the
requirements of the algorithm or the application. Understanding these structures is essential for
efficient programming and algorithm design.
Arrays: -
Arrays are one of the most fundamental and commonly used data structures in computer
programming. They are used to store a collection of elements (items), typically of the same data
type, in a contiguous block of memory.
We can directly access an array element by using its index value.
Basic terminologies of array
Array Index: In an array, elements are identified by their indexes. Array index
starts from 0.
Array element: Elements are items stored in an array and can be accessed by their
index.
Array Length: The length of an array is determined by the number of elements it
can contain.
Representation of Array
The representation of an array can be defined by its declaration. A declaration means allocating
memory for an array of a given size.
Arrays can be declared in various ways in different languages. For better illustration, below are
some language-specific array declarations.
int arr[5]; // This array will store integer type element
char arr[10]; // This array will store char type element
float arr[20]; // This array will store float type element
However, the above declaration is static or compile-time memory allocation, which means
that the array element’s memory is allocated when a program is compiled. Here only a fixed
size (i,e. the size that is mentioned in square brackets []) of memory will be allocated for
storage, but don’t you think it will not be the same situation as we know the size of the array
every time, there might be a case where we don’t know the size of the array. If we declare a
larger size and store a lesser number of elements will result in a wastage of memory or either
be a case where we declare a lesser size then we won’t get enough memory to store the rest of
the elements. In such cases, static memory allocation is not preferred.
Why Array Data Structures is needed?
Assume there is a class of five students and if we have to keep records of their marks in
examination then, we can do this by declaring five variables individual and keeping track of
records but what if the number of students becomes very large, it would be challenging to
manipulate and maintain the data.
What it means is that, we can use normal variables (v1, v2, v3, ..) when we have a small
number of objects. But if we want to store a large number of instances, it becomes difficult to
manage them with normal variables. The idea of an array is to represent many instances in
one variable..
Types of arrays:
There are majorly two types of arrays:
One-dimensional array (1-D arrays): You can imagine a 1d array as a row, where
elements are stored one after another.
1D array
3D array
Memory Representation: -
The memory representation of data structures, particularly arrays, is a critical aspect in
understanding how they work and how they are accessed in computer memory. Let's delve into
how arrays are represented in memory:
Linked Lists: Each element (node) contains the data and a pointer to the next node.
Nodes are not stored in contiguous memory locations.
Stacks and Queues: Can be implemented using arrays (contiguous memory) or linked
lists (non-contiguous memory).
Trees and Graphs: Usually represented in memory using pointers (like linked lists) or
arrays (especially for specific types like binary trees).
Efficiency: Knowing how data structures are represented in memory helps in writing
efficient code, especially in terms of memory usage and access time.
Memory Management: It's crucial for understanding how much memory a data structure
will occupy and how it will grow or shrink.
Debugging: Helps in debugging issues related to memory, such as memory leaks or
buffer overflows.
1-D array
2-D array
To find the address of any element in a 2-Dimensional array there are the following two ways-
Row Major Order
Column Major Order
1. Row Major Order:
Row major ordering assigns successive elements, moving across the rows and then down the
next row, to successive memory locations. In simple language, the elements of an array are
stored in a Row-Wise fashion.
To find the address of the element using row-major order uses the following formula:
Address of A[I][J] = B + W * ((I – LR) * N + (J – LC))
I = Row Subset of an element whose address to be found,
J = Column Subset of an element whose address to be found,
B = Base address,
W = Storage size of one element store in an array(in byte),
LR = Lower Limit of row/start row index of the matrix(If not given assume it as zero),
LC = Lower Limit of column/start column index of the matrix(If not given assume it as zero),
N = Number of column given in the matrix.
Example: Given an array, arr[1………10][1………15] with base value 100 and the size of
each element is 1 Byte in memory. Find the address of arr[8][6] with the help of row-major
order.
Solution:
Given:
Base address B = 100
Storage size of one element store in any array W = 1 Bytes
Row Subset of an element whose address to be found I = 8
Column Subset of an element whose address to be found J = 6
Lower Limit of row/start row index of matrix LR = 1
Lower Limit of column/start column index of matrix = 1
Number of column given in the matrix N = Upper Bound – Lower Bound + 1
= 15 – 1 + 1
= 15
Formula:
Address of A[I][J] = B + W * ((I – LR) * N + (J – LC))
Solution:
Address of A[8][6] = 100 + 1 * ((8 – 1) * 15 + (6 – 1))
= 100 + 1 * ((7) * 15 + (5))
= 100 + 1 * (110)
Address of A[I][J] = 210
2. Column Major Order:
If elements of an array are stored in a column-major fashion means moving across the column
and then to the next column then it’s in column-major order. To find the address of the element
using column-major order use the following formula:
Address of A[I][J] = B + W * ((J – LC) * M + (I – LR))
I = Row Subset of an element whose address to be found,
J = Column Subset of an element whose address to be found,
B = Base address,
W = Storage size of one element store in any array(in byte),
LR = Lower Limit of row/start row index of matrix(If not given assume it as zero),
LC = Lower Limit of column/start column index of matrix(If not given assume it as zero),
M = Number of rows given in the matrix.
Example: Given an array arr[1………10][1………15] with a base value of 100 and the size
of each element is 1 Byte in memory find the address of arr[8][6] with the help of column-
major order.
Solution:
Given:
Base address B = 100
Storage size of one element store in any array W = 1 Bytes
Row Subset of an element whose address to be found I = 8
Column Subset of an element whose address to be found J = 6
Lower Limit of row/start row index of matrix LR = 1
Lower Limit of column/start column index of matrix = 1
Number of Rows given in the matrix M = Upper Bound – Lower Bound + 1
= 10 – 1 + 1
= 10
Formula: used
Address of A[I][J] = B + W * ((J – LC) * M + (I – LR))
Address of A[8][6] = 100 + 1 * ((6 – 1) * 10 + (8 – 1))
= 100 + 1 * ((5) * 10 + (7))
= 100 + 1 * (57)
Address of A[I][J] = 157
From the above examples, it can be observed that for the same position two different address
locations are obtained that’s because in row-major order movement is done across the rows and
then down to the next row, and in column-major order, first move down to the first column and
then next column. So both the answers are right.
So it’s all based on the position of the element whose address is to be found for some cases the
same answers is also obtained with row-major order and column-major order and for some
cases, different answers are obtained.
Calculate the address of any element in the 3-D Array:
A 3-Dimensional array is a collection of 2-Dimensional arrays. It is specified by using three
subscripts:
Block size
Row size
Column size
More dimensions in an array mean more data can be stored in that array.
Example:
3-D array
To find the address of any element in 3-Dimensional arrays there are the following two ways-
1. Row Major Order
1. Column Major Order
1. Row Major Order:
To find the address of the element using row-major order, use the following formula:
Address of A[i][j][k] = B + W *(M * N * (i-x) + N *(j-y) + (k-z))
Here:
B = Base Address (start address)
W = Weight (storage size of one element stored in the array)
M = Row (total number of rows)
N = Column (total number of columns)
P = Width (total number of cells depth-wise)
x = Lower Bound of Row
y = Lower Bound of Column
z = Lower Bound of Width
Example: Given an array, arr[1:9, -4:1, 5:10] with a base value of 400 and the size of each
element is 2 Bytes in memory find the address of element arr[5][-1][8] with the help of row-
major order?
Solution:
Given:
Block Subset of an element whose address to be found I = 5
Row Subset of an element whose address to be found J = -1
Column Subset of an element whose address to be found K = 8
Base address B = 400
Storage size of one element store in any array(in Byte) W = 2
Lower Limit of blocks in matrix x = 1
Lower Limit of row/start row index of matrix y = -4
Lower Limit of column/start column index of matrix z = 5
M(row) = Upper Bound – Lower Bound + 1 = 1 – (-4) + 1 = 6
N(Column)= Upper Bound – Lower Bound + 1 = 10 – 5 + 1 = 6
Formula used:
Address of[I][J][K] =B + W (M * N(i-x) + N *(j-y) + (k-z))
Solution:
Address of arr[5][-1][8] = 400 + 2 * {[6 * 6 * (5 – 1)] + 6 * [(-1 + 4)]} + [8 – 5]
= 400 + 2 * (6*6*4)+(6*3)+3
= 400 + 2 * (165)
= 730
2. Column Major Order:
To find the address of the element using column-major order, use the following formula:1
Address of A[i][j][k]= B + W(M * N(i – x) + M *(k – z) + (j – y))
Here:
B = Base Address (start address)
W = Weight (storage size of one element stored in the array)
M = Row (total number of rows)
N = Column (total number of columns)
P = Width (total number of cells depth-wise)
x = Lower Bound of block (first subscipt)
y = Lower Bound of Row
z = Lower Bound of Column
Example: Given an array arr[1:8, -5:5, -10:5] with a base value of 400 and the size of each
element is 4 Bytes in memory find the address of element arr[3][3][3] with the help of
column-major order?
Solution:
Given:
Row Subset of an element whose address to be found I = 3
Column Subset of an element whose address to be found J = 3
Block Subset of an element whose address to be found K = 3
Base address B = 400
Storage size of one element store in any array(in Byte) W = 4
Lower Limit of blocks in matrix x = 1
Lower Limit of row/start row index of matrix y = -5
Lower Limit of column/start column index of matrix z = -10
M (row)= Upper Bound – Lower Bound + 1 = 5 +5 + 1 = 11
N (column)= Upper Bound – Lower Bound + 1 = 5 + 10 + 1 = 16
Formula used:
Address of[i][j][k] = B + W(M * N(i – x) + M * (k-z) + (j-y))
Solution:
Address of arr[3][3][3] = 400 + 4 * ((11*16*(3-1)+11*(3-(-10)+(3-(-5)))
= 400 + 4 * ((176*2 + 11*13 + 8)
= 400 + 4 * (503)
= 400 + 2012
= 2412
Sparse Matrices-
A matrix is a two-dimensional data object made of m rows and n columns, therefore having
total m x n values. If most of the elements of the matrix have 0 value, then it is called a sparse
matrix.
Why to use Sparse Matrix instead of simple matrix ?
Storage: There are lesser non-zero elements than zeros and thus lesser memory can
be used to store only those elements.
Computing time: Computing time can be saved by logically designing a data
structure traversing only non-zero elements..
Example:
00304
00570
00000
02600
Representing a sparse matrix by a 2D array leads to wastage of lots of memory as zeroes in the
matrix are of no use in most of the cases. So, instead of storing zeroes with non-zero elements,
we only store non-zero elements. This means storing non-zero elements with triples- (Row,
Column, value).
Sparse Matrix Representations can be done in many ways following are two common
representations:
Array representation
Linked list representation
Method 1: Using Arrays:
2D array is used to represent a sparse matrix in which there are three rows named as
Row: Index of row, where non-zero element is located
Column: Index of column, where non-zero element is located
1. Value: Value of the non-zero element located at index – (row, column)
Implementation:
// C++ program for Sparse Matrix Representation
// using Array
#include<stdio.h>
int main()
{
// Assume 4x5 sparse matrix
int sparseMatrix[4][5] =
{
{0 , 0 , 3 , 0 , 4 },
{0 , 0 , 5 , 7 , 0 },
{0 , 0 , 0 , 0 , 0 },
{0 , 2 , 6 , 0 , 0 }
};
int size = 0;
for (int i = 0; i < 4; i++)
for (int j = 0; j < 5; j++)
if (sparseMatrix[i][j] != 0)
size++;
printf("\n");
}
return 0;
}
Output
001133
242312
345726
Time Complexity: O(NM), where N is the number of rows in the sparse matrix, and M is the
number of columns in the sparse matrix.
Auxiliary Space: O(NM), where N is the number of rows in the sparse matrix, and M is the
number of columns in the sparse matrix.
Method 2: Using Linked Lists
In linked list, each node has four fields. These four fields are defined as:
1. Row: Index of row, where non-zero element is located
Column: Index of column, where non-zero element is located
Value: Value of the non zero element located at index – (row,column)
Next node: Address of the next node
}
else
{
while (temp->next != NULL)
temp = temp->next;
}
}
printf("row_position: ");
while(temp != NULL)
{
printf("column_postion: ");
while(r != NULL)
{
printf("%d ", r->column_postion);
r = r->next;
}
printf("\n");
printf("Value: ");
while(s != NULL)
{
printf("%d ", s->value);
s = s->next;
}
printf("\n");
}
PrintList(start);
return 0;
}
Output
row_position:0 0 1 1 3 3
column_position:2 4 2 3 1 2
Value:3 4 5 7 2 6
C
C++
Java
Python3
C#
PHP
Javascript
#include <stdio.h>
// Driver code
int main(void)
{
int arr[] = { 2, 3, 4, 10, 40 };
int x = 10;
int N = sizeof(arr) / sizeof(arr[0]);
// Function call
int result = search(arr, N, x);
(result == -1)
? printf("Element is not present in array")
: printf("Element is present at index %d", result);
return 0;
}
Output
Element is present at index 3
Complexity Analysis of Linear Search:
Time Complexity:
Best Case: In the best case, the key might be present at the first index. So the best
case complexity is O(1)
Worst Case: In the worst case, the key might be present at the last index i.e.,
opposite to the end from which the search has started in the list. So the worst-case
complexity is O(N) where N is the size of the list.
Average Case: O(N)
Auxiliary Space: O(1) as except for the variable to iterate through the list, no other variable is
used.
Advantages of Linear Search:
Linear search can be used irrespective of whether the array is sorted or not. It can be
used on arrays of any data type.
Does not require any additional memory.
It is a well-suited algorithm for small datasets.
Drawbacks of Linear Search:
Linear search has a time complexity of O(N), which in turn makes it slow for large
datasets.
Not suitable for large arrays.
When to use Linear Search?
When we are dealing with a small dataset.
When you are searching for a dataset stored in contiguous memory.
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.
How to Implement Binary Search?
The Binary Search Algorithm can be implemented in the following two ways
Iterative Binary Search Algorithm
Recursive Binary Search Algorithm
Given below are the pseudocodes for the approaches.
1. Iterative Binary Search Algorithm:
Here we use a while loop to continue the process of comparing the key and splitting the search
space in two halves.
Implementation of Iterative Binary Search Algorithm:
// Driver code
int main(void)
{
int arr[] = { 2, 3, 4, 10, 40 };
int n = sizeof(arr) / sizeof(arr[0]);
int x = 10;
int result = binarySearch(arr, 0, n - 1, x);
(result == -1) ? printf("Element is not present"
" in array")
: printf("Element is present at "
"index %d",
result);
return 0;
}
Selection Sort: -
Selection sort is a simple and efficient sorting algorithm that works by repeatedly selecting the
smallest (or largest) element from the unsorted portion of the list and moving it to the sorted
portion of the list.
The algorithm repeatedly selects the smallest (or largest) element from the unsorted portion of
the list and swaps it with the first element of the unsorted part. This process is repeated for the
remaining unsorted portion until the entire list is sorted.
Output
Sorted array:
11 12 22 25 64
Complexity Analysis of Selection Sort
Time Complexity: The time complexity of Selection Sort is O(N2) as there are two nested
loops:
One loop to select an element of Array one by one = O(N)
Another loop to compare that element with every other Array element = O(N)
Therefore overall complexity = O(N) * O(N) = O(N*N) = O(N2)
Auxiliary Space: O(1) as the only extra memory used is for temporary variables while
swapping two values in Array. The selection sort never makes more than O(N) swaps and can
be useful when memory writing is costly.
Advantages of Selection Sort Algorithm
Simple and easy to understand.
Works well with small datasets.
Disadvantages of the Selection Sort Algorithm
Selection sort has a time complexity of O(n^2) in the worst and average case.
Does not work well on large datasets.
Does not preserve the relative order of items with equal keys which means it is not
stable.
Bubble Sort: -
Bubble Sort is the simplest sorting algorithm that works by repeatedly swapping the adjacent
elements if they are in the wrong order. This algorithm is not suitable for large data sets as its
average and worst-case time complexity is quite high.
Bubble Sort Algorithm
In Bubble Sort algorithm,
traverse from left and compare adjacent elements and the higher one is placed at
right side.
In this way, the largest element is moved to the rightmost end at first.
This process is then continued to find the second largest and place it and so on until
the data is sorted.
Implementation of Bubble Sort
Below is the implementation of the bubble sort. It can be optimized by stopping the algorithm
if the inner loop didn’t cause any swap.
Output
Sorted array:
11 12 22 25 34 64 90
Complexity Analysis of Bubble Sort:
Time Complexity: O(N2)
Auxiliary Space: O(1)
Advantages of Bubble Sort:
Bubble sort is easy to understand and implement.
It does not require any additional memory space.
It is a stable sorting algorithm, meaning that elements with the same key value
maintain their relative order in the sorted output.
Disadvantages of Bubble Sort:
Bubble sort has a time complexity of O(N2) which makes it very slow for large data
sets.
Bubble sort is a comparison-based sorting algorithm, which means that it requires a
comparison operator to determine the relative order of elements in the input data
set. It can limit the efficiency of the algorithm in certain cases.
Insertion Sort: -
Insertion sort is a simple sorting algorithm that works similar to the way you sort playing
cards in your hands. The array is virtually split into a sorted and an unsorted part. Values from
the unsorted part are picked and placed at the correct position in the sorted part.
Insertion Sort Algorithm
To sort an array of size N in ascending order iterate over the array and compare the current
element (key) to its predecessor, if the key element is smaller than its predecessor, compare it to
the elements before. Move the greater elements one position up to make space for the swapped
element.
Working of Insertion Sort algorithm
Consider an example: arr[]: {12, 11, 13, 5, 6}
12 11 13 5 6
First Pass:
Initially, the first two elements of the array are compared in insertion sort.
12 11 13 5 6
Here, 12 is greater than 11 hence they are not in the ascending order and 12 is not
at its correct position. Thus, swap 11 and 12.
So, for now 11 is stored in a sorted sub-array.
11 12 13 5 6
Second Pass:
Now, move to the next two elements and compare them
11 12 13 5 6
Here, 13 is greater than 12, thus both elements seems to be in ascending order,
hence, no swapping will occur. 12 also stored in a sorted sub-array along with 11
Third Pass:
Now, two elements are present in the sorted sub-array which are 11 and 12
Moving forward to the next two elements which are 13 and 5
11 12 13 5 6
Both 5 and 13 are not present at their correct place so swap them
11 12 5 13 6
After swapping, elements 12 and 5 are not sorted, thus swap again
11 5 12 13 6
5 11 12 13 6
5 11 12 13 6
Clearly, they are not sorted, thus perform swap between both
5 11 12 6 13
5 11 6 12 13
5 6 11 12 13
return 0;
}
Output
5 6 11 12 13
Time Complexity: O(N^2)
Auxiliary Space: O(1)
Complexity Analysis of Insertion Sort:
Time Complexity of Insertion Sort
The worst-case time complexity of the Insertion sort is O(N^2)
The average case time complexity of the Insertion sort is O(N^2)
The time complexity of the best case is O(N).
Space Complexity of Insertion Sort
The auxiliary space complexity of Insertion Sort is O(1)
Characteristics of Insertion Sort
This algorithm is one of the simplest algorithms with a simple implementation
Basically, Insertion sort is efficient for small data values
Insertion sort is adaptive in nature, i.e. it is appropriate for data sets that are already
partially sorted.
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.
Merge Sort Algorithm
merge(arr, l, m, r);
}
}
// Driver code
int main()
{
int arr[] = { 12, 11, 13, 5, 6, 7 };
int arr_size = sizeof(arr) / sizeof(arr[0]);
Output
Given array is
12 11 13 5 6 7
Sorted array is
5 6 7 11 12 13
1. Sequential Search: The Sequential Search is the basic and simple Searching
Algorithm. Sequential Search starts at the beginning of the list or array. It traversed
the list or array sequentially and checks for every element of the list or array. The
Linear Search is an example of the Sequential Search.
A Linear Search checks one by one each element of the array, without jumping to
any item. It searches the element in the array until a match is found. If the match is
found then it returns the index of the item otherwise it returns the -1. The worst-case
complexity of the Linear Search Algorithm is O(N), where N is the total number of
elements in the list.
How Linear Search works:
Let’s understand with an example of how linear search works. Suppose, in this
example, the task is to search an element x in the array. For searching the given
element, start from the leftmost element of the array and one by one compare x with
each element of the array. If the x matches with the an element it returns the index
otherwise it returns the -1. Below is the image to illustrate the same:
1. Interval Search: These algorithms are designed to searching for a given element in
sorted data structures. These types of searching algorithms are much more efficient
than a Linear Search Algorithm. The Binary Search is an example of the Interval
Search.
A Binary Search searches the given element in the array by dividing the array into
two halves. First, it finds the middle element of the array and then compares the
given element with the middle element of thearray, if the element to be searched is
less than the item in the middle of the array then the given element can only lie in
the left subarray otherwise it lies in the right subarray. It repeatedly checks until the
element is found. The worst-case complexity of the Binary Search Algorithm
is O(log N),
How Binary Search works:
Let’s understand with an example of how Binary search works. Suppose, in this
example we have to search a element x in the array. For searching the given
element, first we find the middle element of the array then compare x with the
middle element of the array. If x matches with the middle element we return the mid
index and if x is greater than the mid element then x can be only lie in the right half
subarray after the mid element, so we recur for the right half otherwise recur for the
left half. It repeatedly checks until the element is found. Below is the image to
illustrate the same:
Sorting Algorithm: A Sorting Algorithm is used to arranging the data of list or array into
some specific order. It can be numerical or lexicographically order. For Example: The below
list of characters is sorted in increasing order of their ASCII values. That is, the character with
lesser ASCII value will be placed first than the character with higher ASCII value. The Bubble
Sort, Insertion Sort, Selection Sort, Merge Sort, Quick Sort, Heap Sort, Radix Sort, etc are the
examples of Sorting Algorithms.
There are two different categories in sorting. They are:
Internal Sorting: When all data is placed in memory, then sorting is called internal
sorting.
External Sorting: When all data that needs to be sorted cannot be placed in
memory at a time, the sorting is called External Sorting. External Sorting is used for
massive amount of data. Merge Sort and its variations are typically used for external
sorting. Some external storage like hard-disk, CD, etc is used for external storage.
Difference between Searching and Sorting Algorithm:
S.No. Searching Algorithm Sorting Algorithm
The Linear Search and the Binary The Bubble Sort, Insertion Sort, Selection
5. Search are the examples of Sort, Merge Sort, Quick Sort etc are the
Searching Algorithms. examples of Sorting Algorithms.
#include <bits/stdc++.h>
using namespace std;
int main() {
//initialize a string
string s="geeksforgeeks";
// get count
cout<<"The count of ch is "<<arr[ch-'a']<<endl;
return 0;
}
Output
The count of ch is 4
Complexity Analysis of a Hash Table:
For lookup, insertion, and deletion operations, hash tables have an average-case time
complexity of O(1). Yet, these operations may, in the worst case, require O(n) time, where n is
the number of elements in the table.
Applications of Hash Table:
Hash tables are frequently used for indexing and searching massive volumes of
data. A search engine might use a hash table to store the web pages that it has
indexed.
Data is usually cached in memory via hash tables, enabling rapid access to
frequently used information.
Hash functions are frequently used in cryptography to create digital signatures,
validate data, and guarantee data integrity.
Hash tables can be used for implementing database indexes, enabling fast access to
data based on key values.
Collision Resolution: - the hash function is used to find the index of the array. The hash value
is used to create an index for the key in the hash table. The hash function may return the same
hash value for two or more keys. When two or more keys have the same hash value, a collision
happens. To handle this collision, we use collision resolution techniques.
Collision Resolution Techniques
There are two types of collision resolution techniques.
Separate chaining (open hashing)
Open addressing (closed hashing)
Separate chaining: This method involves making a linked list out of the slot where the
collision happened, then adding the new key to the list. Separate chaining is the term used to
describe how this connected list of slots resembles a chain. It is more frequently utilized when
we are unsure of the number of keys to add or remove.
Time complexity
Its worst-case complexity for searching is o(n).
Its worst-case complexity for deletion is o(n).
Advantages of separate chaining
It is easy to implement.
The hash table never fills full, so we can add more elements to the chain.
It is less sensitive to the function of the hashing.
Disadvantages of separate chaining
In this, the cache performance of chaining is not good.
Memory wastage is too much in this method.
It requires more space for element links.
Open addressing: To prevent collisions in the hashing table open, addressing is employed as a
collision-resolution technique. No key is kept anywhere else besides the hash table. As a result,
the hash table’s size is never equal to or less than the number of keys. Additionally known as
closed hashing.
The following techniques are used in open addressing:
Linear probing
Quadratic probing
Double hashing