Thanks to visit codestin.com
Credit goes to www.scribd.com

0% found this document useful (0 votes)
1 views117 pages

Ds Unit1

The document provides an overview of data structures, their types, and applications, emphasizing their importance in organizing and managing data for efficient access and manipulation. It covers various data structures such as arrays, linked lists, stacks, and queues, along with their operations and implementations in C++. Additionally, it explains object-oriented programming concepts in C++, including classes, objects, methods, and access specifiers.

Uploaded by

pcfad009
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)
1 views117 pages

Ds Unit1

The document provides an overview of data structures, their types, and applications, emphasizing their importance in organizing and managing data for efficient access and manipulation. It covers various data structures such as arrays, linked lists, stacks, and queues, along with their operations and implementations in C++. Additionally, it explains object-oriented programming concepts in C++, including classes, objects, methods, and access specifiers.

Uploaded by

pcfad009
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/ 117

Prof. Priti G.

Jadhav

Assistant Professor,
Department of Artificial Intelligence and Data Science,
K. K. Wagh Institute of Engineering Education and Research, Nashik
A data structure is a storage that is used to store and organize data. It is a way of arranging
data on a computer so that it can be accessed and updated efficiently.
A data structure organizes, processes, retrieves, and stores data, making it essential for
nearly every program or software system. To help you master them, we've compiled a
comprehensive guide covering types, classifications, and applications of data structures.
Data is the raw material that data structures store, organize, and manage, and
upon which algorithms operate to perform computations and solve problems.

Data in Data Structures:

•Representation:
•Data within a data structure can be numbers, text, images, objects, or any other
type of information that a program needs to process.
•Organization:
•Data structures provide specific ways to arrange and store this data. For
example, an array stores data in contiguous memory locations, while a linked list
links data elements together in a chain.
•Access and Manipulation:
•Data structures allow for efficient access, insertion, deletion, and modification of
the stored data based on the structure's characteristics.
Data in Algorithms:

•Input:
•Algorithms take data as input, which can be in various forms depending on
the algorithm's purpose. For example, a sorting algorithm might take an
unsorted list of numbers as input.
•Processing:
•Algorithms process the input data according to their predefined steps or
instructions. This processing can involve calculations, comparisons,
transformations, or other operations.
•Output:
•Algorithms produce output based on the processed data. This output can
be a modified version of the input data, a new set of data, or a result that
indicates the solution to a problem.
Data structures are defined as special classes implemented to hold data only,
i.e. Pure models, e.g. Car, Kid, Animal, Event, Employee, Company,
Customer ...etc. Those data are generally declared or considered as instance
variables in other classes beginnings.
The methods of this class should not perform any real significant work,
otherwise the data structure class is not a data structure anymore
C++ is an object-oriented programming language.

Everything in C++ is associated with classes and objects, along with its
attributes and methods. For example: in real life, a car is an object. The car
has attributes, such as weight and color, and methods, such as drive and
brake.
Attributes and methods are basically variables and functions that belongs to
the class. These are often referred to as "class members".
Create a Class
class MyClass { // The class
public: // Access specifier
int myNum; // Attribute (int variable)
string myString; // Attribute (string variable)
};

•The class keyword is used to create a class called MyClass.


•The public keyword is an access specifier, which specifies that members (attributes
and methods) of the class are accessible from outside the class. You will learn more
about access specifiers later.
•Inside the class, there is an integer variable myNum and a string variable myString.
When variables are declared within a class, they are called attributes.
•At last, end the class definition with a semicolon ;
Create an Object
To create an object of MyClass, specify the class name, followed by the object
name.
To access the class attributes (myNum and myString), use the dot syntax (.) on
the object:
#include <iostream>
#include <string>
using namespace std;

class MyClass { // The class


public: // Access specifier
int myNum; // Attribute (int variable)
string myString; // Attribute (string variable)
};

int main() {
MyClass myObj; // Create an object of MyClass

// Access attributes and set values


myObj.myNum = 15;
myObj.myString = "Some text";

// Print values
cout << myObj.myNum << "\n";
cout << myObj.myString;
return 0; 15
Some text
}
Define a Method Inside the Class
#include <iostream>
using namespace std;

class MyClass { // The class


public: // Access specifier
void myMethod() { // Method/function
cout << "Hello World!";
}
};

int main() {
MyClass myObj; // Create an object of
MyClass
myObj.myMethod(); // Call the method
return 0;
} Hello World!
Access Specifiers
Access specifiers control how the members (attributes and methods) of a class can be accessed.
They help protect data and organize code so that only the right parts can be seen or changed.
#include <iostream>
using namespace std;

class MyClass {
public: // Public access specifier
int x; // Public attribute
private: // Private access specifier
int y; // Private attribute
};
int main() {
MyClass myObj;
myObj.x = 25; // Allowed (x is public)
myObj.y = 50; // Not allowed (y is private) In function 'int main()':
return 0; Line 8: error: 'int MyClass::y' is private
Line 14: error: within this context
}
Create a Function
C++ provides some pre-defined functions, such as main(), which is used to
execute code. But you can also create your own functions to perform certain
actions.

Syntax
void myFunction()
{
// code to be
executed
}

•myFunction() is the name of the function


•void means that the function does not have a return value. You will learn more about return
values later in the next chapter
•inside the function (the body), add code that defines what the function should do
Call a Function
To call a function, write the function's name followed by two parentheses () and a
semicolon ;
In the following example, myFunction() is used to print a text (the action), when it is called:

#include <iostream>
using namespace std;

void myFunction() {
cout << "WELCOME TO KKWAGH";
}

int main() {
myFunction();
return 0;
WELCOME TO KKWAGH
}
C++ Function Parameters

Parameters and Arguments


Information can be passed to functions as a parameter. Parameters act as variables inside the
function.
Parameters are specified after the function name, inside the parentheses. You can add as many
parameters as you want, just separate them with a comma:

Syntax
void functionName(parameter1, parameter2, parameter3)
{
// code to be executed
}
#include <iostream>
#include <string>
using namespace std;

void myFunction(string fname) {


cout << fname << " KKW\n";
}

int main() {
myFunction("ABC");
myFunction("PQR");
myFunction("XYZ");
return 0;
}
ABC KKW
PQR KKW
XYZ KKW
Return Values

#include <iostream>
using namespace std; #include <iostream>
using namespace std;
int myFunction(int x) {
return 5 + x; int myFunction(int x, int y) {
} return x + y;
}
int main() {
cout << myFunction(3); int main() {
return 0; cout << myFunction(5, 3);
} return 0;
}
8
Pass By Reference
#include <iostream>
using namespace std;
void swapNums(int &x, int &y) {
int z = x;
x = y;
y = z;
}
int main() {
int firstNum = 10;
int secondNum = 20;

cout << "Before swap: " << "\n";


cout << firstNum << secondNum << "\n";

swapNums(firstNum, secondNum);

cout << "After swap: " << "\n";


cout << firstNum << secondNum << "\n"; Before swap:
1020
After swap:
return 0;
2010
}
#include <iostream>
using namespace std;
class Adder {
public:
int add(int a, int b); // Member function declaration
};
// Member function definition outside the class
int Adder::add(int a, int b) {
return a + b;
}

int main() {
int num1, num2;

cout << "Enter first number: ";


cin >> num1;

cout << "Enter second number: ";


cin >> num2;

Adder obj; // Create object of the class


int sum = obj.add(num1, num2); // Pass arguments to function

cout << "Sum = " << sum << endl; Enter first number: 7
Enter second number: 13
return 0; Sum = 20
}
•Linear data structure: Data structure in which data elements are arranged sequentially or
linearly, where each element is attached to its previous and next adjacent elements, is called a
linear data structure.
Examples: array, stack, queue, linked list, etc.

• Static data structure: Static data structure has a fixed memory size. It is easier to access
the elements in a static data structure.
Example: array data structure.
• Dynamic data structure: In the dynamic data structure, the size is not fixed. It can be
randomly updated during the runtime which may be considered efficient concerning the
memory (space) complexity of the code.
Examples: stack and queue data structures.

•Non-linear data structure: Data structures where data elements are not placed sequentially or
linearly are called non-linear data structures. In a non-linear data structure, we can't traverse all
the elements in a single run.
Examples: tree and graph data structures
Different applications of an array are as follows:
Arrays efficiently manage and store database records.
•It helps in implementing sorting algorithm.
•It is also used to implement other data structures like Stacks, Queues, Heaps, Hash
tables, etc.
•An array can be used for CPU scheduling.

Linked list Data Structure


A linked list is a linear data structure in which elements are not stored at contiguous
memory locations. The elements in a linked list are linked using pointers as shown in the
below image.
Applications of the Linked list

•Linked lists are used to implement other data structures like stacks, queues, etc.
•It is used for the representation of sparse matrices.
•It is used in the linked allocation of files.
•Linked lists are used to display image containers. Users can visit past, current, and next
images.
•They are used to perform undo operations.

Stack Data Structure


Stack is a linear data structure that follows LIFO(Last in first out) principle i.e.,
entering and retrieving data is possible from only one end. The entering and retrieving
of data is also called push and pop operation in a stack.
Stack Data Structure
Stack is a linear data structure that follows LIFO(Last in first out) principle i.e., entering
and retrieving data is possible from only one end. The entering and retrieving of data is
also called push and pop operation in a stack.
Push Operation in Stack:
Adds an item to the stack. If the stack is full, then it is said to be an Overflow condition.
•Before pushing the element to the stack, we check if the stack is full .
•If the stack is full (top == capacity-1) , then Stack Overflows and we cannot insert the element to the stack.
•Otherwise, we increment the value of top by 1 (top = top + 1) and the new value is inserted at top position .
•The elements can be pushed into the stack till we reach the capacity of the stack.
Pop Operation in Stack:
Removes an item from the stack. The items are popped in the reversed order in which they are pushed. If the stack is
empty, then it is said to be an Underflow condition.
•Before popping the element from the stack, we check if the stack is empty .
•If the stack is empty (top == -1), then Stack Underflows and we cannot remove any element from the stack.
•Otherwise, we store the value at top, decrement the value of top by 1 (top = top – 1) and return the stored top value.
Top or Peek Operation in Stack:
Returns the top element of the stack.

Before returning the top element from the stack, we check if the stack is empty.
If the stack is empty (top == -1), we simply print “Stack is empty”.
Otherwise, we return the element stored at index = top .
isEmpty Operation in Stack:
Returns true if the stack is empty, else false.=

Check for the value of top in stack.


If (top == -1) , then the stack is empty so return true .
Otherwise, the stack is not empty so return false .
isFull Operation in Stack :
Returns true if the stack is full, else false.

Check for the value of top in stack.


If (top == capacity-1), then the stack is full so return true .
Otherwise, the stack is not full so return false.
#include <iostream> switch (choice) {
using namespace std; case 1:
#define MAX 100 cout << "Enter value to
class Stack { push: ";
int arr[MAX]; cin >> value;
int top; s.push(value);
public: break;
Stack() { top = -1; } case 2:
void push(int x) { s.pop();
if (top >= MAX - 1) { break;
cout << "Stack Overflow\n"; case 3:
return; s.display();
int main() {
} break;
Stack s;
arr[++top] = x; case 4:
int choice, value;
cout << x << " pushed into cout << "Exiting
stack\n"; program.\n";
while (true) {
} return 0;
cout << "\nStack Menu:\n";
void pop() { default:
cout << "1. Push\n2. Pop\n3.
if (top < 0) { cout << "Invalid
Display\n4. Exit\n";
cout << "Stack Underflow\n"; choice!\n";
cout << "Enter your choice: ";
return; }
cin >> choice;
} }
cout << arr[top--] << " popped return 0;
from stack\n"; }
}
1. Push
2. Pop
3. Display
4. Exit
Enter your choice: 1
Enter value to push: 10
10 pushed into stack
...
Queue Data Structure
Queue is a linear data structure that follows First In First Out(FIFO) principle i.e. the
data item stored first will be accessed first. In this, entering is done from one end and
retrieving data is done from other end. An example of a queue is any queue of
consumers for a resource where the consumer that came first is served first.
Operation 1: enqueue()
Operation 2: dequeue()
void pop() { int main() {
#include <iostream> if (front == -1 || front > rear) { Queue q;
using namespace std; cout << "Queue int choice, value;
#define MAX 100 while (true) {
Underflow\n";
class Queue { cout << "\nQueue Menu:\n";
return;} cout << "1. Push\n2. Pop\n3. Display\n4. Exit\n";
int arr[MAX]; cout << arr[front++] << " popped cout << "Enter your choice: ";
int front, rear; from queue\n"; cin >> choice;
public: if (front > rear) { switch (choice) {
Queue() { front = rear = -1; // Reset case 1:
front = -1; queue when empty cout << "Enter value to push: ";
rear = -1; } cin >> value;
} q.push(value);
}
break;
void push(int x) { case 2:
if (rear == MAX - 1) { void display() { q.pop();
cout << "Queue if (front == -1 || front > rear) break;
Overflow\n"; { case 3:
q.display();
return; cout << "Queue is
break;
} empty\n"; case 4:
if (front == -1) front = 0; return;} cout << "Exiting program.\n";
cout << "Queue elements: "; return 0;
arr[++rear] = x; for (int i = front; i <= rear; i++) default:
cout << x << "pushed into cout << arr[i] << " "; cout << "Invalid choice!\n";
queue\n"; cout << endl; } }
} }}; return 0;
}
Output

1. Push
2. Pop
3. Display
4. Exit
Enter your choice: 1
Enter value to push: 20
20 pushed into queue
...

Applications of Queue:
Different applications of Queue are as follows:
•Queue is used for handling website traffic.
•It helps to maintain the playlist in media players.
•It helps in serving requests on a single shared resource, like a printer, CPU task
scheduling, etc.
•Queues are used for job scheduling in the operating system.
Tree Data Structure
A tree is a non-linear and hierarchical data structure where the elements are arranged in
a tree-like structure. In a tree, the topmost node is called the root node. Each node
contains some data, and data can be of any type. It consists of a central node, structural
nodes, and sub-nodes which are connected via edges. Different tree data structures
allow quicker and easier access to the data as it is a non-linear data structure.
Applications of Tree:
•Heap is a tree data structure that is implemented using arrays and used to implement
priority queues.
•B-Tree and B+ Tree are used to implement indexing in databases.
•Syntax Tree helps in scanning, parsing, generation of code, and evaluation of arithmetic
expressions in Compiler design.
•Spanning trees are used in routers in computer networks.
•Domain Name Server also uses a tree data structure.
Binary Search Tree Data Structure
A Binary Search Tree (or BST) is a data structure used for organizing and storing data in a
sorted manner. Each node in a Binary Search Tree has at most two children, a left child and
a right child, with the left child containing values less than the parent node and the right child
containing values greater than the parent node. This hierarchical structure allows for
efficient searching, insertion, and deletion operations on the data stored in the tree.
What is an Algorithm
The word Algorithm means "A set of finite rules or instructions to be followed
in calculations or other problem-solving operations" Or "A procedure for
solving a mathematical problem in a finite number of steps that frequently
involves recursive operations".
Use of the Algorithms:

1.Computer Science: Algorithms form the basis of computer programming and


are used to solve problems ranging from simple sorting and searching to complex
tasks such as artificial intelligence and machine learning.
2.Mathematics: Algorithms are used to solve mathematical problems, such as
finding the optimal solution to a system of linear equations or finding the shortest
path in a graph.
3.Operations Research: Algorithms are used to optimize and make decisions in
fields such as transportation, logistics, and resource allocation.
4.Artificial Intelligence: Algorithms are the foundation of artificial intelligence
and machine learning, and are used to develop intelligent systems that can
perform tasks such as image recognition, natural language processing, and
decision-making.
5.Data Science: Algorithms are used to analyze, process, and extract insights
from large amounts of data in fields such as marketing, finance, and healthcare.
What is the need for algorithms?

1.Algorithms are necessary for solving complex problems efficiently and


effectively.

2.They help to automate processes and make them more reliable, faster, and
easier to perform.

3.Algorithms also enable computers to perform tasks that would be difficult or


impossible for humans to do manually.

4.They are used in various fields such as mathematics, computer science,


engineering, finance, and many others to optimize processes, analyze data, make
predictions, and provide solutions to problems.
•Clear and Unambiguous: The algorithm should be unambiguous. Each of its steps should be clear in all aspects and must lead to
only one meaning.
•Well-Defined Inputs: If an algorithm says to take inputs, it should be well-defined inputs. It may or may not take input.
•Well-Defined Outputs: The algorithm must clearly define what output will be yielded and it should be well-defined as well. It
should produce at least 1 output.
•Finite-ness: The algorithm must be finite, i.e. it should terminate after a finite time.
•Feasible: The algorithm must be simple, generic, and practical, such that it can be executed with the available resources. It must
not contain some future technology or anything.
•Language Independent: The Algorithm designed must be language-independent, i.e. it must be just plain instructions that can be
implemented in any language, and yet the output will be the same, as expected.
•Input: An algorithm has zero or more inputs. Each that contains a fundamental operator must accept zero or more inputs.
• Output: An algorithm produces at least one output. Every instruction that contains a fundamental operator must accept zero or
more inputs.
•Definiteness: All instructions in an algorithm must be unambiguous, precise, and easy to interpret. By referring to any of the
instructions in an algorithm one can clearly understand what is to be done. Every fundamental operator in instruction must be
defined without any ambiguity.
•Finiteness: An algorithm must terminate after a finite number of steps in all test cases. Every instruction which contains a
fundamental operator must be terminated within a finite amount of time. Infinite loops or recursive functions without base
conditions do not possess finiteness.
•Effectiveness: An algorithm must be developed by using very basic, simple, and feasible operations so that one can trace it out by
using just paper and pencil.
Properties of Algorithm:
•It should terminate after a finite time.
•It should produce at least one output.
•It should take zero or more input.
•It should be deterministic means giving the same output for the same input case.
•Every step in the algorithm must be effective i.e. every step should do some work.
Advantages of Algorithms:
•It is easy to understand.
•An algorithm is a step-wise representation of a solution to a given problem.
•In an Algorithm the problem is broken down into smaller pieces or steps hence, it is easier for the programmer to
convert it into an actual program.
Disadvantages of Algorithms:
•Writing an algorithm takes a long time so it is time-consuming.
•Understanding complex logic through algorithms can be very difficult.
•Branching and Looping statements are difficult to show in Algorithms(imp).
How to Design an Algorithm?
To write an algorithm, the following things are needed as a pre-requisite:
1.The problem that is to be solved by this algorithm i.e. clear problem definition.
2.The constraints of the problem must be considered while solving the problem.
3.The input to be taken to solve the problem.
4.The output is to be expected when the problem is solved.
5.The solution to this problem is within the given constraints.
Algorithm complexity, particularly
as described by Big O
notation, quantifies the time
complexity (how fast an algorithm
runs) and space complexity (how
much memory it uses) in relation to
the input size, providing an upper
bound on its performance as the
input grows. Time complexity
measures the computational time
required, while space complexity
assesses the memory usage.
Examples of Common Big O Notations:
•O(1) - Constant Time/Space:
•The time or space required remains constant, regardless of the input size.
•O(log n) - Logarithmic Time/Space:
•The time or space grows logarithmically with the input size, meaning it
increases very slowly as the input gets larger (e.g., binary search).
•O(n) - Linear Time/Space:
•The time or space grows linearly with the input size (e.g., traversing an
array).
•O(n^2) - Quadratic Time/Space:
•The time or space grows quadratically with the input size (e.g., nested
loops).
•O(2^n) - Exponential Time/Space:
•The time or space grows exponentially with the input size, often indicating
a less efficient algorithm (e.g., some recursive algorithms without
memoization).
Time Complexity and Space Complexity
To measure performance of algorithms, we typically use time and space
complexity analysis. The idea is to measure order of growths in terms of
input size.

•Independent of the machine and its configuration, on which the algorithm is


running on.
•Shows a direct correlation with the number of inputs.
•Can distinguish two algorithms clearly without ambiguity.
Time Complexity: The time complexity of an algorithm quantifies the
amount of time taken by an algorithm to run as a function of the length of the
input. Note that the time to run is a function of the length of the input and not
the actual execution time of the machine on which the algorithm is running
on.
The valid algorithm takes a finite amount of time for execution. The time
required by the algorithm to solve given problem is called time complexity of
the algorithm. Time complexity is very useful measure in algorithm analysis.
It is the time needed for the completion of an algorithm. To estimate the time
complexity, we need to consider the cost of each fundamental instruction and
the number of times the instruction is executed.
Assuming that each of the operations in
the computer takes approximately
int a[n]; constant time, let it be c. The number of
for(int i = 0;i < n;i++) lines of code executed actually depends
cin >> a[i] on the value of Z. During analyses of the
algorithm, mostly the worst-case
for(int i = 0;i < n;i++) scenario is considered, i.e., when there is
for(int j = 0;j < n;j++) no pair of elements with sum equals Z.
if(i!=j && a[i]+a[j] == z) In the worst case,
return true •N*c operations are required for input.
return false •The outer loop i loop runs N times.
•For each i, the inner loop j loop
runs N times.
Space Complexity:
Definition -
Problem-solving using computer requires memory to hold temporary data or final
result while the program is in execution. The amount of memory required by the
algorithm to solve given problem is called space complexity of the algorithm.

It is the amount of memory needed for the completion of an algorithm.


To estimate the memory requirement we need to focus on two parts:
(1) A fixed part: It is independent of the input size. It includes memory for
instructions (code), constants, variables, etc.
(2) A variable part: It is dependent on the input size. It includes memory for
recursion stack, referenced variables, etc.
Example : Addition of two scalar variables
Algorithm ADD SCALAR(A, B)
//Description: Perform arithmetic addition of two numbers
//Input: Two scalar variables A and B
//Output: variable C, which holds the addition of A and B
C <— A+B
return C

The addition of two scalar numbers requires one extra memory location to hold
the result. Thus the space complexity of this algorithm is constant,
hence S(n) = O(1).
int freq[n];
int a[n];
for(int i = 0; i<n;
i++)
{
cin>>a[i];
freq[a[i]]++;
}

Output
51
20 4
10 3
// C++ program for the above approach // Driver Code
#include <bits/stdc++.h> int main()
using namespace std; {
// Given array
// Function to count frequencies of array int arr[] = { 10, 20, 20, 10, 10, 20, 5, 20 };
items int n = sizeof(arr) / sizeof(arr[0]);
void countFreq(int arr[], int n)
{ // Function Call
unordered_map<int, int> freq; countFreq(arr, n);
return 0;
// Traverse through array elements and }
// count frequencies
for (int i = 0; i < n; i++)
freq[arr[i]]++; Here two arrays of length N, and variable i are
// Traverse through map and print used in the algorithm so, the total space used
frequencies is N * c + N * c + 1 * c = 2N * c + c, where c is
for (auto x : freq)
cout << x.first << " " << x.second << a unit space taken. For many inputs,
endl;
}
constant c is insignificant, and it can be said
that the space complexity is O(N).
Big O notation
is a powerful tool used in computer science to describe the time complexity or
space complexity of algorithms. Big-O is a way to express the upper bound of
an algorithm’s time or space complexity.

•Describes the asymptotic behavior (order of growth of time or space in terms of


input size) of a function, not its exact value.
•Can be used to compare the efficiency of different algorithms or data structures.
•It provides an upper limit on the time taken by an algorithm in terms of the
size of the input. We mainly consider the worst case scenario of the algorithm to
find its time complexity in terms of Big O
•It’s denoted as O(f(n)), where f(n) is a function that represents the number of
operations (steps) that an algorithm performs to solve a problem of size n.
BIg O Definition
Given two functions f(n) and g(n), we say
that f(n) is O(g(n)) if there exist constants c >
0 and n0 >= 0 such that f(n) <= c*g(n) for all n >= n0.

In simpler terms, f(n) is O(g(n)) if f(n) grows no faster


than c*g(n) for all n >= n0 where c and n0 are constants.

Importance of Big O Notation


Big O notation is a mathematical notation used to find an upper bound on time
taken by an algorithm or data structure.
Big O notation is important for several reasons:

•Big O Notation is important because it helps analyze the efficiency of


algorithms.

•It provides a way to describe how the runtime or space requirements of an


algorithm grow as the input size increases.

•Allows programmers to compare different algorithms and choose the most


efficient one for a specific problem.

•Helps in understanding the scalability of algorithms and predicting how they will
perform as the input size grows.

•Enables developers to optimize code and improve overall performance.


A Quick Way to find Big O of an Expression
•Ignore the lower order terms and consider only highest order term.
•Ignore the constant associated with the highest order term.

Example 1: f(n) = 3n2 + 2n + 1000Logn + 5000


After ignoring lower order terms, we get the highest order term as 3n2
After ignoring the constant 3, we get n2
Therefore the Big O value of this expression is O(n2)

Example 2 : f(n) = 3n3 + 2n2 + 5n + 1


Dominant Term: 3n3
Order of Growth: Cubic (n3)
Big O Notation: O(n3)
1. Linear Time Complexity: 2. Logarithmic Time Complexity: Big
Big O(n) Complexity O(log n) Complexity

bool findElement(int arr[], int binarySearch(int arr[], int l, int r, int x)


int n, int key) {
if (r >= l) {
{ int mid = l + (r - l) / 2;
for (int i = 0; i < n; i++) if (arr[mid] == x)
{ return mid;
if (arr[i] == key) { if (arr[mid] > x)
return binarySearch(arr, l, mid - 1,
return true; x);
} return binarySearch(arr, mid + 1, r, x);
} }
return false; return -1;
}
}
3. Quadratic Time 4. Cubic Time Complexity:
Complexity: Big O(n2) Big O(n3) Complexity
Complexity
void multiply(int mat1[][N], int
void bubbleSort(int arr[], int mat2[][N], int res[][N])
n) {
{ for (int i = 0; i < N; i++) {
for (int i = 0; i < n - 1; i++) { for (int j = 0; j < N; j++) {
for (int j = 0; j < n - i - 1; res[i][j] = 0;
j++) { for (int k = 0; k < N; k++)
if (arr[j] > arr[j + 1]) { res[i][j] += mat1[i][k] *
swap(&arr[j], &arr[j mat2[k][j];
+ 1]); }
} }
} }
}
}
Factorial Time Complexity: Big O(n!)
Exponential Time Complexity: Complexity
Big O(2n) Complexity
void permute(int* a, int l, int r)
{
if (l == r) {
void generateSubsets(int arr[], int for (int i = 0; i <= r; i++) {
n) cout << a[i] << " ";
{ }
for (int i = 0; i < (1 << n); i++) { cout << endl;
}
for (int j = 0; j < n; j++) {
else {
if (i & (1 << j)) { for (int i = l; i <= r; i++) {
cout << arr[j] << " "; swap(a[l], a[i]);
} permute(a, l + 1, r);
} swap(a[l], a[i]); // backtrack
cout << endl; }
}
}
}
Common Big O Notations:
•O(1) - Constant Time:
The algorithm's execution time remains the same regardless of the input size (e.g., accessing an element in an
array by index).
•O(log n) - Logarithmic Time:
The execution time increases logarithmically with the input size (e.g., binary search).
•O(n) - Linear Time:
The execution time increases linearly with the input size (e.g., traversing a list once).
•O(n log n) - Linearithmic Time:
The execution time increases at a rate of n multiplied by the logarithm of n (e.g., efficient sorting algorithms
like merge sort).
•O(n^2) - Quadratic Time:
The execution time increases quadratically with the input size (e.g., nested loops iterating through all pairs of
elements in a list).
•O(2^n) - Exponential Time:
The execution time increases exponentially with the input size (e.g., recursive calculation of Fibonacci
sequence without memoization).
•O(n!) - Factorial Time:
The execution time increases factorially with the input size (e.g., solving the traveling salesman problem with
brute force).
Worst, Average and Best Case Analysis of Algorithms
Asymptotic analysis overcomes the problems of the naive way of analyzing
algorithms.

Worst Case Analysis (Mostly used)


•In the worst-case analysis, we calculate the upper bound on the running time of
an algorithm. We must know the case that causes a maximum number of
operations to be executed.
•For Linear Search, the worst case happens when the element to be searched (x)
is not present in the array. When x is not present, the search() function compares
it with all the elements of arr[] one by one.
•This is the most commonly used analysis of algorithms (We will be discussing
below why). Most of the time we consider the case that causes maximum
operations.
2. Best Case Analysis (Very Rarely used)

•In the best-case analysis, we calculate the lower bound on the running
time of an algorithm. We must know the case that causes a minimum
number of operations to be executed.
•For linear search, the best case occurs when x is present at the first
location. The number of operations in the best case is constant (not
dependent on n). So the order of growth of time taken in terms of input
size is constant.
3. Average Case Analysis (Rarely used)

•In average case analysis, we take all possible inputs and calculate
the computing time for all of the inputs. Sum all the calculated
values and divide the sum by the total number of inputs.

•We must know (or predict) the distribution of cases. For the linear
search problem, let us assume that all cases are uniformly
distributed (including the case of x not being present in the array).
So we sum all the cases and divide the sum by (n+1). We take
(n+1) to consider the case when the element is not present.
An array, when considered as an abstract data type (ADT), is a collection of
elements, typically of the same data type, that are accessed using an integer
index, and it is defined by its operations rather than its specific implementation.

Abstract Data Types

An Abstract Data Type (ADT) is a conceptual model that defines a set of


operations and behaviors for a data structure, without specifying how these
operations are implemented or how data is organized in memory. The
definition of ADT only mentions what operations are to be performed but
not how these operations will be implemented. It does not specify how data will
be organized in memory and what algorithms will be used for implementing the
operations. It is called "abstract" because it provides an implementation-
independent view.
Features of ADT
•Abstraction: The user does not need to know the implementation of the data structure only
essentials are provided.
•Better Conceptualization: ADT gives us a better conceptualization of the real world.
•Robust: The program is robust and has the ability to catch errors.
•Encapsulation: ADTs hide the internal details of the data and provide a public interface for
users to interact with the data. This allows for easier maintenance and modification of the data
structure.
•Data Abstraction: ADTs provide a level of abstraction from the implementation details of the
data. Users only need to know the operations that can be performed on the data, not how those
operations are implemented.
•Data Structure Independence: ADTs can be implemented using different data structures, such
as arrays or linked lists, without affecting the functionality of the ADT.
•Information Hiding: ADTs can protect the integrity of the data by allowing access only to
authorized users and operations. This helps prevent errors and misuse of the data.
•Modularity: ADTs can be combined with other ADTs to form larger, more complex data
structures. This allows for greater flexibility and modularity in programming.
Why Use ADTs?

•Encapsulation: Hides complex implementation details behind a clean interface.


•Reusability: Allows different internal implementations (e.g., array or linked list)
without changing external usage.
•Modularity: Simplifies maintenance and updates by separating logic.
•Security: Protects data by preventing direct access, minimizing bugs and
unintended changes.
Abstract Data Types (ADTs) User-Defined Data Types (UDTs)

Defines a class of objects and the operations


A custom data type created by combining or
that can be performed on them, along with
Definition extending existing primitive types, specifying
their expected behavior (semantics), but
both structure and operations.
without specifying implementation details.

What operations are allowed and how they


How data is organized in memory and how
Focus behave, without dictating how they are
operations are executed.
implemented.

Allows programmers to create concrete


Provides an abstract model to define data
Purpose implementations of data structures using
structures in a conceptual way.
primitive types.

Implementation Does not specify how operations are Specifies how to create and organize data
Details implemented or how data is structured. types to implement the structure.

Used to design and conceptualize data Used to implement data structures that realize
Usage
structures. the abstract concepts defined by ADTs.

Example List ADT, Stack ADT, Queue ADT. Structures, classes, enumerations, records.
Examples of ADTs

List ADT
The List ADT (Abstract Data Type) is a sequential collection of elements that supports a
set of operations without specifying the internal implementation. It provides an
ordered way to store, access, and modify data.
Operations:
The List ADT need to store the required data in the sequence and should
have the following operations:
•get(): Return an element from the list at any given position.
•insert(): Insert an element at any position in the list.
•remove(): Remove the first occurrence of any element from a non-empty
list.
•removeAt(): Remove the element at a specified location from a non-
empty list.
•replace(): Replace an element at any position with another element.
•size(): Return the number of elements in the list.
•isEmpty(): Return true if the list is empty; otherwise, return false.
•isFull(): Return true if the list is full, otherwise, return false. Only
applicable in fixed-size implementations (e.g., array-based lists).
Stack ADT
The Stack ADT is a linear data structure that follows the LIFO (Last In, First Out) principle.
It allows elements to be added and removed only from one end, called the top of the stack.
Operations:
In Stack ADT, the order of insertion and deletion should be according to
the FILO or LIFO Principle. Elements are inserted and removed from the
same end, called the top of the stack. It should also support the following
operations:
•push(): Insert an element at one end of the stack called the top.
•pop(): Remove and return the element at the top of the stack, if it is not
empty.
•peek(): Return the element at the top of the stack without removing it, if
the stack is not empty.
•size(): Return the number of elements in the stack.
•isEmpty(): Return true if the stack is empty; otherwise, return false.
•isFull(): Return true if the stack is full; otherwise, return false. Only
relevant for fixed-capacity stacks (e.g., array-based).
3. Queue ADT

The Queue ADT is a linear data structure that follows the FIFO (First In, First Out) principle.
It allows elements to be inserted at one end (rear) and removed from the other end (front).
Operations:
The Queue ADT follows a design similar to the Stack ADT,
but the order of insertion and deletion changes to FIFO.
Elements are inserted at one end (called the rear) and
removed from the other end (called the front). It should
support the following operations:
•enqueue(): Insert an element at the end of the queue.
•dequeue(): Remove and return the first element of the
queue, if the queue is not empty.
•peek(): Return the element of the queue without removing
it, if the queue is not empty.
•size(): Return the number of elements in the queue.
•isEmpty(): Return true if the queue is empty; otherwise,
return false.
Advantage:
The advantages are listed below:
•Encapsulation: ADTs provide a way to encapsulate data and operations into a single unit, making it easier to
manage and modify the data structure.
•Abstraction: ADTs allow users to work with data structures without having to know the implementation details,
which can simplify programming and reduce errors.
•Data Structure Independence: ADTs can be implemented using different data structures, which can make it easier
to adapt to changing needs and requirements.
•Information Hiding: ADTs can protect the integrity of data by controlling access and preventing unauthorized
modifications.
•Modularity: ADTs can be combined with other ADTs to form more complex data structures, which can increase
flexibility and modularity in programming.

Disadvantages:
The disadvantages are listed below:
•Overhead: Implementing ADTs can add overhead in terms of memory and processing, which can affect
performance.
•Complexity: ADTs can be complex to implement, especially for large and complex data structures.
•Learning Curve: Using ADTs requires knowledge of their implementation and usage, which can take time and effort
to learn.
•Limited Flexibility: Some ADTs may be limited in their functionality or may not be suitable for all types of data
structures.
•Cost: Implementing ADTs may require additional resources and investment, which can increase the cost of
development.
Array Data Structure
Array is a collection of items of the same variable type that are stored at contiguous
memory locations.

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.
Memory representation of Array
n an array, all the elements are stored in contiguous memory locations. So, if we initialize an array, the elements
will be allocated sequentially in memory. This allows for efficient access and manipulation of elements.
Declaration of Array

// This array will store integer type


element int arr[5];
// This array will store char type element
char arr[10];
// This array will store float type element
float arr[20];

Initialization of Array

int arr[] = { 1, 2, 3, 4, 5 };
char arr[5] = { 'a', 'b', 'c', 'd', 'e' };
float arr[10] = { 1.4, 2.0, 24, 5.0, 0.0 };
Array Representation
Basic Operations in Arrays
•Traverse − print all the array elements one by one.
•Insertion − Adds an element at the given index.
•Deletion − Deletes an element at the given index.
•Search − Searches an element using the given index or by the value.
•Update − Updates an element at the given index.
•Display − Displays the contents of the array.
Array - Insertion Operation

#include <iostream>
using namespace std;
int main(){
int LA[3] = {}, i;
cout << "Array Before Insertion:" << endl;
for(i = 0; i < 3; i++)
cout << "LA[" << i <<"] = " << LA[i] << endl;
Output
//prints garbage values
Array Before Insertion:
cout << "Inserting elements.." <<endl; LA[0] = 0
cout << "Array After Insertion:" << endl; // prints array LA[1] = 0
values LA[2] = 0
for(i = 0; i < 5; i++) { Inserting elements..
LA[i] = i + 2; Array After Insertion:
cout << "LA[" << i <<"] = " << LA[i] << endl; LA[0] = 2
} LA[1] = 3
return 0; LA[2] = 4
LA[3] = 5
}
LA[4] = 6
Array - Deletion Operation

#include <iostream>
using namespace std;
int main(){
int LA[] = {1,3,5};
int i, n = 3;
cout << "The original array elements are :"<<endl;
for(i = 0; i<n; i++) {
cout << "LA[" << i << "] = " << LA[i] << endl;
}
for(i = 1; i<n; i++) { Output
LA[i] = LA[i+1]; The original array elements are :
n = n - 1; LA[0] = 1
} LA[1] = 3
cout << "The array elements after deletion :"<<endl; LA[2] = 5
for(i = 0; i<n; i++) { The array elements after deletion :
cout << "LA[" << i << "] = " << LA[i] <<endl; LA[0] = 1
} LA[1] = 5
}
Stability in Sorting Algorithms
A sorting algorithm is considered stable if it preserves the relative order of
elements with equal keys in the sorted output. This means that if two elements
have the same value, their original order in the input sequence is maintained
in the sorted sequence.

Example: If you have a list of students sorted by their names, and then you
sort them by their grades, a stable sort would ensure that students with the
same grade remain in alphabetical order by name. An unstable sort might
reorder students with the same grade, losing the original alphabetical order.
Stable Algorithms: Insertion Sort, Merge Sort, Bubble Sort, Counting Sort,
Radix Sort (when implemented with a stable counting sort).
Unstable Algorithms: Quick Sort, Heap Sort, Selection Sort.i
A sorting algorithm is said to be stable if two objects with equal keys appear in the
same order in sorted output as they appear in the input data set
Common internal sorting methods include:
•Bubble Sort:
•Compares adjacent elements and swaps them if they are in the wrong order, repeatedly passing through
the list until no more swaps are needed.
•Insertion Sort:
•Builds the final sorted array one item at a time. It iterates through the input elements and inserts each
element into its correct position in the already sorted part of the array.
•Selection Sort:
•Divides the input list into two parts: a sorted sublist and an unsorted sublist. It repeatedly finds the
minimum element from the unsorted part and puts it at the end of the sorted sublist.
•Merge Sort:
•A divide-and-conquer algorithm that recursively divides the unsorted list into sublists until each sublist
contains only one element (which is considered sorted). Then, it repeatedly merges sublists to produce
new sorted sublists until there is only one sorted list remaining.
•Quick Sort:
•Another divide-and-conquer algorithm that picks an element as a pivot and partitions the array around
the picked pivot.
•Heap Sort:
•Uses a binary heap data structure. It builds a max-heap (or min-heap) from the input data and
repeatedly extracts the maximum (or minimum) element from the heap.
Where stable sorting algorithms are useful? So we might have to sort again to obtain the list of
Consider the following dataset of Student Names students section-wise too. But in doing so, if the
and their respective class sections. sorting algorithm is not stable, we might get a result
(Dava,A) like this:
(Alia,B) (Chinmay,A)
(Ketrina,A) (Dava,A)
(Emran,B) (Ketrina,A)
(Chinmay,A) (Emran,B)
(Alia,B)
If we sort this data according to
name only, then it is highly unlikely The dataset is now sorted according to sections, but not
that the resulting dataset will be according to names.
grouped according to sections as
well. (Chinmay,A)
(Alia,B) (Dava,A)
(Chinmay,A) (Ketrina,A)
(Dava,A) (Alia,B)
(Emran,B) (Emran,B)
(Ketrina,A)
Bubble Sort Algorithm
Bubble Sort is an elementary sorting algorithm, which works by repeatedly exchanging
adjacent elements, if necessary. When no exchanges are required, the file is sorted.

Step 1 − Check if the first element in the input array is greater than
the next element in the array.
Step 2 − If it is greater, swap the two elements; otherwise move the
pointer forward in the array.
Step 3 − Repeat Step 2 until we reach the end of the array.
Step 4 − Check if the elements are sorted; if not, repeat the same
process (Step 1 to Step 3) from the last element of the array to the
first.
Step 5 − The final output achieved is the sorted array.
voidbubbleSort(int numbers[], #include <stdio.h>
intarray_size)
int main() {
{ int my_array[] = {64, 34, 25, 12, 22, 11, 90, 5};
inti, j, temp; int n = sizeof(my_array) / sizeof(my_array[0]);
for (i = (array_size - 1); i>= 0; i--)
for (int i = 0; i < n-1; i++) {
for (j = 1; j <= i; j++) for (int j = 0; j < n-i-1; j++) {
if (numbers[j-1] > numbers[j]) if (my_array[j] > my_array[j+1]) {
{ int temp = my_array[j];
my_array[j] = my_array[j+1];
temp = numbers[j-1]; my_array[j+1] = temp;
numbers[j-1] = numbers[j]; }
numbers[j] = temp; }
}
}
} printf("Sorted array: ");
for (int i = 0; i < n; i++) {
printf("%d ", my_array[i]);
}
printf("\n");

return 0;
}
int main(){
#include<iostream> int n;
using namespace std; n = 5;
void bubbleSort(int *array, int size){ int arr[5] = {67, 44, 82, 17, 20}; //initialize an array
for(int i = 0; i<size; i++) { cout << "Array before Sorting: ";
int swaps = 0; //flag to detect any swap is for(int i = 0; i<n; i++)
there or not cout << arr[i] << " ";
for(int j = 0; j<size-i-1; j++) { cout << endl;
if(array[j] > array[j+1]) { //when the bubbleSort(arr, n);
current item is bigger than next cout << "Array after Sorting: ";
int temp; for(int i = 0; i<n; i++)
temp = array[j]; cout << arr[i] << " ";
array[j] = array[j+1]; cout << endl;
array[j+1] = temp; }
swaps = 1; //set swap flag
}
}
if(!swaps)
break; // No swap in this pass, so array is
sorted
} Output
} Array before Sorting: 67 44 82 17 20
Array after Sorting: 17 20 44 67 82
Insertion Sort Algorithm

Now we have a bigger picture of how this sorting technique works, so we can derive
simple steps by which we can achieve insertion sort.

Step 1 − If it is the first element, it is already sorted. return 1;

Step 2 − Pick next element

Step 3 − Compare with all elements in the sorted sub-list

Step 4 − Shift all the elements in the sorted sub-list that is greater than the value to be
sorted

Step 5 − Insert the value

Step 6 − Repeat until list is sorted


int main(){
#include<iostream> int n;
using namespace std; n = 5;
void insertionSort(int *array, int int arr[5] = {67, 44, 82, 17, 20}; // initialize
size){ the array
int key, j; cout << "Array before Sorting: ";
for(int i = 1; i<size; i++) for(int i = 0; i<n; i++)
{ cout << arr[i] << " ";
key = array[i];//take value cout << endl;
j = i; insertionSort(arr, n);
while(j > 0 && array[j-1]>key) cout << "Array after Sorting: ";
{ for(int i = 0; i<n; i++)
array[j] = array[j-1]; cout << arr[i] << " ";
j--; cout << endl;
} }
array[j] = key; //insert in right
place Output
} Array before Sorting: 67 44 82 17 20
} Array after Sorting: 17 20 44 67 82
Selection Sort

Selection Sort is a comparison-based sorting algorithm. It sorts an array by


repeatedly selecting the smallest (or largest) element from the unsorted
portion and swapping it with the first unsorted element. This process
continues until the entire array is sorted.

1.First we find the smallest element and swap it with the first element. This
way we get the smallest element at its correct position.

2.Then we find the smallest among remaining elements (or second


smallest) and swap it with the second element.

3.We keep doing this until we get all elements moved to correct position.
Pseudocode
Algorithm: Selection-Sort (A)
fori← 1 to n-1 do
min j ←i;
min x ← A[i]
for j ←i + 1 to n do
if A[j] < min x then
min j ← j
min x ← A[j]
A[min j] ← A [i]
A[i] ← min x

1. Set MIN to location 0.


2. Search the minimum element in
the list.
3. Swap with value at location MIN.
4. Increment MIN to point to next
element.
5. Repeat until the list is sorted.
#include<iostream> int main(){
using namespace std; int n;
void swapping(int &a, int &b) n = 5;
{ //swap the content of a and b int arr[5] = {12, 19, 55, 2, 16};
int temp; // initialize the array
temp = a; cout << "Array before Sorting: ";
a = b; for(int i = 0; i<n; i++)
b = temp; cout << arr[i] << " ";
} cout << endl;
void selectionSort(int *array, int size){ selectionSort(arr, n);
int i, j, imin; cout << "Array after Sorting: ";
for(i = 0; i<size-1; i++) { for(int i = 0; i<n; i++)
imin = i; //get index of minimum cout << arr[i] << " ";
data cout << endl;
for(j = i+1; j<size; j++) }
if(array[j] < array[imin])
imin = j;

//placing in correct position


swap(array[i], array[imin]);
}
} Output
Array before Sorting: 12 19 55 2 16
Array after Sorting: 2 12 16 19 55
Quick Sort

QuickSort is a sorting algorithm based on the Divide and Conquer 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.

There are mainly three steps in the algorithm:


1.Choose a Pivot: Select an element from the array as the pivot. The choice of
pivot can vary (e.g., first element, last element, random element, or median).
2.Partition the Array: Rearrange the array around the pivot. After partitioning, all
elements smaller than the pivot will be on its left, and all elements greater than
the pivot will be on its right. The pivot is then in its correct position, and we obtain
the index of the pivot.
3.Recursively Call: Recursively apply the same process to the two partitioned
sub-arrays (left and right of the pivot).
4.Base Case: The recursion stops when there is only one element left in the sub-
array, as a single element is already sorted.
1. Choose the highest index value has pivot
2. Take two variables to point left and right of the list
excluding pivot
3. Left points to the low index
4. Right points to the high
5. While value at left is less than pivot move right
6. While value at right is greater than pivot move left
7. If both step 5 and step 6 does not match swap left and
right
8. If left ≥ right, the point where they met is new pivot

procedure quickSort(left, right)


if right-left <= 0
return
else
pivot = A[right]
partition = partitionFunc(left, right, pivot)
quickSort(left,partition-1)
quickSort(partition+1,right)
end if
end procedure
#include <iostream> cout << "\npivot swapped : " <<
int partition(int left, int right, int pivot) intArray[leftPointer] << "," <<
using namespace std;
{ intArray[right] << endl;
#define MAX 7
int leftPointer = left -1; swap(leftPointer,right);
int intArray[MAX] =
int rightPointer = right; cout << "Updated Array: ";
{4,6,3,2,1,9,7}; display();
void display() { return leftPointer;
while(true) {
int i; }
while(intArray[++leftPointer] <
cout << "["; void quickSort(int left, int right) {
pivot) {
if(right-left <= 0) {
//do nothing return;
// navigate through all items
} } else {
for(i = 0;i < MAX;i++) {
int pivot = intArray[right];
cout << intArray[i] << " ";
while(rightPointer > 0 && int partitionPoint = partition(left,
}
intArray[--rightPointer] > pivot) { right, pivot);
//do nothing quickSort(left, partitionPoint - 1);
cout << "]\n"; quickSort(partitionPoint +
}
} 1,right);
if(leftPointer >= rightPointer) {
}
break; }
void swap(int num1, int num2)
} else {
{
cout << "item swapped : " << int main() {
int temp = intArray[num1];
intArray[leftPointer] << "," << cout << "Input Array: ";
intArray[num1] =
intArray[rightPointer] << endl; display();
intArray[num2]; quickSort(0, MAX-1);
swap(leftPointer, rightPointer);
intArray[num2] = temp; cout << "\nOutput Array: ";
}
} display();
}
}
Output
Input Array: [4 6 3 2 1 9 7 ]

pivot swapped : 9,7


Updated Array: [4 6 3 2 1 7 9 ]

pivot swapped : 4,1


Updated Array: [1 6 3 2 4 7 9 ]
item swapped : 6,2

pivot swapped : 6,4


Updated Array: [1 2 3 4 6 7 9 ]

pivot swapped : 3,3


Updated Array: [1 2 3 4 6 7 9 ]

Output Array: [1 2 3 4 6 7 9 ]
void quicksort(int array[], int low, int high) {
#include <stdio.h> if (low < high) {
int pivotIndex = partition(array, low, high);
void quicksort(int array[], int low, int high); quicksort(array, low, pivotIndex - 1);
int partition(int array[], int low, int high); quicksort(array, pivotIndex + 1, high);
}
int main() { }
int myArray[] = {64, 34, 25, 12, 22, 11, 90, 5}; int partition(int array[], int low, int high) {
int n = sizeof(myArray) / int pivot = array[high];
sizeof(myArray[0]); int i = low - 1;

quicksort(myArray, 0, n - 1); for (int j = low; j < high; j++) {


if (array[j] <= pivot) {
printf("Sorted array: "); i++;
for (int i = 0; i < n; i++) { int temp = array[i];
printf("%d ", myArray[i]); array[i] = array[j];
} array[j] = temp;
return 0; }
} }
int temp = array[i + 1];
array[i + 1] = array[high];
array[high] = temp;
return i + 1;
}
Merge Sort
Merge sort is a popular sorting algorithm known for its efficiency and stability. It follows the divide-and-
conquer approach. It works by recursively dividing the input array into two halves, recursively sorting the
two halves and finally merging them back together to obtain the sorted array.

How does Merge Sort work?

Divide: Divide the list or array recursively into two halves until it can no more be divided.
Conquer: Each subarray is sorted individually using the merge sort algorithm.
Merge: The sorted subarrays are merged back together in sorted order. The process continues until all elements from both subarrays have
been merged.
Conquer:
•[38] is already sorted.
•[27] is already sorted.
•[43] is already sorted.
•[10] is already sorted.

Merge:
•Merge [38] and [27] to get [27, 38] .
•Merge [43] and [10] to get [10,43] .
•Merge [27, 38] and [10,43] to get the final sorted list [10, 27, 38, 43]

Therefore, the sorted list is [10, 27, 38, 43] .


using namespace std;

// Merges two subarrays of arr[]. // Merge the temp vectors back


// First subarray is arr[left..mid] // into arr[left..right]
// Second subarray is arr[mid+1..right] while (i < n1 && j < n2) {
void merge(vector<int>& arr, int left, if (L[i] <= R[j]) {
int mid, int right){ arr[k] = L[i];
i++;
int n1 = mid - left + 1; }
int n2 = right - mid; else {
arr[k] = R[j];
// Create temp vectors j++;
vector<int> L(n1), R(n2); }
k++;
// Copy data to temp vectors L[] and R[] }
for (int i = 0; i < n1; i++)
L[i] = arr[left + i]; // Copy the remaining elements of L[],
for (int j = 0; j < n2; j++) // if there are any
R[j] = arr[mid + 1 + j]; while (i < n1) {
arr[k] = L[i];
int i = 0, j = 0; i++;
int k = left; k++;
}
// Copy the remaining elements of R[], // Driver code
// if there are any int main(){
while (j < n2) {
arr[k] = R[j]; vector<int> arr = {38, 27, 43, 10};
j++; int n = arr.size();
k++;
} mergeSort(arr, 0, n - 1);
} for (int i = 0; i < arr.size(); i++)
cout << arr[i] << " ";
// begin is for left index and end is right index cout << endl;
// of the sub-array of arr to be sorted
void mergeSort(vector<int>& arr, int left, int right) return 0;
{ }

if (left >= right)


return;

int mid = left + (right - left) / 2;


mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
} Output
10 27 38 43
Shell Sort Algorithm Pseudocode
procedure shellSort()
A : array of items
Shell sort is a highly efficient sorting
algorithm and is based on insertion sort /* calculate interval*/
while interval < A.length /3 do:
algorithm. This algorithm avoids large interval = interval * 3 + 1
end while
shifts as in case of insertion sort, if the
smaller value is to the far right and has to while interval > 0 do:
for outer = interval; outer < A.length; outer ++ do:
be moved to the far left.
/* select value to be inserted */
This algorithm uses insertion sort on a valueToInsert = A[outer]
inner = outer;
widely spread elements, first to sort them
and then sorts the less widely spaced /*shift element towards right*/
while inner > interval -1 && A[inner - interval]
elements. This spacing is termed >= valueToInsert do:
A[inner] = A[inner - interval]
as interval. inner = inner interval
Shell Sort Algorithm end while

1. Initialize the value of h. /* insert the number at hole position */


A[inner] = valueToInsert
2. Divide the list into smaller sub-list of end for
equal interval h. /* calculate interval*/
3. Sort these sub-lists using insertion sort. interval = (interval -1) /3;
end while
4. Repeat until complete list is sorted. end procedure
#include<iostream>
using namespace std; int main(){
void shellSort(int *arr, int n){ int n;
int gap, j, k; n = 5;
for(gap = n/2; gap > 0; gap = gap / 2) int arr[5] = {33, 45, 62, 12, 98}; // initialize the array
{ //initially gap = n/2, decreasing by gap /2 cout << "Array before Sorting: ";
for(j = gap; j<n; j++) for(int i = 0; i<n; i++)
{ cout << arr[i] << " ";
for(k = j-gap; k>=0; k -= gap) cout << endl;
{ shellSort(arr, n);
if(arr[k+gap] >= arr[k]) cout << "Array after Sorting: ";
break; for(int i = 0; i<n; i++)
else cout << arr[i] << " ";
{ cout << endl;
int temp; }
temp = arr[k+gap];
arr[k+gap] = arr[k];
arr[k] = temp;
}
}
}
}
}
Radix Sort is a non-comparative integer sorting algorithm that sorts data
by processing individual digits or characters. It avoids direct comparisons
between elements, instead relying on the distribution of elements into
"buckets" based on the value of a specific digit or character position.
How it works:
•Iterative Sorting by Digit/Character:
•Radix Sort typically works by sorting the input elements repeatedly, one
digit or character at a time, starting from either the Least Significant Digit
(LSD) or Most Significant Digit (MSD).
•LSD Radix Sort: Starts sorting by the rightmost digit (least significant)
and proceeds towards the leftmost digit (most significant). This is more
common for numerical sorting.
•MSD Radix Sort: Starts sorting by the leftmost digit (most significant)
and proceeds towards the rightmost digit (least significant). This is often
used for string sorting.
Step 1
236, 143, 026, 042, 001, 099, 765, 482, 003, 056

Step 2

Construct a
table to Step 3
store the
values Based on
based on the least
their significant
indexing. digit of all
Since the the
inputs given numbers,
are decimal place the
numbers, numbers
the on their
indexing is respective
done based indices.
on the
possible
values of The elements sorted after this step would be 001, 042,
these digits, 482, 143, 003, 765, 236, 026, 056, 099.
i.e., 0-9
Step 4
Step 5
001, 003, 026, 236, 042, 143, 056, 765, 482, 099
The order
of input for
this step
would be
the order
of the
output in
the
previous
step. Now,
we
perform
sorting
using the
second
least
significant
digit.
The final sorted output is −
The order of the output achieved is 001, 003, 026, 236,
042, 143, 056, 765, 482, 099. 1, 3, 26, 42, 56, 99, 143, 236, 482, 765
void radixsort(int a[], int n){
int max = a[0];
include <iostream>
for (int i = 1; i < n; i++)
using namespace std;
if (a[i] > max)
void countsort(int a[], int n, int pos){
max = a[i];
int output[n + 1];
for (int pos = 1; max / pos > 0; pos *= 10)
int max = (a[0] / pos) % 10;
countsort(a, n, pos);
for (int i = 1; i < n; i++) {
}
if (((a[i] / pos) % 10) > max)
int main(){
max = a[i];
int a[] = {236, 15, 333, 27, 9, 108, 76, 498};
}
int n = sizeof(a) / sizeof(a[0]);
int count[max + 1];
cout <<"Before sorting array elements are: ";
for (int i = 0; i < max; ++i)
for (int i = 0; i < n; ++i) {
count[i] = 0;
cout <<a[i] << " ";
for (int i = 0; i < n; i++)
}
count[(a[i] / pos) % 10]++;
radixsort(a, n);
for (int i = 1; i < 10; i++)
cout <<"\nAfter sorting array elements are: ";
count[i] += count[i - 1];
for (int i = 0; i < n; ++i) {
for (int i = n - 1; i >= 0; i--) {
cout << a[i] << " ";
output[count[(a[i] / pos) % 10] - 1] = a[i];
}
count[(a[i] / pos) % 10]--;
cout << "\n";
}
}
for (int i = 0; i < n; i++) Output
a[i] = output[i]; Before sorting array elements are: 236 15 333 27 9 108 76 498
} After sorting array elements are: 9 15 27 76 108 236 333 498

You might also like